我有以下代码,通用ITest
接口由非通用ITestDouble
接口扩展。 op
方法会覆盖ITestDouble
方法。
当我尝试列出ITestDouble
的所有方法时,我得到op
两次。如何验证它们实际上是相同的方法?
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();
}
}
输出:
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我知道这是与Java Class.getMethods() behavior on overridden methods相同的问题,但这个问题没有得到真正的答案:isBridge()
调用始终返回false
。
编辑:
对于任何可以为我过滤掉“重复”op
方法的脏工作,我也很好。
答案 0 :(得分:7)
很遗憾,您无法获得该信息,因为就 JVM 而言,ITestDouble
有一个合法的方法op(Number)
,它可以完全独立于{{1} }。它实际上是您的Java 编译器,可确保方法始终重合。
这意味着您可以使用JDK5之前的编译器或动态代理创建op(Double)
的病态实现,其中ITestDouble
和op(Number)
的实现完全不同:
op(Double)
修改强> 刚学会了Java ClassMate。它是一个可以正确解析声明中所有类型变量的库。它非常易于使用:
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
}
现在如果你迭代 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();
,你会看到以下内容:
methods
现在可以轻松过滤重复项:
void other();
int op(java.lang.Double);
int op(java.lang.Double);
答案 1 :(得分:3)
到目前为止,每位受访者都做出了积极的贡献,但我会尝试将不同的想法整理成一个答案。理解正在发生的事情的关键是Java编译器和JVM如何实现泛型 - 这解释了桥接器,以及为什么它在界面中是错误的。
总之,尽管如此:
签名兼容性和构建实现的桥接方法需要更通用的方法int op(Number)
isBridge()
方法只适用于具体类,而不是接口
你选择的两种方法中的哪一种可能并不重要 - 它们在运行时的工作方式相同。
好的,这是一个很长的答案:
Java的泛型实现
当你有一个泛型类或接口时,编译器在类文件中构造一个方法,每个泛型类型都用适当的具体类型替换。例如,ITest
有一个方法:
int op(T value)
其中类将T
定义为T extends Number
,因此类文件有一个方法:
int op(Number);
使用ITest
时,编译器会为解析泛型的每种类型创建其他类。例如,如果有一行代码:
ITest<Double> t = new ...
编译器使用以下方法生成一个类:
int op(Number);
int op(Double);
但是JVM肯定只需要int op(Double)
版本吗?编译器是否确保ITest<Double>
仅接收op(Double)
上的呼叫?
Java需要int op(Number)
方法的原因有两个:
面向对象要求无论在何处使用类,您都可以始终将其替换为子类(至少从类型安全性)。如果int op(Number)
不存在,则该类将不提供超类(或超级接口)签名的完整实现。
Java是一种具有强制转换和反射的动态语言,因此 可以使用不正确的类型调用该方法。此时,Java保证您获得类强制转换异常。
实际上,上面的2.的实现是通过编译器产生'桥接方法'来实现的。
桥接方法有什么作用?
从ITest<Double>
创建ITest<T extends Number>
时,编译器会创建int op(Number)
方法,其实现方式为:
public int op(Number n) {
return this.op((Double) n);
}
此实现有两个属性:
如果n是Double
,则会将调用委托给int op(Double)
,
如果n 不 Double
,则会导致ClassCastException
。
此方法是从泛型类型到具体类型的“桥梁”。从本质上讲,只有具体的方法可以是桥梁,因此子接口上的int op(Double)
只是一个签名。
OP怎么样?
在问题的示例中,编译器创建的子接口类文件ITestDouble
具有以下两种方法:
int op(Number);
int op(Double);
int op(Number)
是必需的,以便ITestDouble
的实现可以拥有其桥接方法 - 但这种方法本身不是桥梁,因为它只是一个签名,而不是实现。可以说Sun / Oracle在这里错过了一个技巧,并且可能值得为它们提出一个错误。
如何找到正确的方法?
首先,它是否重要? ITestDouble
的所有实现都将由编译器自动插入桥接方法,并且桥接方法调用int op(Double)
方法。换句话说,调用哪个方法并不重要,只需选择一个。
其次,在运行时,您很可能会传递实例,而不是接口。执行getMethods()
时,您将能够区分桥接方法和实际实现。这就是johncarl所说的。
第三,如果你确实需要通过询问接口来解决这个问题,你可能想要测试“最低”子类型的参数。例如,在元级别:
收集所有两个名称相同的方法
收集参数类型:Method.getParameterTypes()[0]
使用Class.isAssignableFrom(Class)
。如果参数与调用方法的类的子类相同,则该方法返回true。
使用参数为另一个方法参数的子类的方法。
答案 2 :(得分:2)
这可能适合您的需要。根据特定需求或任何失败案例进行调整。简而言之,它检查方法是否匹配为声明的类声明的确切“泛型”类型。如果您使用自己的泛型,这可能会失败。我建议将对getDeclaringClass()的调用与你自己的泛型的逻辑结合起来。
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;
}
答案 3 :(得分:1)
更新
对于您的解决方案,请尝试此方法(Method.getGenericParameterTypes()):
public static void main(String[] args) {
for (Method m : ITestDouble.class.getMethods()) {
Type [] types = m.getGenericParameterTypes();
System.out.println(m.getDeclaringClass() + ": " + m + "(genericParameterTypes: "
+ Arrays.toString(types) + ")"+" "+(types.length>0?types[0].getClass():""));
Type t = types.length>0?types[0]:null;
if(t instanceof TypeVariable){
TypeVariable<?> v = (TypeVariable)t;
System.out.println(v.getName()+": "+Arrays.toString(v.getBounds()));
}
}
}
输出是:
interface FakeTest$ITestDouble: public abstract int FakeTest$ITestDouble.op(java.lang.Double)(genericParameterTypes: [class java.lang.Double]) class java.lang.Class
interface FakeTest$ITestDouble: public abstract void FakeTest$ITestDouble.other()(genericParameterTypes: [])
interface FakeTest$ITest: public abstract int FakeTest$ITest.op(java.lang.Number)(genericParameterTypes: [T]) class sun.reflect.generics.reflectiveObjects.TypeVariableImpl
T: [class java.lang.Number]
在编译期间会删除泛型。所以你真的有:
public interface ITestDouble extends ITest {
public int op(Double value);
@Override
public void other();
}
public interface ITest {
public int op(Number value);
public void other();
}
Class ITest不知道你有多少实现。所以它只有一个方法op参数Number。您可以使用 T extends Number 定义无限实现。 (在你的T = Double)。
答案 4 :(得分:1)
您可以使用方法getDeclaredMethods()而不是getMethods()来仅获取您要反映的类(而不是超类)上声明的方法。它解决了重复方法的问题。
答案 5 :(得分:0)
我知道这可能无法回答您的问题或100%解决您的问题,但您可以使用isBridge()方法来确定具体类实现的方法与通常“桥接”的方法一样:
public class Test {
public static void main(String[] args) throws NoSuchMethodException {
for (Method m : TestDouble.class.getMethods()) {
System.out.println(m.getDeclaringClass() + ": " + m + "(bridge: " + m.isBridge() + ")");
}
}
public class TestDouble extends ITestDouble{
public int op(Double value) {
return 0;
}
public void other() {
}
}
public interface ITestDouble extends ITest<Double> {
public int op(Double value);
public void other();
}
public interface ITest<T extends Number> {
public int op(T value);
public void other();
}
}
输出:
class test.Test$TestDouble: public int test.Test$TestDouble.op(java.lang.Double)(bridge: false)
class test.Test$TestDouble: public int test.Test$TestDouble.op(java.lang.Number)(bridge: true)
class test.Test$TestDouble: public void test.Test$TestDouble.other()(bridge: false)
class java.lang.Object: public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException(bridge: false)
class java.lang.Object: public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException(bridge: false)
class java.lang.Object: public final void java.lang.Object.wait() throws java.lang.InterruptedException(bridge: false)
class java.lang.Object: public boolean java.lang.Object.equals(java.lang.Object)(bridge: false)
class java.lang.Object: public java.lang.String java.lang.Object.toString()(bridge: false)
class java.lang.Object: public native int java.lang.Object.hashCode()(bridge: false)
class java.lang.Object: public final native java.lang.Class java.lang.Object.getClass()(bridge: false)
class java.lang.Object: public final native void java.lang.Object.notify()(bridge: false)
class java.lang.Object: public final native void java.lang.Object.notifyAll()(bridge: false)
值得注意的是:
Test$TestDouble.op(java.lang.Number)(bridge: true)