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

标签 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"

我很清楚没有类型 <T extends AutoCloseable & Cloneable>在运行时,因此编译器会做它可以做的最少错误的事情,并使用两个边界接口(interface)之一的类型创建一个数组,丢弃另一个。

不管怎样,切换接口(interface)的顺序,结果还是一样的。

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

// printType() prints "AutoCloseable"

这促使我进行了更多调查,看看当接口(interface)发生变化时会发生什么。 在我看来,编译器使用某种严格的顺序规则来决定哪个接口(interface)是最重要的,而接口(interface)在代码中出现的顺序没有任何作用。

<T extends AutoCloseable & Runnable>                             // "AutoCloseable"
<T extends Runnable & AutoCloseable>                             // "AutoCloseable"
<T extends AutoCloseable & Serializable>                         // "Serializable"
<T extends Serializable & AutoCloseable>                         // "Serializable"
<T extends SafeVarargs & Serializable>                           // "SafeVarargs"
<T extends Serializable & SafeVarargs>                           // "SafeVarargs"
<T extends Channel & SafeVarargs>                                // "Channel"
<T extends SafeVarargs & Channel>                                // "Channel"
<T extends AutoCloseable & Channel & Cloneable & SafeVarargs>    // "Channel"

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

我什至不确定 JLS 是否对此有任何说明,而且我通过谷歌搜索找到的信息均未涵盖此特定主题。

最佳答案

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

这些规则主要是表示“找到所有可能的输入类型并检查它们”的技术方式(例如 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
   }
}
  • JSL 18.2指示如何处理类型推断的约束,例如将 AutoCloseable & Channel 简化为仅 Channel。 但这些规则无助于回答这个问题。

当然,从调用中获取 AutoCloseable[] 可能看起来很奇怪,因为我们不能用 Java 代码来做到这一点。 但实际上,实际类型并不重要。 在语言级别,argsT[],其中 T 是“虚拟类型”,既是 A 又是 B (JSL 4.9 )。

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

也就是说,只要你只传入AutoCloseable&Runnable,并且只调用ObjectAutoCloseable printType 中的 Runnable 方法,实际的数组类型无关紧要。 其实printType的字节码都是一样的,不管传入的是什么数组。

由于 printType 不关心可变参数数组类型,因此 getComponentType() 不重要也不应该重要。 如果要获取接口(interface),请尝试 getGenericInterfaces()它返回一个数组。

  • 由于类型删除( JSL 4.6 ),T 的接口(interface)顺序确实会影响( JSL 13.1 )编译的方法签名和字节码。将使用第一个接口(interface) AutoClosable,例如在 printType 中调用 AutoClosable.close() 时不会进行类型检查。
  • 但这与问题的方法调用的类型干扰无关,即为什么创建和传递 AutoClosable[]。在删除之前检查了许多类型安全,因此顺序不会影响类型安全。我认为这是 JSL 的一部分含义,“类型的顺序......仅在删除......由第一种类型决定时才有意义”(JSL 4.4)。这意味着订单在其他方面无关紧要。
  • 不管怎样,这个删除规则确实会导致一些极端情况,例如添加 printType(AutoCloseable[]) 会触发编译错误,而添加 printType(Runnable[]) 则不会。我相信这是一个意想不到的副作用,并且真的超出了范围。
  • 附言挖得太深可能会导致insanity ,考虑到我认为我是Ovis aries , 查看源代码 into assembly ,并且努力用英语而不是 J̶́S͡L̴̀ 回答。我的理智分数是 b҉ȩyon̨d͝ r̨̡͝e̛a̕l̵ numb͟ers͡ .回头。 ̠̝͕b̭̳͠͡ͅẹ̡̬̦̙f͓͉̼̻o̼͕̱͎̬̟̪r҉͏̛̣̼͙͍͍̠̫͙ȩ̵̮̟̱̫͚..t̷҉̛̫͔͉̥͎̬ò̢̱̪͉̲͎͜o̭͈̩̖̭̬ ..̮̘̯̗l̷̞͍͙̻̻͙̯̣͈̳͓͇a̸̢̢̰͓͓̪̳͉̯͉̼͝͝t̛̥̪̣̹̬͔̖͙̬̩̝̰͕̖̮̰̗͓̕͢ę̴̹̯̟͉̲͔͉̳̲̣͝͞..

关于java - Java 编译器如何为具有多个边界的参数化类型选择运行时类型?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50177313/

相关文章:

java - Thymeleaf 中的 Spring 安全表达式

java - 如何更改 Java 中现有 JFrame 窗口的 setAlwaysOnTop() 属性?

c++ - 如何使用一张 map 存储不同的功能

java - 测试 final 字段的初始化安全性

java - getVolatile 和 getAcquire 有什么区别?

java - 什么是 Java 中的捕获转换,谁能给我举个例子?

java - 轴描述中的下标

java - 从 OS X 上的 Java 应用程序运行外部脚本

maven - 当将null作为varargs参数发送时,Gradle和Maven的行为不同

java - Vararg 方法覆盖/重载混淆