繁体   English   中英

关于接口重写方法的思考

[英]Reflection on interface overridden methods

我有以下代码,通用ITest接口由非通用ITestDouble接口扩展。 ITestDouble会覆盖op方法。

当我尝试列出的所有方法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()行为相同的问题,但这个问题没有得到真正的答案: isBridge()调用总是返回false

编辑:我也很好,任何库op以为我过滤掉“重复”的op方法。

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

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

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

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

总之,尽管如此:

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

  2. isBridge()方法只能对具体类而不是接口都是true

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

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

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保证您获得类强制转换异常。

实际上,上述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

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

OP怎么样?

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

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

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

如何找到正确的方法?

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

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

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

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

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

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

  4. 使用该方法,其中参数是另一个方法参数的子类

这可能适合您的需要。 根据特定需求或任何失败案例进行调整。 简而言之,它检查方法是否匹配为声明的类声明的确切“泛型”类型。 如果您使用自己的泛型,这可能会失败。 我建议将对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;
}

更新:

对于您的解决方案,请尝试此方法(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类不知道你有多少实现。 所以它只有一个方法op参数Number。 您可以使用T extends Number定义无限实现。 (在你的T = Double)。

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

我知道这可能无法回答您的问题或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)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM