Java 8和Java 9中,Java超级接口的运行时差异

8
我注意到在Java8和Java9中运行以下程序的输出不同。
import java.lang.reflect.Method;
public class OrderingTest {
    public static void main(String[] args) {
        ServiceImpl service = new ServiceImpl();
        for (Method method : service.getClass().getMethods()) {
            for (Class<?> anInterface : method.getDeclaringClass().getInterfaces()) {
                try {
                    Method intfMethod = anInterface.getMethod(method.getName(), method.getParameterTypes());
                    System.out.println("intfMethod = " + intfMethod);
                } catch (NoSuchMethodException e) { }
            }
        }
    }
}

class ServiceImpl implements ServiceX {
    @Override
    public Foo getType() { return null; }
}

interface ServiceX extends ServiceA<Foo>, ServiceB { }
abstract class Goo { }
class Foo extends Goo { }

interface ServiceA<S> {
    S getType();
}
interface ServiceB {
    @java.lang.Deprecated
    Goo getType();
}

你可以在这里运行两个版本的Java: https://www.jdoodle.com/online-java-compiler/ Java 8输出:
intfMethod = public abstract java.lang.Object ServiceA.getType()
intfMethod = public abstract java.lang.Object ServiceA.getType()
intfMethod = public abstract java.lang.Object ServiceA.getType()

Java 9 输出:
intfMethod = public abstract Goo ServiceB.getType()
intfMethod = public abstract Goo ServiceB.getType()
intfMethod = public abstract Goo ServiceB.getType()

但是当我重新排序超级接口为:

interface ServiceX extends ServiceB, ServiceA<Foo> { }

然后两个版本的Java都输出:

intfMethod = public abstract Goo ServiceB.getType()
intfMethod = public abstract Goo ServiceB.getType()
intfMethod = public abstract Goo ServiceB.getType()

我在想是什么导致了这个问题?是否有我不知道的新Java功能?
Java 8文档 https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.4.8 Java 9文档 https://docs.oracle.com/javase/specs/jls/se9/html/jls-8.html#jls-8.4.8
2个回答

7
区别似乎在于使用的getMethod API实现不同,这一点可以从所述Java-9开始的文档中看到:

在每个子集内,只选择最具体的方法。 设M为具有相同VM签名(返回类型、名称、参数类型)的一组方法中的一种方法。如果没有来自同一组的其他方法N != M,使得N比M更具体,则M是最具体的方法。如果:

a. N由类声明,而M由接口声明;或

b. N和M都由类或接口声明,并且N的声明类型与M的声明类型相同或是其子类型(显然,如果M和N的声明类型是相同的类型,则M和N是同一方法)。

虽然Java-8在内部跟进了interfaceCandidates.getFirst()(即顺序变化很重要),但升级版似乎正在使用特定算法,在返回所请求的方法之前使用res.getMostSpecific()


正如你所说,旧版Java 8 https://github.com/openjdk/jdk/blob/jdk8-b120/jdk/src/share/classes/java/lang/Class.java 调用getMethod0返回第一个结果,但Java v9 https://github.com/openjdk/jdk/blob/jdk9-b94/jdk/src/java.base/share/classes/java/lang/Class.java 则调用MethodArray.removeLessSpecifics()。 - Hugh Pearse

-4

答案已从问题正文移动:


根本原因在于Java类型擦除在编译时会创建一个返回类型为“Object”的桥接方法,这比返回类型为“Goo”不够具体。Java 9对Class.getMethod()的实现进行了更新,从返回第一个定义的接口实现改为返回最具体的实现。
foo@bar:~$ javap ./ServiceImpl.class
Compiled from "OrderingTest.java"
class ServiceImpl implements ServiceX {
  ServiceImpl();
  public Foo getType();
  public java.lang.Object getType(); <== here
  public Goo getType();
}

在Java 9中,可以通过添加另一个接口来覆盖泛型方法并使用更具体的API来维护Java 8中的旧功能。这将与Java 8中一样在Java 9中运行。

import java.lang.reflect.Method;
public class OrderingTest {
    public static void main(String[] args) {
        ServiceImpl service = new ServiceImpl();
        for (Method method : service.getClass().getMethods()) {
            for (Class<?> anInterface : method.getDeclaringClass().getInterfaces()) {
                try {
                    Method intfMethod = anInterface.getMethod(method.getName(), method.getParameterTypes());
                    System.out.println("intfMethod = " + intfMethod);
                } catch (NoSuchMethodException e) { }
            }
        }
    }
}

class ServiceImpl implements ServiceX {
    @Override
    public Foo getType() { return new Foo(); }
}

interface ServiceX extends ServiceAA, ServiceB { }
abstract class Goo { }
class Foo extends Goo { }
interface ServiceAA extends ServiceA<Foo> {
    @Override
    Foo getType();
}
interface ServiceA<S> {
    S getType();
}
interface ServiceB {
    @java.lang.Deprecated
    Goo getType();
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接