6

我有以下代码,其通用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为我过滤掉“重复”方法的肮脏工作的库。

4

6 回答 6

7

不幸的是,您无法获得该信息,因为就JVM而言,它ITestDouble具有op(Number)完全独立于op(Double). 实际上是您的 Java编译器确保方法始终一致。

这意味着您可以使用 JDK5 之前的编译器或动态代理创建ITestDouble具有完全不同实现的op(Number)病态实现:op(Double)

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
}

编辑: 刚刚了解了Java ClassMate。它是一个可以正确解析声明中所有类型变量的库。这是非常容易使用:

    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);

现在很容易过滤重复项:

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;
}
于 2012-09-18T08:29:33.117 回答
4

到目前为止,每个受访者都做出了积极贡献,但我会尝试将不同的想法总结成一个答案。理解发生了什么的关键是 Java 编译器和 JVM 如何实现泛型——这解释了桥,以及为什么它在接口中是错误的。

总之,虽然:

  1. int op(Number)签名兼容性和构建实现的桥接方法需要更通用的方法

  2. isBridge()方法仅适用于具体类,而不适用于接口

  3. 您选择这两种方法中的哪一种可能并不重要——它们在运行时中的工作方式相同。

好的,这是长答案:

Java 的泛型实现

当您有一个泛型类或接口时,编译器会在类文件中构造一个方法,其中每个泛型类型都替换为适当的具体类型。例如,ITest有一个方法:

int op(T value)

其中类定义TT extends Number,所以类文件有一个方法:

int op(Number);    

使用ITest时,编译器会为泛型解析为的每种类型创建额外的类。例如,如果有一行代码:

ITest<Double> t = new ...

编译器使用以下方法生成一个类:

int op(Number);
int op(Double);

但是JVM肯定只需要int op(Double)版本吗?编译器不确保ITest<Double>只接收调用op(Double)吗?

int op(Number)Java 需要该方法的原因有两个:

  1. 面向对象要求无论你在哪里使用一个类,你都可以用一个子类替换它(至少从类型安全的角度来看)。如果int op(Number)不存在,该类将不会提供超类(或超接口)签名的完整实现。

  2. Java 是一种同时具有强制转换和反射的动态语言,因此可能调用错误类型的方法。在这一点上,Java 保证你得到一个类转换异常。

实际上,上面2.的实现是由编译器产生一个“桥接方法”来实现的。

桥接方法有什么作用?

ITest<Double>is created fromITest<T extends Number>时,编译器创建该int op(Number)方法,其实现为:

public int op(Number n) {
    return this.op((Double) n);
} 

这个实现有两个属性:

  1. 其中 n 是 a Double,它将调用委托给int op(Double),并且

  2. 当 n不是a Double时,它会导致ClassCastException

此方法是从泛型类型到具体类型的“桥梁”。从本质上讲,只有具体的方法才能成为桥梁,所以int op(Double)子接口上的 只是一个签名。

OP呢?

在问题的示例中,ITestDouble编译器创建的子接口类文件具有两种方法:

int op(Number);
int op(Double);

int op(Number)是必需的,以便实现ITestDouble可以拥有它们的桥接方法 -但此方法本身不是桥接,因为它只是一个签名,而不是一个实现。可以说 Sun/Oracle 在这里错过了一个技巧,可能值得向他们提出一个错误。

如何找到正确的方法?

首先,重要吗?的所有实现都ITestDouble将由编译器自动插入桥接方法,并且桥接方法调用该int op(Double)方法。换句话说,调用哪个方法并不重要,只要选择一个即可。

其次,在运行时你很可能会被传递实例,而不是接口。当您执行 时getMethods(),您将能够区分桥接方法和实际实现。这是约翰卡尔所说的。

第三,如果您确实需要通过询问接口来解决这个问题,您可能需要测试“最低”子类型的参数。例如,在元级别:

  1. 收集所有同名的两个方法

  2. 收集参数类型:Method.getParameterTypes()[0]

  3. 使用Class.isAssignableFrom(Class). 如果参数相同或者是调用该方法的类的子类,则该方法返回 true。

  4. 使用参数是另一个方法参数的子类的方法。

于 2012-09-21T08:39:55.897 回答
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;
}
于 2012-09-20T08:08:10.150 回答
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();
    }

ITest 类不知道你有多少实现。所以它只有一个带有参数 Number 的方法 op。您可以使用T extends Number定义无限实现。(在你的 T = 双倍)。

于 2012-09-11T16:17:54.457 回答
1

您可以使用方法getDeclaredMethods()而不是 getMethods() 来仅获取在您所反映的类(不是超类)上声明的方法。它解决了重复方法的问题。

于 2012-09-14T18:15:21.470 回答
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)
于 2012-09-11T16:58:03.283 回答