Java Wildcard Capture因未知原因失败

时间:2017-05-27 00:17:56

标签: java generics wildcard static-methods

编写静态泛型方法来执行插入排序。我遇到了以下我无法解决的外卡捕获问题。有一个简单的解决方案,但我仍然想知道为什么我的原始尝试不编译。我为quicksort做了类似的事情,它可以正确编译。

注意:我知道通过将声明从List<? extends T> col更改为List<T> col,代码将编译并正常运行。但是因为我在下面的“移动”方法中捕获通配符,所以如果我离开它也应该编译 “?延伸T”原样。有没有人知道为什么它拒绝编译?我在Eclipse(NEON)中使用jdk1.8.0_60。

以下是无法编译的相关代码:

public static <T extends Comparable<? super T>> 
    void insertionSort(List<? extends T> col) {

    for ( int i = 1 ; i < col.size(); i++ ){
        int j = i - 1 ;
        T key = col.get(i);
        while ( j > -1  && col.get(j).compareTo(key)  > 0 ) {
        T ele = col.get(j);
        InsertionSort.move(j+1,ele,col); // **DOES NOT COMPILE** 
        j = j - 1;
        }
        InsertionSort.move(j+1, key, col); // **DOES NOT COMPILE** 
    }
}

private static <T> void move(int jplus1, T key, List<T> col) {
    col.set(jplus1, key);   
}

我正在使用quickSort做类似的事情,无论信不信,编译正确。 以下是实际正确编译的快速排序:请注意 swap 方法。

public static <T extends Comparable<? super T>> 
void quickSort(List<? extends T> col) {
    Objects.requireNonNull(col);
    _quickSort(col, 0, col.size() - 1);
}

private static <T extends Comparable<? super T>> 
    void _quickSort(List<? extends T> col, int start, int end) {
    int partitionPoint = 0;
    if (start < end) {
        partitionPoint = partition(col, start, end);
        _quickSort(col, start, partitionPoint - 1);
        _quickSort(col, partitionPoint + 1, end);
    }
}

private static <T extends Comparable<? super T>> 
int partition(List<? extends T> col, int start, int end) {
    T pivot = col.get(end);
    int partitionIndex = start;
    for (int j = start; j <= (end - 1); j++) {
        int cmp = col.get(j).compareTo(pivot);
        if (cmp < 1) {
            swap(j, partitionIndex, col); // COMPILES CORRECTLY
            partitionIndex++;
        }
    }
    swap(partitionIndex, end, col);
    return partitionIndex;
}

private static <T> void swap(int j, int partitionIndex, List<T> col) {
    T temp = col.get(j);
    col.set(j, col.get(partitionIndex));
    col.set(partitionIndex, temp);
}

4 个答案:

答案 0 :(得分:1)

简短版本:问题是T key方法的move参数。

更深入:所以你有一个? extends T通配符和一个包含该类型的List。让我们调用实际类型为U的任何类型。由于您直接传递列表,因为您收到的是原始类型,因此您调用的<U>版本为move。这需要您传递List<U> - 工作正常 - 和U key - 但您为密钥传递的变量是T类型。 U被绑定为T的子类,因此这是一个不安全的演员。

您也可以拨打<T>move。在这种情况下,T key会没问题,但现在您正试图将List<U>作为List<T>传递而 是一个不安全的演员。无论哪种方式,您都有不安全的强制转换,因此编译器会报告错误。

如果消除干预变量并调用move(j+1, col.get(i), col),我相信会编译,因为编译器可以告诉col.get(i)的结果是U类型,满足类型签名move<U>

为了更明确地说明为什么这是一个问题,让我们来看一个例子。我假装Number实施Comparable<Number>因为它使事情更容易理解。你打电话给你的排序如下:

ArrayList<Integer> list = new ArrayList<>();
list.add(2);
list.add(1);
insertionSort<Number>(list);

现在,您的代码将进入第一个move调用。在那一行,您有:

  • 第一个参数:j+1,类型为int
  • 第二个参数:ele,类型为Number
  • 第三个参数:col,类型为List<? extends Number>

在这种情况下,? extends Number恰好是Integer

现在,鉴于这些论点,被调用的move的泛型类型是什么?有两种明显的可能性,move<Integer>move<Number>

move<Integer>:这需要第二个T key类型的参数(Integer)。您为其提供了Number类型的变量。这不合适,所以这个选项不起作用。

move<Number>:第二个参数工作正常,但现在看第三个。第三个参数需要类型List<Number>。您传递的内容是List<Integer>。如果编译器允许您这样做,则意味着允许move方法将3.14159添加到列表中,这会破坏列表的仅包含的约束整数。因此,这不适合,所以这个选项不起作用。

没有任何其他相关选项需要考虑。所以,编译器到达那一行,试图弄清楚它是否正在调用move<Integer>move<Number>move<String>或其他任何东西,并发现没有任何作用。 move没有任何版本符合您尝试立即传递给它的所有三个参数,因此会抛出错误。

答案 1 :(得分:1)

此编译错误是由Producer Extends,Consumer Super Principle:PECS What is PECS (Producer Extends Consumer Super)?

引起的

最重要的是,您的代码既是生产者又是消费者(消费者是move方法)因此您无法使用? extends T,因为您无法执行put(或{{ 1}}在这种情况下)使用set

修改

为什么你的快速排序extends方法会起作用?

注意方法签名差异

快速排序swap

VS

insertionSort <T> void swap(int j, int partitionIndex, List<T> col)

快速排序的区别在于您将索引交换,但在不编译的insertionSort中,您将其中一个值作为类型<T> void move(int jplus1, T key, List<T> col) {提供。
只需使用索引,您就必须从列表中获取值,因此类型T是一种全新的推断类型。在您的insertedSort中,您提供了一个已经获取的T值,这是一个错误的捕获类型,与新的推断类型相比,因为您正在获取和放置,所以它不能包含T

如果你改变你的代码只使用偏移它会编译,但是你必须修改你的方法以不同的方式插入,因为你拥有它的方式如果你改变它就会失去先前插入列表中的值。

答案 2 :(得分:0)

swap和move之间的区别在于swap不接受类型为T的泛型键。这就是编译器所抱怨的内容,如前面的答案中所述。在您的方法中使用通配符根本没有意义。为什么你需要通配符?你不能这样做:

public static <T extends Comparable<T>> void insertionSort(List<T> col) {
    for (int i = 1; i < col.size(); i++) {
        int j = i - 1;
        T key = col.get(i);
        while (j > -1 && col.get(j).compareTo(key) > 0) {
            T ele = col.get(j);
            move(j + 1, ele, col); // **DOES NOT COMPILE**
            j = j - 1;
        }
        move(j + 1, key, col); // **DOES NOT COMPILE**
    }
}

private static <T> void move(int jplus1, T key, List<T> col) {
    col.set(jplus1, key);
}

答案 3 :(得分:0)

我找到了解决问题的方法:它需要创建一个额外的方法来正确捕获通配符:

public static <T extends Comparable<? super T>> void insertionSort(List<? extends T> col){
    _insertionSort(col,1,col.size());
}
public static <T extends Comparable<? super T>> void _insertionSort(List<T> col,int start,int end){