什麼是原始類型,爲何咱們不該該使用它呢?

問題:

  • Java中的原始類型是什麼?爲何我常常聽到不該該在新代碼中使用它們的信息?
  • 若是咱們不能使用原始類型,那有什麼選擇呢?有什麼更好的選擇?

#1樓

原始類型是沒有任何類型參數的泛型類或接口的名稱。 例如,給定通用Box類: html

public class Box<T> {
    public void set(T t) { /* ... */ }
    // ...
}

要建立Box<T>的參數化類型,請爲形式類型參數T提供一個實際的類型參數: java

Box<Integer> intBox = new Box<>();

若是省略實際類型參數,則建立Box<T>的原始類型: 程序員

Box rawBox = new Box();

所以, Box是通用類型Box<T>的原始類型。 可是,非泛型類或接口類型不是原始類型。 編程

原始類型顯示在舊版代碼中,由於在JDK 5.0以前,許多API類(例如Collections類)不是通用的。 使用原始類型時,您實際上會得到泛型行爲Box爲您提供Object 。 爲了向後兼容,容許將參數化類型分配給其原始類型: 數組

Box<String> stringBox = new Box<>();
Box rawBox = stringBox;               // OK

可是,若是將原始類型分配給參數化類型,則會收到警告: 安全

Box rawBox = new Box();           // rawBox is a raw type of Box<T>
Box<Integer> intBox = rawBox;     // warning: unchecked conversion

若是您使用原始類型來調用在相應的泛型類型中定義的泛型方法,也會收到警告: 數據結構

Box<String> stringBox = new Box<>();
Box rawBox = stringBox;
rawBox.set(8);  // warning: unchecked invocation to set(T)

該警告代表原始類型會繞過通用類型檢查,從而將不安全代碼的捕獲推遲到運行時。 所以,應避免使用原始類型。 oracle

Type Erasure部分提供了有關Java編譯器如何使用原始類型的更多信息。 app

未檢查的錯誤消息

如前所述,將舊代碼與通用代碼混合時,您可能會遇到相似於如下內容的警告消息: 編程語言

注意:Example.java使用未經檢查或不安全的操做。

注意:使用-Xlint:unchecked從新編譯以獲取詳細信息。

當使用對原始類型進行操做的較舊API時,可能會發生這種狀況,如如下示例所示:

public class WarningDemo {
    public static void main(String[] args){
        Box<Integer> bi;
        bi = createBox();
    }

    static Box createBox(){
        return new Box();
    }
}

術語「未檢查」是指編譯器沒有足夠的類型信息來執行確保類型安全所需的全部類型檢查。 儘管編譯器會給出提示,可是默認狀況下「 unchecked」警告是禁用的。 要查看全部「未選中」的警告,請使用-Xlint:unchecked從新編譯。

使用-Xlint:unchecked從新編譯前面的示例將顯示如下附加信息:

WarningDemo.java:4: warning: [unchecked] unchecked conversion
found   : Box
required: Box<java.lang.Integer>
        bi = createBox();
                      ^
1 warning

要徹底禁用未檢查的警告,請使用-Xlint:-unchecked標誌。 @SuppressWarnings("unchecked")註釋禁止未檢查的警告。 若是您不熟悉@SuppressWarnings語法,請參閱註釋。

原始資料: Java教程


#2樓

Java中的「原始」類型是非泛型的類,它處理「原始」對象,而不是類型安全的泛型類型參數。

例如,在Java泛型可用以前,您將使用以下收集類:

LinkedList list = new LinkedList();
list.add(new MyObject());
MyObject myObject = (MyObject)list.get(0);

當您將對象添加到列表時,它並不關心它是什麼類型的對象,而且當您從列表中獲取對象時,必須將其顯式轉換爲指望的類型。

使用泛型,您能夠刪除「未知」因素,由於必須明確指定能夠在列表中找到的對象類型:

LinkedList<MyObject> list = new LinkedList<MyObject>();
list.add(new MyObject());
MyObject myObject = list.get(0);

請注意,對於泛型,您沒必要強制轉換來自get調用的對象,該集合是預約義的,僅適用於MyObject。 這是仿製藥的主要驅動因素。 它將運行時錯誤的來源更改成能夠在編譯時檢查的錯誤。


#3樓

什麼是原始類型?爲何我常常聽到它們不該該在新代碼中使用?

「原始類型」是在沒有爲其類型化的參數指定類型實參的狀況下使用泛型類,例如,使用List而不是List<String> 。 當將泛型引入Java時,幾個類已更新爲使用泛型。 將這些類用做「原始類型」(不指定類型實參)容許遺留代碼繼續編譯。

