I have the following code, with a generic ITest
interface extended by a not generic ITestDouble
interface. The op
method is overridden by ITestDouble
.
When I try to list all the methods of ITestDouble
, I get op
twice. How can I verify that they are actually the same method?
public class Test {
public static void main(String[] args) throws NoSuchMethodException {
for (Method m : ITestDouble.class.getMethods()) {
System.out.println(m.getDeclaringClass() + ": " + m + "(bridge: " + m.isBridge() + ")");
}
}
public interface ITestDouble extends ITest<Double> {
@Override
public int op(Double value);
@Override
public void other();
}
public interface ITest<T extends Number> {
public int op(T value);
public void other();
}
}
Output:
interface Test$ITestDouble: public abstract int Test$ITestDouble.op(java.lang.Double)(bridge: false)
interface Test$ITestDouble: public abstract void Test$ITestDouble.other()(bridge: false)
interface Test$ITest: public abstract int Test$ITest.op(java.lang.Number)(bridge: false)
PS I know this is the same question as Java Class.getMethods() behavior on overridden methods, but that question got no real answer: the isBridge()
call always returns false
.
EDIT:
I'm also fine with any library which would do the dirty job of filtering out the "duplicate" op
method for me.
Unfortunately you cannot have that information, because as far as the JVM is concerned, ITestDouble
has a legitimate method op(Number)
which can be totally independent of op(Double)
. It is actually your Java compiler that makes sure the methods always coincide.
That implies that you can create pathological implementations of ITestDouble
with totally different implementations for op(Number)
and op(Double)
by using a pre-JDK5 compiler, or a dynamic proxy:
public static void main(String[] args) throws NoSuchMethodException {
final Method opNumber = ITest.class.getMethod("op", Number.class);
final Method opDouble = ITestDouble.class.getMethod("op", Double.class);
final Method other = ITestDouble.class.getMethod("other");
ITestDouble dynamic = (ITestDouble) Proxy.newProxyInstance(
ITestDouble.class.getClassLoader(),
new Class<?>[]{ITestDouble.class},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method m, Object[] args) throws Throwable {
if (opDouble.equals(m)) return 1;
if (opNumber.equals(m)) return 2;
// etc....
return null;
}
});
System.out.println("op(Double): " + dynamic.op(null); // prints 1.
System.out.println("op(Number): " + ((ITest) dynamic).op(null); // prints 2. Compiler gives warning for raw types
}
EDIT: Just learned of Java ClassMate. It is a library that can correctly resolve all type variables in a declaration. It is very easy to use:
TypeResolver typeResolver = new TypeResolver();
MemberResolver memberResolver = new MemberResolver(typeResolver);
ResolvedType type = typeResolver.resolve(ITestDouble.class);
ResolvedTypeWithMembers members = memberResolver.resolve(type, null, null);
ResolvedMethod[] methods = members.getMemberMethods();
Now if you iterate over methods
you'll see the following:
void other();
int op(java.lang.Double);
int op(java.lang.Double);
Now it is easy to filter for duplicates:
public boolean canOverride(ResolvedMethod m1, ResolvedMethod m2) {
if (!m1.getName().equals(m2.getName())) return false;
int count = m1.getArgumentCount();
if (count != m2.getArgumentCount()) return false;
for (int i = 0; i < count; i++) {
if (!m1.getArgumentType(i).equals(m2.getArgumentType(i))) return false;
}
return true;
}
Every respondent so far has positively contributed, but I'll try to wrap the different ideas up into a single answer. The key to understanding what is going on is how the Java compiler and JVM implement generics - that explains the bridge, and why it is false in the interface.
In summary, though:
The more generic method int op(Number)
is required for signature compatibility and to construct the implementation's bridge method
The isBridge()
method can only be true for concrete classes, not interfaces
It probably doesn't matter which of the two methods you pick up - they will work identically in the runtime.
Ok, here's the long answer:
Java's implementation of Generics
When you have a generic class or interface, the compiler constructs a method in the class file with each generic type replaced with an appropriate concrete type. For example, ITest
has a method:
int op(T value)
where the class defines T
as T extends Number
, so the class file has a method:
int op(Number);
When ITest
is used, the compiler creates additional classes for each type the generic is resolved to. For example, if there is a line of code:
ITest<Double> t = new ...
The compiler produces a class with the following methods:
int op(Number);
int op(Double);
But surely the JVM only needs the int op(Double)
version? Doesn't the compiler make sure that ITest<Double>
only receives calls on op(Double)
?
There are two reasons why Java needs the int op(Number)
method:
Object orientation requires that wherever you use a class you can always replace it by a subclass (at least from type safety). If int op(Number)
did not exist, the class will not provide a full implementation of the signature of the superclass (or super-interface).
Java is a dynamic language with both casting and reflection, so it is possible to call the method with an incorrect type. At this point Java guarantees that you get a class cast exception.
In fact, the implementation of 2. above is achieved by the compiler producing a 'bridge method'.
What does a bridge method do?
When ITest<Double>
is created from ITest<T extends Number>
, the compiler creates the int op(Number)
method, and its implementation is:
public int op(Number n) {
return this.op((Double) n);
}
This implementation has two properties:
Where n is a Double
, it delegates the call to int op(Double)
, and
Where n is not a Double
, it causes the ClassCastException
.
This method is a 'bridge' from the generic type to the concrete type. From its very nature, only concrete methods can be bridges, so the int op(Double)
on the sub-interface is only a signature.
What about the OP?
In the example in the question, the sub-interface class file ITestDouble
created by the compiler has both the methods:
int op(Number);
int op(Double);
The int op(Number)
is needed so that implementations of ITestDouble
can have their bridge method - but this method is not itself a bridge because it is only a signature, not an implementation. Arguably Sun/Oracle have missed a trick here, and it might be worth raising a bug with them.
How to find the correct method?
First, does it matter? All implementations of ITestDouble
will have the bridge method inserted automatically by the compiler and the bridge method calls the int op(Double)
method. In other words, it really doesn't matter which method is called, just pick one.
Second, at runtime you most likely will be passed instances, not Interfaces. When you do the getMethods()
, you will be able to distinguish between the bridge method and the actual implementation. This is what johncarl said.
Third, if you do need to solve this by interrogating the interface, you might want to test the arguments for the 'lowest' subtype. Eg, in a meta level:
Collect all the two methods with the same name
Collect the argument type: Method.getParameterTypes()[0]
Use Class.isAssignableFrom(Class)
. The method returns true if the argument is the same or a subclass of the class on which the method is called.
Use the method with the argument being the subclass of the other method's argument.
This might work for what you need. Tweak it for specific needs or any fail cases. In short, it checks to see if the method matches the exact 'generic' type declared for the class from which is declared. If you are using your own generics, this might fall-over. I would suggest combining calls to getDeclaringClass() with the logic for your own generics.
public static boolean matchesGenericSignature(Method m) {
Type[] parameters = m.getGenericParameterTypes();
if (parameters.length == 0)
return false;
Class<?> declaring = m.getDeclaringClass();
TypeVariable<?>[] types = declaring.getTypeParameters();
for (TypeVariable<?> typeVariable : types) {
for (Type parameter : parameters) {
if (typeVariable.equals(parameter)) {
return true;
}
}
}
return false;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With