variable - java type parameter
什麼是原始類型,為什麼我們不應該使用它? (10)
Java中的原始類型是什麼,為什麼我經常聽說它們不應該用於新代碼中?
原始類型是Java語言的古老歷史。 一開始就有Collections
,他們沒有任何東西,只剩下Objects
。 Collections
上的每個操作都需要從Object
轉換為所需的類型。
List aList = new ArrayList();
String s = "Hello World!";
aList.add(s);
String c = (String)aList.get(0);
雖然這在大部分時間都有效,但錯誤確實發生了
List aNumberList = new ArrayList();
String one = "1";//Number one
aNumberList.add(one);
Integer iOne = (Integer)aNumberList.get(0);//Insert ClassCastException here
舊的無類型集合不能強制類型安全,因此程序員必須記住他存儲在集合中的內容。
為了克服這個限制,泛型開發人員會聲明一次存儲類型,而編譯器會這樣做。
List<String> aNumberList = new ArrayList<String>();
aNumberList.add("one");
Integer iOne = aNumberList.get(0);//Compile time error
String sOne = aNumberList.get(0);//works fine
比較:
// Old style collections now known as raw types
List aList = new ArrayList(); //Could contain anything
// New style collections with Generics
List<String> aList = new ArrayList<String>(); //Contains only Strings
比較接口更複雜:
//raw, not type save can compare with Other classes
class MyCompareAble implements CompareAble
{
int id;
public int compareTo(Object other)
{return this.id - ((MyCompareAble)other).id;}
}
//Generic
class MyCompareAble implements CompareAble<MyCompareAble>
{
int id;
public int compareTo(MyCompareAble other)
{return this.id - other.id;}
}
請注意,使用原始類型與compareTo(MyCompareAble)
實現CompareAble
接口是不可能的。 為什麼你不應該使用它們:
- 存儲在
Collection
任何Object
必須在可以使用之前進行投射 - 使用泛型支持編譯時間檢查
- 使用原始類型與將每個值存儲為
Object
編譯器的功能:泛型向後兼容,它們使用與原始類型相同的Java類。 魔術主要發生在編譯時。
List<String> someStrings = new ArrayList<String>();
someStrings.add("one");
String one = someStrings.get(0);
將編譯為:
List someStrings = new ArrayList();
someStrings.add("one");
String one = (String)someStrings.get(0);
如果您直接使用原始類型,則這與您將編寫的代碼相同。 以為我不確定CompareAble
接口會發生什麼,我猜想它會創建兩個compareTo
函數,一個接受一個MyCompareAble
,另一個接受一個Object
並在投射它之後將其傳遞給第一個。
什麼是原始類型的替代品:使用generics
問題:
- Java中的原始類型是什麼,為什麼我經常聽說它們不應該用於新代碼中?
- 如果我們不能使用原始類型,那麼有什麼替代方案?它有多好?
什麼是原始類型?
Java語言規範定義了一個原始類型 ,如下所示:
JLS 4.8原始類型
原始類型被定義為以下之一:
通過採用不帶伴隨類型參數列表的泛型類型聲明的名稱形成的引用類型。
元素類型為原始類型的數組類型。
未從
R
的超類或超級接口繼承的原始類型R
的非static
成員類型。
這裡有一個例子來說明:
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
。
mt1
和mt2
都用實際的類型參數聲明,所以它們不是原始類型。
原始類型有什麼特別之處?
基本上,原始類型的行為就像在泛型引入之前一樣。 也就是說,以下內容在編譯時是完全合法的。
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 String
一個instanceof 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第2版的條目23:不要在新代碼中使用原始類型 :
原始類型
List
和參數化類型List<Object>
之間有什麼區別? 鬆散地說,前者選擇了泛型類型檢查,而後者明確地告訴編譯器它可以容納任何類型的對象。 雖然可以將List<String>
傳遞給List<String>
類型的參數,但不能將其傳遞給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
任何內容add
到List
。
考慮以前代碼片段的以下變化:
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);
}
}
當我們使用原始的MyType
, getNames
也會被刪除,所以它返回一個原始的List
!
JLS 4.6繼續解釋以下內容:
類型擦除還將構造函數或方法的簽名映射到沒有參數化類型或類型變量的簽名。 構造函數或方法簽名
s
刪除是一個由與s
相同名稱和s
中所有形式參數類型的刪除組成的簽名。如果方法或構造函數的簽名被擦除,方法的返回類型和泛型方法或構造函數的類型參數也會被擦除。
刪除通用方法的簽名沒有類型參數。
以下錯誤報告包含來自編譯器開發者Maurizio Cimadamore和JLS作者之一Alex Buckley關於為什麼應該發生這種行為的一些想法: https://bugs.openjdk.java.net/browse/JDK-6400189 : https://bugs.openjdk.java.net/browse/JDK-6400189 。 (簡而言之,它使規格更簡單。)
如果它不安全,為什麼它允許使用原始類型?
這裡是JLS 4.8的另一個引用:
僅允許使用原始類型作為遺留代碼兼容性的讓步。 強烈建議在將通用性引入到Java編程語言後編寫的代碼中使用原始類型。 未來版本的Java編程語言可能會禁止使用原始類型。
有效的Java第二版也有這個補充:
鑑於你不應該使用原始類型,為什麼語言設計者允許它們? 提供兼容性。
Java平台即將進入第二個十年,當時泛型被引入,並且存在大量不使用泛型的Java代碼。 所有這些代碼都保持合法並與使用泛型的新代碼進行互操作被認為至關重要。 將參數化類型的實例傳遞給設計用於普通類型的方法必須合法,反之亦然。 這一要求被稱為遷移兼容性 ,促使決定支持原始類型。
總之,不應該在新代碼中使用原始類型。 你應該總是使用參數化類型 。
沒有例外嗎?
不幸的是,由於Java泛型沒有具體化,所以在新代碼中必須使用原始類型有兩個例外:
- 類文字,例如
List.class
,而不是List<String>.class
-
instanceof
操作數,例如o instanceof Set
,而不是o instanceof Set<String>
也可以看看
教程頁面 。
原始類型是沒有任何類型參數的泛型類或接口的名稱。 例如,給定泛型Box類:
public class Box<T> {
public void set(T t) { /* ... */ }
// ...
}
要創建Box的參數化類型,請為正式類型參數T提供實際的類型參數:
Box<Integer> intBox = new Box<>();
如果省略實際的類型參數,則創建一個原始類型的Box:
Box rawBox = new Box();
raw -type是使用泛型類型時缺少類型參數的原因 。
原始類型不應該被使用,因為它可能會導致運行時錯誤,比如在應該是一個int
Set
中插入double
。
Set set = new HashSet();
set.add(3.45); //ok
當從Set
獲取東西時,你不知道會發生什麼。 讓我們假設你期望它全部是int
,你將它投射到Integer
; 運行時出現double
3.45時發生異常。
將一個類型參數添加到您的Set
,您將立即收到編譯錯誤。 這種先發製人的錯誤可以讓您在運行期間爆發之前解決問題(從而節省時間和精力)。
Set<Integer> set = new HashSet<Integer>();
set.add(3.45); //NOT ok.
以下是另一種原始類型會咬你的例子:
public class StrangeClass<T> {
@SuppressWarnings("unchecked")
public <X> X getSomethingElse() {
return (X)"Testing something else!";
}
public static void main(String[] args) {
final StrangeClass<Object> withGeneric = new StrangeClass<>();
final StrangeClass withoutGeneric = new StrangeClass();
final String value1,
value2;
// Works
value1 = withGeneric.getSomethingElse();
// Produces compile error:
// incompatible types: java.lang.Object cannot be converted to java.lang.String
value2 = withoutGeneric.getSomethingElse();
}
}
正如在接受的答案中提到的那樣,您在原始類型的代碼中失去對泛型的所有支持。 每個類型參數都被轉換為擦除(在上面的例子中就是Object
)。
原始類型在表達你想表達的內容時很好。
例如,反序列化函數可能會返回一個List
,但它不知道列表的元素類型。 所以List
在這裡是合適的返回類型。
在這裡,我正在考慮多個案例,通過這些案例你可以清除這個概念
1. ArrayList<String> arr = new ArrayList<String>();
2. ArrayList<String> arr = new ArrayList();
3. ArrayList arr = new ArrayList<String>();
情況1
ArrayList<String> arr
它是一個ArrayList
引用變量,其類型為String
,引用類型為String
的ArralyList
對象。 這意味著它只能保存String類型的對象。
這是嚴格的String
不是原始類型,所以它永遠不會發出警告。
arr.add("hello");// alone statement will compile successfully and no warning.
arr.add(23); //prone to compile time error.
//error: no suitable method found for add(int)
案例2
在這種情況下, ArrayList<String> arr
是一個嚴格的類型,但是您的Object new ArrayList();
是一種原始類型。
arr.add("hello"); //alone this compile but raise the warning.
arr.add(23); //again prone to compile time error.
//error: no suitable method found for add(int)
這裡arr
是嚴格的類型。 所以,當添加一個integer
時會引發編譯時錯誤。
警告 : -
Raw
類型對像被引用到ArrayList
的Strict
類型引用變量。
案例3
在這種情況下, ArrayList arr
是一個原始類型,但是您的Object new ArrayList<String>();
是一種嚴格的類型。
arr.add("hello");
arr.add(23); //compiles fine but raise the warning.
它將添加任何類型的對象,因為arr
是原始類型。
警告 : -
Strict
類型對像被引用到raw
類型引用的變量。
我在做了一些示例練習之後發現了這個頁面,並且具有完全相同的困惑。
==============我從這段代碼中提取樣本===============
public static void main(String[] args) throws IOException {
Map wordMap = new HashMap();
if (args.length > 0) {
for (int i = 0; i < args.length; i++) {
countWord(wordMap, args[i]);
}
} else {
getWordFrequency(System.in, wordMap);
}
for (Iterator i = wordMap.entrySet().iterator(); i.hasNext();) {
Map.Entry entry = (Map.Entry) i.next();
System.out.println(entry.getKey() + " :\t" + entry.getValue());
}
======================到此代碼========================
public static void main(String[] args) throws IOException {
// replace with TreeMap to get them sorted by name
Map<String, Integer> wordMap = new HashMap<String, Integer>();
if (args.length > 0) {
for (int i = 0; i < args.length; i++) {
countWord(wordMap, args[i]);
}
} else {
getWordFrequency(System.in, wordMap);
}
for (Iterator<Entry<String, Integer>> i = wordMap.entrySet().iterator(); i.hasNext();) {
Entry<String, Integer> entry = i.next();
System.out.println(entry.getKey() + " :\t" + entry.getValue());
}
}
================================================== =============================
這可能會更安全,但需要花費4個小時來破壞哲學...
編譯器希望你寫這個:
private static List<String> list = new ArrayList<String>();
否則,你可以將任何你喜歡的類型添加到list
,使得實例化成new ArrayList<String>()
毫無意義。 Java泛型只是一個編譯時的特性,所以用new ArrayList<String>()
創建的對像如果分配給“原始類型” List
的引用,會很樂意接受Integer
或JFrame
元素 - 對象本身對什麼類型一無所知它應該包含,只有編譯器。
private static List<String> list = new ArrayList<String>();
您應該指定類型參數。
該警告建議定義為支持generics類型應該參數化,而不是使用其原始形式。
List
被定義為支持泛型: public class List<E>
。 這允許執行編譯時檢查的許多類型安全的操作。