在方法参数

时间:2017-08-06 18:47:46

标签: java generics type-inference

请考虑以下代码示例:

@SafeVarargs
public static <U> Object[] sortedCopy(Comparator<? super U> comparator, U... values) {
    U[] copy = Arrays.copyOf(values, values.length);
    Arrays.sort(copy, comparator);
    return copy; //copy is implicitly cast to Object[] -> no heap pollution
}

public static <U> Object[] sortedCopy(U... values) {
    return sortedCopy(Comparator.naturalOrder(), values); //why does this compile???
}

我原本希望编译器拒绝sortedCopy(U...)中的行,其理由如下:

Comparator.naturalOrder()的返回类型为Comparator<T>,其中T是此方法的泛型类型参数,必须满足约束T extends Comparable<? super T>。由于该方法不接受任何参数,并且其在上面的代码中的调用没有明确指定T的类型(如Comparator.<U>naturalOrder(),因为U不会扩展Comparable {1}}),T必须以另一种方式推断。我可以看到推断T的唯一方法是使用sortedCopy(Comparator<? super U>, U...)的方法签名。编译器知道values的类型,因此可以推断U,反过来也可以推断T的边界,即有界通配符? super U。但是,编译器应该意识到任何? super U都不能满足T Comparator.naturalOrder()所指定的T extends Comparable<? super T>的要求,因为U本身不会扩展Comparable<? super U>,因此U的任何超类型都不能。

让我感到困惑的是,当我将签名从sortedCopy(U...)更改为sortedCopy(U[])时,编译器生成错误。我想这与以下事实有关:在第二种情况下,U在运行时作为数组的类型存在,而在第一种情况下,它不是。但我仍然不明白为什么这会使有问题的方法调用有效,因为:

  1. 据我了解,如果作为vararg参数传递给方法的值是泛型且因此不可重新生成的类型,泛型类型的varargs参数将转换为Object[],如果我理解的话正确的是,上述代码就是这种情况,因为来自U的{​​{1}}是不可恢复的。但即便如此,为什么编译器没有意识到sortedCopy(U...)没有扩展Object
  2. 前面的参数讨论了运行时类型。但是,我们仍然在进行预编译,因此关于运行时类型的推测在这种情况下甚至不应该相关,因为虽然Comparable<? super Object>可能在运行时不再存在,但编译器仍然知道它并且应该能够检查是否满足等式约束,无论方法参数是数组还是vararg。
  3. 那么为什么上面的代码示例中的行仍然可以编译?

    除此之外,如果方法U的{​​{1}}注释不合适,我也会感激您的反馈。我相信它是,但我对它没有信心。

2 个答案:

答案 0 :(得分:3)

Varargs可能有点鬼鬼祟祟。我在IDE中看到的是它将sortedCopy(U... values)视为递归方法,这意味着它没有选择带有Comparator作为其第一个参数的重载参数。

如果将此值从varargs更改为数组参数,并传入int[],则会出现您期望的编译失败。

Error:(12, 16) no suitable method found for sortedCopy(java.util.Comparator<T>,U[])
    method Foo.<U>sortedCopy(java.util.Comparator<? super U>,U[]) is not applicable
      (inferred type does not conform to upper bound(s)
        inferred: U
        upper bound(s): java.lang.Comparable<? super U>,U,java.lang.Object)
    method Foo.<U>sortedCopy(U[]) is not applicable
      (cannot infer type-variable(s) U
        (actual and formal argument lists differ in length))

Error:(18, 47) no suitable method found for sortedCopy(java.util.Comparator<T>,int[])
    method Foo.<U>sortedCopy(java.util.Comparator<? super U>,U[]) is not applicable
      (inference variable U has incompatible bounds
        equality constraints: int
        upper bounds: java.lang.Object)
    method Foo.<U>sortedCopy(U[]) is not applicable
      (cannot infer type-variable(s) U
        (actual and formal argument lists differ in length))

如果您传入Integer[],或者仅传入vararg表单,则只会收到第一个错误。

词法分析器可以使用picking the most specific method周围的规则。具体来说,你被这条规则所灼烧:

  

如果S <:T

,则类型S对于任何表达式比类型T更具特异性

更详细:

  

类型T的子类型都是U类型,因此T是U的超类型和null类型。

由此我们推断,因为泛型没有良好的约束,它接受Comparable作为你的vararg的一部分。这就是为什么我的IDE选择它作为一个递归方法,以及为什么当我运行它时,我得到一个StackOverflowError

如果您将通用版本正确绑定到<U extends Comparable<U>>以确保它不会拾取您无法实际排序的任何内容,则此问题实际上会消失...

public static <U extends Comparable<U>> Object[] sortedCopy(Comparator<? super U> comparator, U... values) {
    U[] copy = Arrays.copyOf(values, values.length);
    Arrays.sort(copy, comparator);
    return copy;
}

public static <U extends Comparable<U>> Object[] sortedCopy(U... values) {
    return sortedCopy(Comparator.naturalOrder(), values);
}

...警告你可能现在有堆污染,而且引入固定方法会更简单,更简洁。

答案 1 :(得分:1)

在我看来,这个函数正在调用你自己的场景中调用自己。

在第二种方法中,你可以用对象替换U,一切都适合那里,因为它是一个varargs。