「原始類型」用於向後兼容。 不建議在新代碼中使用它們,由於將泛型類與類型參數一塊兒使用可實現更強的鍵入,這反過來又能夠提升代碼的可理解性並致使更早地發現潛在的問題。

若是咱們不能使用原始類型,那有什麼選擇呢?有什麼更好的選擇?

首選的替代方法是按預期使用通用類-帶有合適的類型參數(例如List<String> )。 這使程序員能夠更具體地指定類型,將有關變量或數據結構的預期用途的更多含義傳達給將來的維護者,並容許編譯器強制實施更好的類型安全性。 這些優勢一塊兒能夠提升代碼質量,並有助於防止某些編碼錯誤的引入。

例如,對於程序員但願確保名爲「名稱」的List變量僅包含字符串的方法:

List<String> names = new ArrayList<String>();
names.add("John");          // OK
names.add(new Integer(1));  // compile error

#4樓

原始類型是在使用泛型類型時缺乏類型參數

不該使用Raw-type,由於它可能會致使運行時錯誤,例如將double插入到應該爲intSet中。

Set set = new HashSet();
set.add(3.45); //ok

Set檢索內容時,您不知道會發生什麼。 假設您指望它所有是int ,而且將其強制轉換爲Integerdouble精度3.45出現時在運行時發生異常。

類型參數添加到Set ,您將當即得到編譯錯誤。 這種先發制人的錯誤使您能夠在運行時發生故障以前解決問題(從而節省了時間和精力)。

Set<Integer> set = new HashSet<Integer>();
set.add(3.45); //NOT ok.

#5樓

什麼是原始類型?

Java語言規範將原始類型定義以下:

JLS 4.8原始類型

原始類型定義爲如下之一:

  • 經過採用通用類型聲明的名稱而沒有隨附的類型參數列表造成的引用類型。

  • 數組類型,其元素類型爲原始類型。

  • 原始類型R的非static成員類型,該成員類型不繼承自R的超類或超接口。

這是一個例子說明:

public class MyType<E> {
    class Inner { }
    static class Nested { }

    public static void main(String[] args) {
        MyType mt;          // warning: MyType is a raw type
        MyType.Inner inn;   // warning: MyType.Inner is a raw type

        MyType.Nested nest; // no warning: not parameterized type
        MyType<Object> mt1; // no warning: type parameter given
        MyType<?> mt2;      // no warning: type parameter given (wildcard OK!)
    }
}

在這裏, MyType<E>參數化類型JLS 4.5 )。 一般將這種類型簡稱爲MyType ,但從技術上來講,名稱爲MyType<E>

mt在上述定義的第一個要點以前具備原始類型(並生成編譯警告); inn在第三個要點以前也具備原始類型。

MyType.Nested不是參數化類型,即便它是參數化類型MyType<E>的成員類型,由於它是static

mt1mt2都使用實際的類型參數聲明,所以它們不是原始類型。


原始類型有何特別之處?

本質上,原始類型的行爲與引入泛型以前的行爲相同。 也就是說,如下在編譯時徹底合法。

List names = new ArrayList(); // warning: raw type!
names.add("John");
names.add("Mary");
names.add(Boolean.FALSE); // not a compilation error!

上面的代碼運行正常,但假設您還具備如下內容:

for (Object o : names) {
    String name = (String) o;
    System.out.println(name);
} // throws ClassCastException!
  //    java.lang.Boolean cannot be cast to java.lang.String

如今咱們在運行時遇到麻煩,由於names包含的內容不是instanceof Stringinstanceof String

據推測,若是你想要names只包含String ,你也許能夠仍然使用原始型和手動檢查每 add本身,而後手動轉換String每一個項目的names更好的是 ,儘管不要使用原始類型,而讓編譯器利用Java泛型的強大功能爲您完成全部工做

List<String> names = new ArrayList<String>();
names.add("John");
names.add("Mary");
names.add(Boolean.FALSE); // compilation error!

固然,若是names ,讓一個Boolean ,那麼你能夠把它聲明爲List<Object> names ,以及上面的代碼將編譯。

也能夠看看


原始類型與使用<Object>做爲類型參數有何不一樣?

如下是來自有效Java 2nd Edition,項目23的引用:不要在新代碼中使用原始類型

原始類型List和參數化類型List<Object>之間有什麼區別? 鬆散地說,前者選擇了泛型類型檢查,然後者則明確告訴編譯器它可以保存任何類型的對象。 雖然能夠經過一個List<String>到類型的參數List ,則不能將它傳遞給類型的參數List<Object> 。 有泛型的子類型化規則,而且List<String>是原始類型List的子類型,但不是參數化類型List<Object>類型。 所以, 若是使用像List這樣的原始類型則會失去類型安全性,可是若是使用像List<Object>這樣的參數化類型,則不會失去類型安全性

