java - Var-Arg 方法同时将泛型类型推断为 Object 和 Primitive[]

标签 java generics variadic-functions

泛型和 Var-Arg 的意外行为

   public static <T> void method(T singleVal, T ... vals) {
      System.out.println(singleVal);
      for(T val : vals) {
        System.out.println(val);
      }
   }

调用方式:

   method(0, new int[]{1,2,3});
   //method('a', new char[] {'b','c','d'});  //same behavior

打印:

   0
   [I@7699a589   //<--- an array at element 0

仅当向其传递原语[]时才会发生这种情况。

使用 int[] 时,T 变为两者 Integerint [] 同时?

debugEditorVals

当向其传递 Integer[] 而不是 int [] 时,它的行为如预期:

method(0, new Integer[]{1,2,3});
//method('a', new Character[] {'b','c','d'});  //same behavior

0
1
2
3

最佳答案

我相信答案来自JLS §15.12.4.2. Evaluate Arguments 。我们已经简单地完成了重载解析(本示例中只有一个重载),因此我们知道目标方法。这只是评估参数并做出决定的问题。

The process of evaluating the argument list differs, depending on whether the method being invoked is a fixed arity method or a variable arity method (§8.4.1).

If the method being invoked is a variable arity method m, it necessarily has n > 0 formal parameters. The final formal parameter of m necessarily has type T[] for some T, and m is necessarily being invoked with k ≥ 0 actual argument expressions.

If m is being invoked with k ≠ n actual argument expressions, or, if m is being invoked with k = n actual argument expressions and the type of the k'th argument expression is not assignment compatible with T[], then the argument list (e1, ..., en-1, en, ..., ek) is evaluated as if it were written as (e1, ..., en-1, new |T[]| { en, ..., ek }), where |T[]| denotes the erasure (§4.6) of T[].

现在让我们看看这个单词 soup 如何应用于您的示例。有问题的方法是

public static <T> void method(T singleVal, T ... vals)

参数列表是

method(0, new int[]{1,2,3});

方法m有两个形式参数,类型为 TT[] 。由于最后一个参数是数组类型,并且用变量数指示符 ... 进行标记。 ,这是一种变数方法。我们用两个参数调用它,所以 k = n在上面的引用中。现在,第一个参数的类型为 int ,它作为通用参数无效,因此自动装箱将其设置为 Integer 。这意味着TInteger 。第二个参数是int[] 。自 k = n ,如果(且仅当)我们的第二个参数Integer[]兼容,我们将把它包装在一个数组中。 。第二个参数是 int[] ,不是Integer[] 。自动装箱不会转换整个数组,因此我们假设需要对其进行包装。

现在,您可以合理地询问为什么会发生此函数调用。毕竟我们已经决定了TInteger ,现在我们说 Tint[] ?好吧,我撒谎了。或者说,我过于简单化了。我们没有决定 TInteger 。我们决定T兼容 Integer 。基本上,我们决定 T某种父类(super class)型 Integer 。在没有任何附加信息的情况下,Java 会得出结论:T应该是Integer ,但如果需要的话,它也很乐意在类型推断期间扩大范围。

就您而言,我们已经决定 int[]Integer[] 不兼容(或者准确地说,对于 U[] 的任何父类(super class)型 U 使用 Integer ),因为 int是一个原语,我们不能在数组内部自动装箱。所以我们已经决定这是一个将包装在数组中的变量参数调用。也就是说,调用被转换为

method(new Integer(0), new int[][]{new int[]{1, 2, 3}});

这只是找到一个T的问题与方法签名一起使用

// Note: No T... now, we've already made that decision, so it's T[] now.
public static <T> void method(T singleVal, T[] vals)

也就是说,我们需要一个类型T这是 Integer 的共同父类(super class)型和int[]Integer有几个父类(super class)型:Number , Comparable<Integer> , Serializable等等。但只有一种类型是两者的父类(super class)型Integer和数组类型:Object 。也就是说,您已调用 T = Object 电话.

我认为,这里的教训是,泛型和可变参数不能很好地发挥作用,当自动装箱混合在一起时,它们肯定不能很好地发挥作用。就我个人而言,我只使用具有具体类型的变量参数(因此 int...String... 是公平的游戏,但我永远不会写 T... ),只是为了避免这样的困惑。

关于java - Var-Arg 方法同时将泛型类型推断为 Object 和 Primitive[],我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/73428585/

相关文章:

javax.persistence.PersistenceException : No Persistence provider for EntityManager named customerManager

generics - Kotlin 泛型函数

django - 如何遍历 Django 模板中的反向泛型关系?

c++ - 调用可变参数宏的可变参数函数

java - 单击按钮时应用程序强制关闭?

JavaFX 绑定(bind)到多个属性

javafx:如何创建一个 cellFactory 来考虑一个对象的两个属性?

ios - 泛型的偏函数应用

c++ - 如何简化这些可变函数?

c - Rust 字符串和 C 可变参数函数