关于接口重写方法的思考

时间:2012-09-11 15:54:10

标签: java generics reflection override

我有以下代码,通用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方法的脏工作,我也很好。

6 个答案:

答案 0 :(得分:7)

很遗憾,您无法获得该信息,因为就 JVM 而言,ITestDouble有一个合法的方法op(Number),它可以完全独立于{{1} }。它实际上是您的Java 编译器,可确保方法始终重合。

这意味着您可以使用JDK5之前的编译器或动态代理创建op(Double)的病态实现,其中ITestDoubleop(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如何实现泛型 - 这解释了桥接器,以及为什么它在界面中是错误的。

总之,尽管如此:

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

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

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

  4. 好的,这是一个很长的答案:

    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)方法的原因有两个:

    1. 面向对象要求无论在何处使用类,您都可以始终将其替换为子类(至少从类型安全性)。如果int op(Number)不存在,则该类将不提供超类(或超级接口)签名的完整实现。

    2. Java是一种具有强制转换和反射的动态语言,因此 可以使用不正确的类型调用该方法。此时,Java保证您获得类强制转换异常。

    3. 实际上,上面的2.的实现是通过编译器产生'桥接方法'来实现的。

      桥接方法有什么作用?

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

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

      此实现有两个属性:

      1. 如果n是Double,则会将调用委托给int op(Double)

      2. 如果n Double,则会导致ClassCastException

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

        OP怎么样?

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

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

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

        如何找到正确的方法?

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

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

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

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

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

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

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

答案 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)