爲了說明這一點,請考慮如下方法,該方法採用List<Object>並附加一個new Object()

void appendNewObject(List<Object> list) {
   list.add(new Object());
}

Java中的泛型是不變的。 List<String>不是List<Object> ,所以如下內容將生成編譯器警告:

List<String> names = new ArrayList<String>();
appendNewObject(names); // compilation error!

若是已聲明appendNewObject以原始類型List做爲參數,則它將被編譯,所以您將失去從泛型得到的類型安全性。

也能夠看看


原始類型與使用<?>做爲類型參數有何不一樣?

List<Object>List<String>等都是List<?> ,所以極可能會說它們只是List而已。 可是,有一個主要區別:因爲List<E>僅定義add(E) ,所以您不能僅將任意對象添加到List<?> 。 另外一方面,因爲原始類型List沒有類型安全性,所以您幾乎能夠add任何內容addList

請考慮如下片斷的如下變體:

static void appendNewObject(List<?> list) {
    list.add(new Object()); // compilation error!
}
//...

List<String> names = new ArrayList<String>();
appendNewObject(names); // this part is fine!

編譯器作了出色的工做,能夠保護您避免違反List<?>的類型不變性! 若是您已將參數聲明爲原始類型List list ,則代碼將編譯,而且違反了List<String> names的類型不變式。


原始類型是該類型的擦除

返回JLS 4.8:

這是可能做爲一種類型的使用參數化的類型或數組類型,其元素類型是參數化類型的擦除的擦除這種類型稱爲原始類型

[...]

原始類型的超類(分別是超接口)是對泛型類型的任何參數化的超類(超接口)的擦除。

未從其超類或超接口繼承的原始類型C的構造函數,實例方法或非static字段的類型爲原始類型,該原始類型對應於在與C對應的通用聲明中擦除其類型。

簡單來講,當使用原始類型時,構造函數,實例方法和非static字段也會被刪除

請看如下示例:

class MyType<E> {
    List<String> getNames() {
        return Arrays.asList("John", "Mary");
    }

    public static void main(String[] args) {
        MyType rawType = new MyType();
        // unchecked warning!
        // required: List<String> found: List
        List<String> names = rawType.getNames();
        // compilation error!
        // incompatible types: Object cannot be converted to String
        for (String str : rawType.getNames())
            System.out.print(str);
    }
}

當咱們使用原始的MyTypegetNames將被擦除,所以它返回原始的List

JLS 4.6繼續解釋如下內容:

類型擦除還將映射構造函數或方法的簽名到沒有參數化類型或類型變量的簽名。 構造函數或方法簽名的擦除s是由相同的名字的簽名s ,全部給出的形參類型的擦除s

若是擦除方法或構造函數的簽名,則方法的返回類型以及泛型方法或構造函數的類型參數也會被擦除。

通用方法簽名的擦除沒有類型參數。

如下錯誤報告包含編譯器開發人員Maurizio Cimadamore和JLS的做者之一Alex Buckley關於爲什麼應發生這種行爲的一些想法: https : //bugs.openjdk.java.net/browse / JDK-6400189 。 (簡而言之,它使規範更簡單。)


若是不安全,爲何容許使用原始類型?

這是JLS 4.8的另外一句話:

僅容許使用原始類型做爲對遺留代碼兼容性的讓步。 強烈建議不要在將通用性引入Java編程語言以後在編寫的代碼中使用原始類型。 Java編程語言的將來版本可能會禁止使用原始類型。

有效的Java 2nd Edition也要添加如下內容:

既然您不該該使用原始類型,那麼語言設計者爲何要容許它們呢? 提供兼容性。

引入泛型後,Java平臺即將進入第二個十年,而且存在大量不使用泛型的Java代碼。 相當重要的是,全部這些代碼都必須合法並能夠與使用泛型的新代碼互操做。 將參數化類型的實例傳遞給設計用於普通類型的方法必須合法,反之亦然。 這項稱爲遷移兼容性的要求決定了支持原始類型的決定。

總而言之,絕對不要在新代碼中使用原始類型。 您應該始終使用參數化類型


有沒有例外?

不幸的是,因爲Java泛型是非泛型的,所以在新代碼中必須使用原始類型有兩個例外:

  • 類文字,例如List.class ,而不是List<String>.class
  • instanceof操做數,例如o instanceof Set ,而不是o instanceof Set<String>

也能夠看看

相關文章
相關標籤/搜索