Java编译器如何为具有多个边界的参数化类型选择运行时类型?

GOT*_*O 0 25 java variadic-functions jls multiple-bounds

我想更好地理解当Java编译器遇到如下方法的调用时会发生什么.

<T extends AutoCloseable & Cloneable>
void printType(T... args) {
    System.out.println(args.getClass().getComponentType().getSimpleName());
}

// printType() prints "AutoCloseable"
Run Code Online (Sandbox Code Playgroud)

我很清楚,<T extends AutoCloseable & Cloneable>在运行时没有类型,因此编译器可以做出最少的错误,并创建一个类型为两个边界接口之一的数组,丢弃另一个.

无论如何,如果切换接口的顺序,结果仍然是相同的.

<T extends Cloneable & AutoCloseable>
void printType(T... args) {
    System.out.println(args.getClass().getComponentType().getSimpleName());
}

// printType() prints "AutoCloseable"
Run Code Online (Sandbox Code Playgroud)

这导致我做了一些调查,看看接口发生变化时会发生什么.在我看来,编译器使用某种严格的顺序规则来决定哪个接口是最重要的,并且接口在代码中出现的顺序不起作用.

<T extends AutoCloseable & Runnable>                             // "AutoCloseable"
Run Code Online (Sandbox Code Playgroud)
<T extends Runnable & AutoCloseable>                             // "AutoCloseable"
Run Code Online (Sandbox Code Playgroud)
<T extends AutoCloseable & Serializable>                         // "Serializable"
Run Code Online (Sandbox Code Playgroud)
<T extends Serializable & AutoCloseable>                         // "Serializable"
Run Code Online (Sandbox Code Playgroud)
<T extends SafeVarargs & Serializable>                           // "SafeVarargs"
Run Code Online (Sandbox Code Playgroud)
<T extends Serializable & SafeVarargs>                           // "SafeVarargs"
Run Code Online (Sandbox Code Playgroud)
<T extends Channel & SafeVarargs>                                // "Channel"
Run Code Online (Sandbox Code Playgroud)
<T extends SafeVarargs & Channel>                                // "Channel"
Run Code Online (Sandbox Code Playgroud)
<T extends AutoCloseable & Channel & Cloneable & SafeVarargs>    // "Channel"
Run Code Online (Sandbox Code Playgroud)

问题: 当存在多个边界时,Java编译器如何确定参数化类型的varargs数组的组件类型?

我甚至不确定JLS是否对此有所说明,而且我通过谷歌搜索发现的任何信息都没有涵盖这一特定主题.

She*_*epy 12

通常,当编译器遇到对参数化方法的调用时,它可以推断出类型(JSL 18.5.2)并且可以在调用者中创建正确类型的vararg数组.

规则主要是说"找到所有可能的输入类型并检查它们"的技术方法(例如void,三元运算符或lambda).其余的是常识,例如使用最具体的公共基类(JSL 4.10.4).例:

public class Test {
   private static class A implements AutoCloseable, Runnable {
         @Override public void close () throws Exception {}
         @Override public void run () {} }
   private static class B implements AutoCloseable, Runnable {
         @Override public void close () throws Exception {}
         @Override public void run () {} }
   private static class C extends B {}

   private static <T extends AutoCloseable & Runnable> void printType( T... args ) {
      System.out.println( args.getClass().getComponentType().getSimpleName() );
   }

   public static void main( String[] args ) {
      printType( new A() );          // A[] created here
      printType( new B(), new B() ); // B[] created here
      printType( new B(), new C() ); // B[] which is the common base class
      printType( new A(), new B() ); // AutoCloseable[] - well...
      printType();                   // AutoCloseable[] - same as above
   }
}
Run Code Online (Sandbox Code Playgroud)
  • JSL 18.2规定了如何处理类型推断的约束,例如AutoCloseable & Channel简化为Channel.但规则无助于回答这个问题.

AutoCloseable[]当然,从调用中获取可能看起来很奇怪,因为我们无法使用Java代码执行此操作.但实际上实际类型并不重要.在语言层面,argsT[],T"虚拟类型" 在哪里是A和B(JSL 4.9).

编译器只需要确保它的用法满足所有约束,然后它就知道逻辑是合理的,并且不存在类型错误(这就是Java泛型的设计方式).当然编译器仍然需要创建一个真正的数组,并且为此目的它创建了一个"通用数组".因此警告" unchecked generic array creation"(JLS 15.12.4.2).

换句话说,只要你只能传递AutoCloseable & Runnable,并要求只Object,AutoCloseableRunnable的方法printType,实际的数组类型并不重要.实际上,printType无论传入何种类型的数组,其字节码都是相同的.

既然printType不关心vararg数组类型,getComponentType()不会也不应该重要.如果你想获得接口,请尝试getGenericInterfaces()返回一个数组.

  • 由于类型擦除(JSL 4.6),接口的顺序T确实会影响(JSL 13.1)编译的方法签名和字节码.AutoClosable将使用第一个接口,例如,在AutoClosable.close()调用时不会进行类型检查printType.
  • 但这与问题的方法调用的类型干扰无关,即为什么AutoClosable[]创建和传递.在擦除之前检查许多类型的安全装置,因此顺序不影响类型安全性.我认为这是JSL意味着的一部分"The order of types... is only significant in that the erasure ... is determined by the first type"(JSL 4.4).这意味着订单无关紧要.
  • 无论如何,这个擦除规则确实会引起极端情况,例如添加printType(AutoCloseable[])触发器编译错误,而添加printType( Runnable[])则不会.我认为这是一个意想不到的副作用,实际上超出了范围.
  • PS挖掘太深可能导致精神错乱,考虑到我认为我是Ovis aries,查看汇编的来源,并努力用英语而不是J̶S͡L̴回答.我的理智分数是b҉ȩyon̨d͝r̨̡͝e̛a̕l̵numb͟ers͡.回头.̠̝͕b̭̳͠͡ẹ̡̬̦̙f͓͉̼̻o̼͕͎̬̟̪r҉͏̛̣̼͙͍͍̠̫͙ȩ̵̮̟̫͚҉͏̛̣̼͙͍͍̠̫͙ȩ̵̮̟̫͚..t̷҉̛̫͔͉̥͎̬ò̢̪͉͎͜o̭͈̩̖̭̬..̮̘̯̗l̷̞͍͙̻̻͙̯̣͈̳͓͇a̸̢̢̰͓͓̪̳͉̯͉̼͝͝t̛̥̪̣̹̬͔̖͙̬̩̝̰͕̖̮̰̗͓̕͢ę̴̹̯̟͉͔͉̳̣͝͞.̬͖͖͇͈̤̼͖͘͢.͏̪̝̠̯̬͍̘̣̩͉̯̹̼͟͟͠.̨͠҉̬̘̹

  • 阅读你的更新,你做了更糟糕的IMO(除了令人不安的PS).OP的问题非常简单,为什么"最左边"的规则不受尊重 - 并要求对JLS进行覆盖.你的答案只是抛出不同的JLS部分而没有任何意义(至少对我而言).这个"最左边"的规则很普遍(嘿,直到读到这个我会说`T extends Runnable&AutoCloseable`将被删除`Runnable`),但显然不正确.我真的希望你的回答能够涵盖这个细节,但它不是恕我直言 (2认同)