簡體   English   中英

具有通配符的Java泛型可在Eclipse中編譯,但不能在javac中編譯

[英]Java generics with wildcard compile in Eclipse, but not in javac

作為Java泛型在Eclipse中而不是在javac中進行編譯的后續措施,我發布了另一個代碼段,該代碼段在Eclipse中可以編譯並正常運行,但是會在javac中引發編譯錯誤。 (這可以防止使用Maven構建摘錄片段的項目。)

獨立的代碼段:

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;


public class Main {
  public static void main(String[] args) {
    Set<Foo<?>> setOfFoos = new HashSet<Foo<?>>();
    List<Foo<?>> sortedListOfFoos = asSortedList(setOfFoos);
  }


  public static <T extends Comparable<T>> List<T> asSortedList(Collection<T> c) {
    List<T> list = new ArrayList<T>(c);
    java.util.Collections.sort(list);
    return list;
  }


  public static class Foo<T> implements Comparable<Foo<T>> {
    @Override
    public int compareTo(Foo<T> o) {
      return 0;
    }
  }
}

javac中的編譯返回:

Main.java:11: <T>asSortedList(java.util.Collection<T>) in Main cannot be applied to (java.util.Set<Main.Foo<?>>)
    List<Foo<?>> sortedListOfFoos = asSortedList(setOfFoos);
                                    ^

Foo<String>替換Foo<?> ,以上代碼段將在javac中編譯,這意味着問題與使用的通配符有關。 由於Eclipse編譯器應該更寬容,因此代碼段是否可能不是有效的Java?

(我使用javac 1.6.0_37和Eclipse Indigo,編譯器符合級別1.6)

EDIT1:包括另一個示例,該示例已在EDIT2中刪除。)

EDIT2: 無可辯駁地暗示 ,比較Foo<A>Foo<B>在概念上可能是錯誤的,並且受seh答案的啟發,可以將asSortedFooList的工作編寫如下:

public static <T extends Foo<?>> List<T> asSortedFooList(Collection<T> c) {
    List<T> list = new ArrayList<T>(c);
    java.util.Collections.sort(list);
    return list;
}

(在上面的方法定義中,用Foo<?>替換了Comparable<T> 。)因此,javac和imho在概念上比較任何Foo<A>Foo<B>似乎都是安全的。 但是,仍然無法編寫通用方法asSortedList ,如果它的類型參數使用通配符進行參數化,則該方法將返回通用集合的排序列表表示形式。 我試圖用S extends Comparable<S>代替Foo<?>來“欺騙” javac,並在asSortedFooList S extends Comparable<S> ,但這是行不通的。

EDIT3:后來Rafaelle指出,由於沒有必要實現Comparable<Foo<T>> ,並且實現Comparable<Foo<?>>提供了相同的功能,因此設計上存在缺陷,可以通過精細設計解決初始問題。

(最初的原因和好處是, Foo<T> 在某些方面可能並不關心其具體類型,但仍將其實例化為具體類型T實例用於其他目的。該實例不必用於確定其他Foo之間的順序,因為它可以在API的其他部分中使用。

具體示例:假設每個Foo都使用T的不同類型參數實例化。 Foo<T>每個實例都有一個int類型的遞增ID,該ID在compareTo方法的實現中使用。 現在,我們可以對這些類型不同的Foo排序,而不必關心具體的類型T (用Foo<?> ),並且仍然可以訪問具體的類型T的實例,以便以后進行處理。)

在這種情況下,javac是正確的。 從概念上講,您的代碼無法運行,因為該集合可能包含Foo<A>Foo<B> ,它們無法相互比較。

您可能希望將集合Set<Foo<X>>某個類型變量X的Set<Foo<X>> 不幸的是我們不能在方法體內引入類型變量。 僅在方法簽名中

<X> void test(){
    Set<Foo<X>> setOfFoos = new HashSet<Foo<X>>();
    List<Foo<X>> sortedListOfFoos = asSortedList(setOfFoos);
}

