【问题标题】:Why Arrays.sort(T[] a, Comparator<? super T> c) infers T as Object for a 2d array?为什么 Arrays.sort(T[] a, Comparator<? super T> c) 将 T 推断为二维数组的对象?
【发布时间】:2021-07-16 10:00:40
【问题描述】:

如果我想对二维数组进行排序。 (只需重新排序行,不要触摸每行内的数据)。

在以下 sn-p 中:所有 3 种情况都使用相同的 Arrays.sort(T[] a, Comparator&lt;? super T&gt; c) 方法签名。案例(a)工作正常。但是,只需在第二个参数中添加一个 if 条件,T 的推断就会改变。我不明白为什么。

        // array contains 3 tuples, sort it by the first element, then second element
        int[][] array1 = new int[3][2];
        array1[0] = new int[]{1,2};
        array1[1] = new int[]{2,3};
        array1[2] = new int[]{2,4};

        // Case (a): compiles good, tuple is inferred as int[]
        Arrays.sort(array1, Comparator.comparingInt(tuple -> tuple[0]));  // Arrays.sort(T[] a, Comparator<? super T> c) correctly infers that T refers to int[]

        // Case (b.1): compile error: incompatible types
        // tuple is now inferred as Object, why?
        Arrays.sort(array1,
                (a1, a2) -> a1[0] == a2[0] ?
                        Comparator.comparingInt(tuple -> tuple[1]) : Comparator.comparingInt(tuple -> tuple[0]));  

        // Case (b.2): compile error: incompatible types
        Arrays.sort(array1, Comparator.comparingInt(tuple -> tuple[0]).thenComparingInt(tuple -> tuple[1])); 
        
        // Case (c): if downcast tuple[0] to ((int[])tuple)[0], then (b) works fine. 

更新:

  1. 在 cmets 的启发下,我很快意识到案例(b.1)实际上是无效的。 (b.1) 中的 lambda 假设返回一个整数,而不是比较器。例如。 Arrays.sort(array1, (a1, a2) -&gt; a1[0] == a2[0] ? 0 : 1);
  2. 在所有其他情况下,我看到 Comparator.&lt;int[]&gt;comparingInt(...) 正确强制推理。

【问题讨论】:

  • 很可能是因为当您使用三元 condition ? a : b 时,ab 的结果必须在它们的类型中对齐。因此,两个Comparator.comparingInt(...) 调用都必须排成一行,并且该过程的复杂规则很可能会失败,并决定Object 是确保这一点的唯一方法。
  • 请注意,如果自动推断失败,您可以手动指定所需的类型。 Comparator.&lt;int[]&gt;comparingInt(...)。看起来很奇怪,但很有效。
  • 有趣的是,在没有thenComparingInt 的情况下,Arrays.sort(array1, Comparator.comparingInt(tuple -&gt; tuple[0])) 是如何工作的。
  • 无论如何,如果你想要一个字典顺序,你可以做Arrays.sort(array1, Arrays::compare);。不需要所有这些thenComparing 的东西。
  • Arrays::compare 的通用 Java 8 替代方案是 Comparator.comparing(IntBuffer::wrap)。但是当已知数组总是有两个元素时,我会保留Comparator.comparingInt((int[] a)-&gt;a[0]).thenComparingInt(a-&gt;a[1])

标签: java arrays java-8 comparator


【解决方案1】:

简答:编译器不够聪明,无法通过如此复杂的表达式进行推断。它需要一些帮助来推断类型:

Arrays.sort(array1, Comparator.<int[]>comparingInt(tuple -> tuple[0]).thenComparingInt(tuple -> tuple[1]));

相关JEP:http://openjdk.java.net/jeps/101

至于三元表达式的情况,我认为它需要进一步的适配,因为你需要在 lambda 中返回一个int,而不是一个Comparator

Arrays.sort(array1,
            (a1, a2) -> a1[0] == a2[0] ?
                    Comparator.<int[]>comparingInt(tuple -> tuple[1]).compare(a1, a2) :
                    Comparator.<int[]>comparingInt(tuple -> tuple[0]).compare(a1, a2));

【讨论】:

  • (可能是 OP 意识到了这一点,并想知道导致此问题的确切原因和规则(即长答案),而不是解决它的方法。)
  • 你也可以使用Arrays.sort(array1, Comparator.comparingInt((int[] tuple) -&gt; tuple[0]) .thenComparingInt(tuple -&gt; tuple[1]));
【解决方案2】:

在深入挖掘之后,这里有一个关于这种行为的直观解释:

public interface Comparator<T> {
...
default Comparator<T> thenComparingInt(ToIntFunction<? super T> keyExtractor)
public static <T> Comparator<T> comparingInt(ToIntFunction<? super T> keyExtractor)
...
}
  1. public static 方法需要调用者在调用该方法时定义T(如第二个方法中的第一个&lt;T&gt; 一样)。否则默认为Object

  2. 这与非静态方法不同,其中T 已在实例化期间定义。

所以查看Comparator.comparingInt(tuple -&gt; tuple[0]);,编译器不会知道T 是什么,因此默认为Object

为了正确地为编译器提供实际类型 T,我们可以在不同的地方显式定义 T。

// define T when calling the method
Comparator.<int[]>comparingInt(tuple -> tuple[0]); 

// infer by return type, this is samilar to placing the rhs to Arrays.sort(int[][] a, rhs)
Comparator<int[]> c = Comparator.comparingInt(tuple -> tuple[0]); 

// explicit target type for lambda expression
Comparator.comparingInt((ToIntFunction<int[]>) tuple -> tuple[0]);

// explicit type for lambda parameter
Comparator.comparingInt((int[] tuple) -> tuple[0]);

// This works but it's doesn't change the inference of T as comments suggests below.
Comparator.comparingInt(tuple -> ((int[])tuple)[0]); 

一旦它返回一个Comparator&lt;int[]&gt; 对象(因此将T 定义为int[]),实例方法thenComparingInt 就会在知道T 是什么时被调用。

【讨论】:

  • 在 lambda 表达式 within 中进行类型转换不是一个好主意。它不会改变编译器推断出的类型,因此它会产生(Object tuple) -&gt; ((int[])tuple)[0],进行实际的转换。然后,当要排序的数组类型错误时,编译器不会警告您。赋值的实际替代方案是:Comparator.&lt;int[]&gt;comparingInt(tuple -&gt; tuple[0])T 的显式类型)、Comparator.comparingInt((int[] tuple) -&gt; tuple[0])(lambda 参数的显式类型)或Comparator.comparingInt((ToIntFunction&lt;int[]&gt;)tuple -&gt; tuple[0])(lambda 表达式的显式目标类型)。
  • @Holger 我认为你是对的,它会产生一个Object -&gt; int lambda,并且在任何意义上都没有定义 T。将您的 cmets 改编为上述答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-12-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多