您可以通過類似的方法使其工作

<T extends Comparable<? super T>> List<T> asSortedList(Collection<T> c) 


class Foo<T> implements Comparable<Foo<?>> 

對我來說,這是另一個javac錯誤。 當您嘗試將Collection<Foo<?>>發送到帶有簽名的方法時:

public static <T extends Comparable<T>> List<T> asSortedList(Collection<T> c)

編譯器注意到形式參數 T有一個上限,因此請檢查約束條件是否被調用方接受。 type參數是參數化類型Foo<T>的(通配符)實例化,因此如果Foo<?> Comparable<Foo<?>> ,則測試將通過。 根據通用定義:

class Foo<T> implements Comparable<Foo<T>>

我會說這是真的,因此Eclipse再一次是正確的,而javac有一個錯誤。 Angelika Langer的條目從來沒有足夠的關聯。 另請參閱相關的JLS

您詢問它是否是類型安全的。 我的回答是,它是類型安全的 ,它表明您的設計存在缺陷。 考慮您對Comparable<T>接口的虛擬實現,在其中我添加了另外兩個字段:

public static class Foo<T> implements Comparable<Foo<T>> {

  private T pState;
  private String state;

  @Override
  public int compareTo(Foo<T> other) {
    return 0;
  }
}

您始終返回0 ,因此不會發現問題。 但是,當您嘗試使其有用時,您有兩種選擇:

  1. 比較字符串字段
  2. T成員進行比較

String字段始終是一個String ,因此您不會真正受益於類型變量T 另一方面, T沒有其他可用的類型信息,因此,在compareTo()您只能處理一個普通對象,並且type參數同樣無用。 通過實現Comparable<Foo<?>>您可以實現相同的確切功能。

我不知道這是否是一個問題,但這是一個(不是很好)答案:如果您犧牲了某些類型安全性,則可以編寫

@SuppressWarnings({ "unchecked", "rawtypes" })
public static <T extends Comparable> List<T> asSortedList(Collection<T> c) {
    List<T> list = new ArrayList<T>(c);
    java.util.Collections.sort(list);
    return list;
}

它在eclipse和javac中均可使用。 我知道的唯一風險是,如果有人創建class Foo extends Comparable<Bazz>您不會在編譯時檢測到它。 但是,如果有人創建Foo extends Comparable<Bazz> ,則殺死他/她。

我發現可以用javac編譯的解決方案,盡管我不滿意無法確切解釋其工作原理,對此我感到不滿意。 它需要引入一個中介功能:

public final class Main {
  public static class Foo<T> implements Comparable<Foo<T>> {
    @Override
    public int compareTo(Foo<T> o) {
      return 0;
    }
  }


  public static <T extends Comparable<? super T>>
  List<T> asSortedList(Collection<T> c) {
    final List<T> list = new ArrayList<T>(c);
    java.util.Collections.sort(list);
    return list;
  }


  private static <T extends Foo<?>> List<T> asSortedFooList(Collection<T> c) {
    return asSortedList(c);
  }


  public static void main(String[] args) {
    final Set<Foo<?>> setOfFoos = new HashSet<Foo<?>>();
    final List<Foo<?>> listOfFoos = asSortedFooList(setOfFoos);
  }
}

認為這是通過逐步執行通配符解析來實現的; asSortedFooList()捕獲一種類型的已知為Foo ,不論Foo的類型參數。 將該類型參數綁定到asSortedFooList() ,我們可以調用原始的asSortedList() (需要做一個修改-請注意Comparable的type參數的下限),需要將Foo綁定為從Comparable繼承的類型。

同樣,這是一個微不足道的,偶然的解釋。 我在這里回答的主要目的只是提供另一種到達目的地的方式。

如果您可以將通配符用法替換為確切的類型(可能是超類型),則代碼將起作用。 更換

List<Foo<?>> sortedListOfFoos = asSortedList(setOfFoos);

List<Foo<String>> sortedListOfFoos = Main.<Foo<String>>asSortedList(setOfFoos);

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM