java泛型與集合常見陷阱(Java泛型集合)

本文目錄一覽:

Java中集合/泛型相關問題

首先,Vector類是list中的常用子類,其底層依賴數組來實現存儲數據!,所以它會按照原樣輸出數據,然後你在遍歷anum該枚舉變數時,anum.hasMoreElements()這句話只是指向一個位子並判斷有沒有數據而已,不會移動指針,所以會出現one two ,當循環到three時,首先System.out.print(astr+” “); 會輸出three,然後 if(astr.length()4) avector.remove(astr); 移除three,這裡有個問題你要注意,avector.remove(astr);這句話會改變anum它的長度!!會讓anum它的長度減1,所以本來下個數應該讀取four的,結果因為長度減了1,所以four被跳過去了!所以輸出了five,其中eight一樣,因為seven被移除,也被跳過去了!再來就是最後一句System.out.println(avector);因為,eight在循環時被跳過,所以,當你輸出循環結束後的avector時,發現eight還在裡面!!希望你能看懂!

Java泛型集合的應用和方法

泛型(Generic type 或者 generics)是對 Java 語言的類型系統的一種擴展,以支持創建可以按類型進行參數化的類。可以把類型參數看作是使用參數化類型時指定的類型的一個佔位符,就像方法的形式參數是運行時傳遞的值的佔位符一樣。

可以在集合框架(Collection framework)中看到泛型的動機。例如,Map 類允許您向一個 Map 添加任意類的對象,即使最常見的情況是在給定映射(map)中保存某個特定類型(比如 String)的對象。

因為 Map.get() 被定義為返回 Object,所以一般必須將 Map.get() 的結果強制類型轉換為期望的類型,如下面的代碼所示:

Map m = new HashMap();

m.put(“key”, “blarg”);

String s = (String) m.get(“key”);

要讓程序通過編譯,必須將 get() 的結果強制類型轉換為 String,並且希望結果真的是一個 String。但是有可能某人已經在該映射中保存了不是 String 的東西,這樣的話,上面的代碼將會拋出 ClassCastException。

理想情況下,您可能會得出這樣一個觀點,即 m 是一個 Map,它將 String 鍵映射到 String 值。這可以讓您消除代碼中的強制類型轉換,同時獲得一個附加的類型檢查層,該檢查層可以防止有人將錯誤類型的鍵或值保存在集合中。這就是泛型所做的工作。

泛型的好處

Java 語言中引入泛型是一個較大的功能增強。不僅語言、類型系統和編譯器有了較大的變化,以支持泛型,而且類庫也進行了大翻修,所以許多重要的類,比如集合框架,都已經成為泛型化的了。這帶來了很多好處:

類型安全。 泛型的主要目標是提高 Java 程序的類型安全。通過知道使用泛型定義的變數的類型限制,編譯器可以在一個高得多的程度上驗證類型假設。沒有泛型,這些假設就只存在於程序員的頭腦中(或者如果幸運的話,還存在於代碼注釋中)。

Java 程序中的一種流行技術是定義這樣的集合,即它的元素或鍵是公共類型的,比如「String 列表」或者「String 到 String 的映射」。通過在變數聲明中捕獲這一附加的類型信息,泛型允許編譯器實施這些附加的類型約束。類型錯誤現在就可以在編譯時被捕獲了,而不是在運行時當作 ClassCastException 展示出來。將類型檢查從運行時挪到編譯時有助於您更容易找到錯誤,並可提高程序的可靠性。

消除強制類型轉換。 泛型的一個附帶好處是,消除源代碼中的許多強制類型轉換。這使得代碼更加可讀,並且減少了出錯機會。

儘管減少強制類型轉換可以降低使用泛型類的代碼的羅嗦程度,但是聲明泛型變數會帶來相應的羅嗦。比較下面兩個代碼例子。

該代碼不使用泛型:

List li = new ArrayList();

li.put(new Integer(3));

Integer i = (Integer) li.get(0);

該代碼使用泛型:

ListInteger li = new ArrayListInteger();

li.put(new Integer(3));

Integer i = li.get(0);

在簡單的程序中使用一次泛型變數不會降低羅嗦程度。但是對於多次使用泛型變數的大型程序來說,則可以累積起來降低羅嗦程度。

潛在的性能收益。 泛型為較大的優化帶來可能。在泛型的初始實現中,編譯器將強制類型轉換(沒有泛型的話,程序員會指定這些強制類型轉換)插入生成的位元組碼中。但是更多類型信息可用於編譯器這一事實,為未來版本的 JVM 的優化帶來可能。

由於泛型的實現方式,支持泛型(幾乎)不需要 JVM 或類文件更改。所有工作都在編譯器中完成,編譯器生成類似於沒有泛型(和強制類型轉換)時所寫的代碼,只是更能確保類型安全而已。

泛型用法的例子

泛型的許多最佳例子都來自集合框架,因為泛型讓您在保存在集合中的元素上指定類型約束。考慮這個使用 Map 類的例子,其中涉及一定程度的優化,即 Map.get() 返回的結果將確實是一個 String:

Map m = new HashMap();

m.put(“key”, “blarg”);

String s = (String) m.get(“key”);

如果有人已經在映射中放置了不是 String 的其他東西,上面的代碼將會拋出 ClassCastException。泛型允許您表達這樣的類型約束,即 m 是一個將 String 鍵映射到 String 值的 Map。這可以消除代碼中的強制類型轉換,同時獲得一個附加的類型檢查層,這個檢查層可以防止有人將錯誤類型的鍵或值保存在集合中。

下面的代碼示例展示了 JDK 5.0 中集合框架中的 Map 介面的定義的一部分:

public interface MapK, V {

public void put(K key, V value);

public V get(K key);

}

注意該介面的兩個附加物:

類型參數 K 和 V 在類級別的規格說明,表示在聲明一個 Map 類型的變數時指定的類型的佔位符。

在 get()、put() 和其他方法的方法簽名中使用的 K 和 V。

為了贏得使用泛型的好處,必須在定義或實例化 Map 類型的變數時為 K 和 V 提供具體的值。以一種相對直觀的方式做這件事:

MapString, String m = new HashMapString, String();

m.put(“key”, “blarg”);

String s = m.get(“key”);

當使用 Map 的泛型化版本時,您不再需要將 Map.get() 的結果強制類型轉換為 String,因為編譯器知道 get() 將返回一個 String。

在使用泛型的版本中並沒有減少鍵盤錄入;實際上,比使用強制類型轉換的版本需要做更多鍵入。使用泛型只是帶來了附加的類型安全。因為編譯器知道關於您將放進 Map 中的鍵和值的類型的更多信息,所以類型檢查從執行時挪到了編譯時,這會提高可靠性並加快開發速度。

向後兼容

在 Java 語言中引入泛型的一個重要目標就是維護向後兼容。儘管 JDK 5.0 的標準類庫中的許多類,比如集合框架,都已經泛型化了,但是使用集合類(比如 HashMap 和 ArrayList)的現有代碼將繼續不加修改地在 JDK 5.0 中工作。當然,沒有利用泛型的現有代碼將不會贏得泛型的類型安全好處。

二 泛型基礎

類型參數

在定義泛型類或聲明泛型類的變數時,使用尖括弧來指定形式類型參數。形式類型參數與實際類型參數之間的關係類似於形式方法參數與實際方法參數之間的關係,只是類型參數表示類型,而不是表示值。

泛型類中的類型參數幾乎可以用於任何可以使用類名的地方。例如,下面是 java.util.Map 介面的定義的摘錄:

public interface MapK, V {

public void put(K key, V value);

public V get(K key);

}

Map 介面是由兩個類型參數化的,這兩個類型是鍵類型 K 和值類型 V。(不使用泛型)將會接受或返回 Object 的方法現在在它們的方法簽名中使用 K 或 V,指示附加的類型約束位於 Map 的規格說明之下。

當聲明或者實例化一個泛型的對象時,必須指定類型參數的值:

MapString, String map = new HashMapString, String();

注意,在本例中,必須指定兩次類型參數。一次是在聲明變數 map 的類型時,另一次是在選擇 HashMap 類的參數化以便可以實例化正確類型的一個實例時。

編譯器在遇到一個 MapString, String 類型的變數時,知道 K 和 V 現在被綁定為 String,因此它知道在這樣的變數上調用 Map.get() 將會得到 String 類型。

除了異常類型、枚舉或匿名內部類以外,任何類都可以具有類型參數。

命名類型參數

推薦的命名約定是使用大寫的單個字母名稱作為類型參數。這與 C++ 約定有所不同(參閱 附錄 A:與 C++ 模板的比較),並反映了大多數泛型類將具有少量類型參數的假定。對於常見的泛型模式,推薦的名稱是:

K —— 鍵,比如映射的鍵。

V —— 值,比如 List 和 Set 的內容,或者 Map 中的值。

E —— 異常類。

T —— 泛型。

泛型不是協變的

關於泛型的混淆,一個常見的來源就是假設它們像數組一樣是協變的。其實它們不是協變的。ListObject 不是 ListString 的父類型。

如果 A 擴展 B,那麼 A 的數組也是 B 的數組,並且完全可以在需要 B[] 的地方使用 A[]:

Integer[] intArray = new Integer[10];

Number[] numberArray = intArray;

上面的代碼是有效的,因為一個 Integer 是 一個 Number,因而一個 In。

java 集合,泛型

要點: ArrayList不是唯一的集合

TreeSet以有序狀態保持並可防止重複

HashMap可用成對的name/value來保存與取出

LinkedList針對經常插入或刪除中間元素所設計的高效率集合

HashSet防止重複的集合,可快速地找尋相符的元素

LinkedHashMap類似HashMap,但可以記住元素插入的順序,也可以設定成依照元

素上次存取的先後來排序

可以使用TreeSet或Collections.sort()方法來排序

ArrayList是最常用的泛型化類型,有兩個關鍵部分:類的聲明和新增元素的方

法的聲明

Sort()方法只能接受Comparable對象的list

Collections.sort()會把list中的String依照字母排序

以泛型的觀點來說,extend代表extend or implement,代表「是一個。。。」

,且適用於類和介面

調用單一參數的sort(List o)方法代表由list元素上的comparetTo()方法來決定

順序。因此元素必須要實現Comparable這個介面

調用sort(List o,Comparator c)方法代表不會調用list元素的compareTo()方法

,而會使用Comparator的compare()方法,這意味著list元素不需要實現Comparable.

LIST:是一種索引位置的集合

SET:注重獨一無二的性質,不允許重複的集合

MAP:使用成對的鍵值和數據值,值可重複,但KEY不可

如果foo與bar兩對象相等,則foo.equals(bar)會返回true,且兩者的hashCode()

也會返回相同的值。要讓Set能把對象視為重複的,就必須讓它們符合上面的條件。

hashCode()與equals()的相關規定:

1)如果兩個對象相等,則hashcode必須也是相等的。

2)如果兩個對象相等,對其中一個對象調用equals()必須返回true。也就是說

,若a.equals(b)則b.equals(a).

3)如果對象有相同的hashcode值,它們也不一定是相等的。但若兩個對象相等

,則hashcode值一定是相等的。

4)因此若equals()被覆蓋過,則hashcode()也必須被覆蓋。

5)hashcode()的默認行為是對在heap上的對象產生獨特的值.你沒有override過

hashcode(),則該class的兩個對象怎樣都不會被認為是相同的。

6)equals()的默認行為是執行==的比較。也就是說會去測試兩個引用是否對

上heap上同一個對象。如果equals()沒有被覆蓋過,兩個對象永遠都不會被視為相同的,

因為不同的對象有不同的位元組組合。

a.equals()必須與a.hashCode()==b.hashCode()等值。

但a.hashCode()==b.hashCode()不一定要與a.equals()等值。

要使用TreeSet,下列其中一項必須為真:

TreeSet集合中的元素必須是有實現Comparable的類型

使用重載、取用Comparator參數的構造函數來創建TreeSet.

MAP中的元素實際上是兩個對象,關鍵字和值。值可以重複,但是關鍵字不可。

如果方法的參數是Animal的數組,它也能夠取用Animal次類型的數組。

也就是說,如果方法是這樣聲明的:void foo(Animal[] a){}

若Dog有extend過Animal,你就可以用下列的兩種方式調用:

foo(anAnimalArray);

foo(aDogArray);

數組的類型是在運行期間檢查的,但集合的類型檢查只會發生在編譯期間

在方法參數中使用萬用字元時,編譯器會阻止任何可能破壞引用參數所指集合的

行為。你能夠使用list中任何元素的方法,但不能加入元素。也就是說,你可以操作集合

元素,但不能新增集合元素。如此才能保障執行期間的安全性,因為編譯器會阻止執行期

的恐怖行動。所以下面這個程序是可以的:

for(Animal a:animals){

a.eat();

}

但這個就過不了編譯:

animals.add(new Cat());

——————–

程序代碼:

=========================================

SongList.txt

———————————-

Communication/The Cardingans

Black Dog/Led Zeppelin

Dreams/Van Halen

Comfortably Numb/Pink Floyd

Beth/Kiss

倒退嚕/黃克林

=========================================

記錄KTV最常點的歌,沒有排序功能

import java.util.*;

import java.io.*;

public class Jukebox1{

//歌曲名稱存在String的ArrayList上

ArrayListString songList= new ArrayListString();

public static void main(String[] args){

new Jukebox1.go();

}

//這個方法會載入文件並列出內容

public void go(){

getSongs();

System.out.println(songList);

}

//讀取文件的程序

void getSongs(){

try{

File file=new File(“SongList.txt”);

BufferedReader reader=new BufferedReader(new

FileReader(file));

String line=null;

while(line=reader.readLine())!=null){

addSong(line);

}

}catch(Exception ex){ex.printStackTrace();}

}

void addSong(String lineToParse){

//split()方法會用反斜線來拆開歌曲內容

String[] tokens=lineToParse.split(“/”);

//因為只需要歌名,所以只取第一項加入SongList

songList.add(tokens[0]);

}

}

====

//依照加入的順序列出,與原始的文本文件順序相同。

輸出:

%java Jukebox1

[Communication,Black Dog,Dreams,Comfortably Numb,Beth,倒退嚕]

—————————————————————–

對點歌系統加上Collections.sort()

import java.util.*;

import java.io.*;

public class Jukebox1{

//歌曲名稱存在String的ArrayList上

ArrayListString songList= new ArrayListString();

public static void main(String[] args){

new Jukebox1.go();

}

//這個方法會載入文件並列出內容

public void go(){

getSongs();

System.out.println(songList);

Collections.sort()(songList);

System.out.println(songList);

}

//讀取文件的程序

void getSongs(){

try{

File file=new File(“SongList.txt”);

BufferedReader reader=new BufferedReader(new

FileReader(file));

String line=null;

while(line=reader.readLine())!=null){

addSong(line);

}

}catch(Exception ex){ex.printStackTrace();}

}

void addSong(String lineToParse){

//split()方法會用反斜線來拆開歌曲內容

String[] tokens=lineToParse.split(“/”);

//因為只需要歌名,所以只取第一項加入SongList

songList.add(tokens[0]);

}

}

====

輸出:

%java Jukebox1

[Communication,Black Dog,Dreams,Comfortably Numb,Beth,倒退嚕]

[Beth,Black Dog,Comfortably Numb,Communication,Dreams,倒退嚕]

————————————————————-

現在要用song對象而不只是string

class Song{

//對應四種屬性的四個實例變數

String title;

String artist;

String rating;

String bpm;

Song(String t, String a,String r,String b){

//變數都會在創建時從構造函數中設定

title=t;

artist=a;

rating=r;

bpm=b;

}

//四種屬性的getter

public String getTitle(){

retrun title;

}

public String getArtist(){

retrun artist;

}

public String getRating(){

retrun rating;

}

public String getBpm(){

retrun bpm;

}

//將toString()覆蓋過,讓它返回歌名

public String toString(){

return title;

}

}

=========================================

SongList.txt

———————————-

Communication/The Cardingans/5/80

Black Dog/Led Zeppelin/4/84

Dreams/Van Halen/6/20

Comfortably Numb/Pink Floyd/5/110

Beth/Kiss/4/100

倒退嚕/黃克林/5/90

=========================================

說明:新的歌曲文件帶有四項屬性,所以我們需要創建出Song的實例變數來帶這些屬性。

修改點歌系統程序

import java.util.*;

import java.io.*;

public class Jukebox1{

//將String改成Song類型

ArrayListSong songList= new ArrayListSong();

public static void main(String[] args){

new Jukebox1.go();

}

//這個方法會載入文件並列出內容

public void go(){

getSongs();

System.out.println(songList);

Collections.sort()(songList);

System.out.println(songList);

}

//讀取文件的程序

void getSongs(){

try{

File file=new File(“SongList.txt”);

BufferedReader reader=new BufferedReader(new

FileReader(file));

String line=null;

while(line=reader.readLine())!=null){

addSong(line);

}

}catch(Exception ex){ex.printStackTrace();}

}

void addSong(String lineToParse){

//split()方法會用反斜線來拆開歌曲內容

String[] tokens=lineToParse.split(“/”);

//使用解析出來 的四項屬性來創建Song對象並加入到list中

Song nextSong =new Song(tokens[0],tokens[1],tokens[2],tokens[3]);

songList.add(nextSong);

}

}

====

輸出:

%javac Jukebox3.java

Jukebox3.java:15:cannot find symbol

symbol :method sort(java.util.ArrrayListSong)

location:class java.util.Collections

Collections.sort(songList);

1 error

關於泛型

1)創建被泛型化類的實例

創建ArrayList時你必須要指定它所容許的對象。

new ArrayListSong()

2)聲明與指定泛型類型的變數

ListSong songList = new ArrayListSong()

3)聲明(與調用)取用泛型類型的方法

void foo(ListSong list)

x.foo(songList)

———————————————

ArrayList的類型參數

下面這行程序:

ArrayListString thisList = new ArrayListString

代表這個ArrayList:

public class ArrayListE extends AbstractListE…{

public boolean add(E o)

//更多代碼

}

會被編譯器這樣看待:

public class ArrayListString extends AbstractListString…{

public boolean add(String o)

//更多代碼

}

——————————————————————-

運用泛型的方法

1)使用定義在類聲明的類型參數。

public class ArrayListE extends AbstractListE…{

public boolean add(E o)

}

2)使用未定義在類聲明的類型參數

//T extends Animal 為方法聲明的一部分,表示任何被聲明為Animal或Animal的子型

public T extends Animal void takeThing(ArrayListT list)

//public void takeThing(ArrayListAnimal list)表示只有ArrayListAnimal合法。

—————————————————————————–

public static T extends Comparable? super T void sort(ListT list)

//T extends Comparable表示它必須是Comparable

//? super T表示Comparable的類型參數必須是T或T的父型

//ListT表示僅能傳入繼承Comparable的參數化類型的list

—————————————————————————–

class Song implements ComparableSong{

//對應四種屬性的四個實例變數

String title;

String artist;

String rating;

String bpm;

//Song s為要比較的對象

public int compareTo(Song s){

return title.compareTo(s.getTitle());

}

Song(String t, String a,String r,String b){

//變數都會在創建時從構造函數中設定

title=t;

artist=a;

rating=r;

bpm=b;

}

//四種屬性的getter

public String getTitle(){

retrun title;

}

public String getArtist(){

retrun artist;

}

public String getRating(){

retrun rating;

}

public String getBpm(){

retrun bpm;

}

//將toString()覆蓋過,讓它返回歌名

public String toString(){

return title;

}

}

====

調用sort()方法後會把Song依照字母作排序

輸出:

%java Jukebox3

[Communication,Black Dog,Dreams,Comfortably Numb,Beth,倒退嚕]

[Beth,Black Dog,Comfortably Numb,Communication,Dreams,倒退嚕]

—————————————————————

用Comparator更新點歌系統

我們在新版本做了三件事:

1)創建並實現Comparator的內部類,以compare()方法取代compareTo()方法

2)製作該類的實例

3)調用重載版的sort(),傳入歌曲的list以及Comparator的實例。

import java.util.*;

import java.io.*;

public class Jukebox5{

//將String改成Song類型

ArrayListSong songList= new ArrayListSong();

public static void main(String[] args){

new Jukebox5.go();

}

//創建江實現Comparator的內部類,注意到類型參數和要比較的類型是相符的

//one.getArtist()會返回String,compareTo以String來比較

class ArtistCompare implements ComparatorSong{

public int compare(Song one,Song two){

return one.getArtist().compareTo(two.getArtist());

}

}

//這個方法會載入文件並列出內容

public void go(){

getSongs();

System.out.println(songList);

Collections.sort()(songList);

System.out.println(songList);

//創建Comparator的實例,調用sort(),傳入list與Comparator對象

ArtistCompare artistCompare = new ArtistCompare();

Collections.sort(songList,artistCompare);

System.out.println(songList);

}

//讀取文件的程序

void getSongs(){

try{

File file=new File(“SongList.txt”);

BufferedReader reader=new BufferedReader(new

FileReader(file));

String line=null;

while(line=reader.readLine())!=null){

addSong(line);

}

}catch(Exception ex){ex.printStackTrace();}

}

void addSong(String lineToParse){

//split()方法會用反斜線來拆開歌曲內容

String[] tokens=lineToParse.split(“/”);

//使用解析出來 的四項屬性來創建Song對象並加入到list中

Song nextSong =new Song(tokens[0],tokens[1],tokens[2],tokens[3]);

songList.add(nextSong);

}

}

—————————————————————–

以HashSet取代ArrayList

import java.util.*;

import java.io.*;

public class Jukebox6{

ArrayListSong songList= new ArrayListSong();

public static void main(String[] args){

new Jukebox6.go();

}

public void go(){

//這個方法沒有更新,所以它還是會把Song加到ArrayList中

getSongs();

System.out.println(songList);

Collections.sort()(songList);

System.out.println(songList);

//創建參數化的HashSet來保存Song,addAll()可以複製其他集合的元素

HashSetSong songSet = new HashSetSong();

songSet.addAll(songList);

System.out.println(songSet);

}

//getSongs() and addSong() methods

}

——————————————————————-

有覆蓋過hashCode()與equals()的Song類

class Song{

String title;

String artist;

String rating;

String bpm;

//Object aSong為要比較的對象

public boolean equals(Object aSong){

Song s = (Song) aSong;

//因為歌名是String,且String本來就喜笑顏開過的equals(),所以我們可調用

return getTitle().equals(s.getTitle());

}

public int hashCode(){

//String也有覆蓋過的hashCode(),注意到hashCode()與

//equals()使用相同的實例變數

return title.hashCode();

}

public int compareTo(Song s){

return title.compareTo(s.getTitle());

}

Song(String t, String a,String r,String b){

//變數都會在創建時從構造函數中設定

title=t;

artist=a;

rating=r;

bpm=b;

}

//四種屬性的getter

public String getTitle(){

retrun title;

}

public String getArtist(){

retrun artist;

}

public String getRating(){

retrun rating;

}

public String getBpm(){

retrun bpm;

}

//將toString()覆蓋過,讓它返回歌名

public String toString(){

return title;

}

}

———————————————————–

如果想要保持有序,使用TreeSet

import java.util.*;

import java.io.*;

public class Jukebox8{

ArrayListSong songList= new ArrayListSong();

int val;

public static void main(String[] args){

new Jukebox8.go();

}

public void go(){

getSongs();

System.out.println(songList);

Collections.sort()(songList);

System.out.println(songList);

//調用沒有參數的構造函數來用TreeSet取代HashSet意味著

//以對象的compareTo()方法來進行排序

TreeSetSong songSet = new TreeSetSong();

//使用addAll()可以把對象全部加入

songSet.addAll(songList);

System.out.println(songList);

}

//讀取文件的程序

void getSongs(){

try{

File file=new File(“SongList.txt”);

BufferedReader reader=new BufferedReader(new

FileReader(file));

String line=null;

while(line=reader.readLine())!=null){

addSong(line);

}

}catch(Exception ex){ex.printStackTrace();}

}

void addSong(String lineToParse){

//split()方法會用反斜線來拆開歌曲內容

String[] tokens=lineToParse.split(“/”);

//使用解析出來 的四項屬性來創建Song對象並加入到list中

Song nextSong =new Song(tokens[0],tokens[1],tokens[2],tokens[3]);

songList.add(nextSong);

}

}

———————————–

public T extends Animal void takeThing(ArrayListT list)

public void takeThing(ArrayList? extends Animallist)是一樣的~

java中泛型與普通集合 有什麼聯繫 有什麼關係 概念是怎麼樣的?

泛型就是添加了一個類型參數你可以在用泛型類或者泛型方法的時候確定這個泛型為一個確定的類型

在以前的java版本中是沒有泛型的只能用根類Object來表示泛型,但是這樣的話就不能表示摸一個確定的類型因為object是所有類的父類所以它是一個表示所有類型

java中加入了泛型以後所有的集合框架都重新寫了使它們支持泛型,這樣你就可以這樣寫

ArrayListString al=new ArrayListString();

表示一個String型的Arraylist

但是泛型有一個問題就是它不支持基本類型作為類型參數

不知道這麼說你能不能理解~~~

學習java遇到的泛型問題,望大牛解答,感激不盡!

泛型(Generic type 或者generics)是對 Java 語言的類型系統的一種擴展,以支持創建可以按類型進行參數化的類。可以把類型參數看作是使用參數化類型時指定的類型的一個佔位符,就像方法的形式參數是運行時傳遞的值的佔位符一樣。

可以在集合框架(Collection framework)中看到泛型的動機。例如,Map類允許您向一個Map添加任意類的對象,即使最常見的情況是在給定映射(map)中保存某個特定類型(比如String)的對象。

因為Map.get()被定義為返回Object,所以一般必須將Map.get()的結果強制類型轉換為期望的類型,如下面的代碼所示:

Map m = new HashMap();

m.put(“key”, “blarg”);

String s = (String) m.get(“key”);

要讓程序通過編譯,必須將get()的結果強制類型轉換為String,並且希望結果真的是一個String。但是有可能某人已經在該映射中保存了不是String的東西,這樣的話,上面的代碼將會拋出ClassCastException。

理想情況下,您可能會得出這樣一個觀點,即m是一個Map,它將String鍵映射到String值。這可以讓您消除代碼中的強制類型轉換,同時獲得一個附加的類型檢查層,該檢查層可以防止有人將錯誤類型的鍵或值保存在集合中。這就是泛型所做的工作。

泛型的好處

Java 語言中引入泛型是一個較大的功能增強。不僅語言、類型系統和編譯器有了較大的變化,以支持泛型,而且類庫也進行了大翻修,所以許多重要的類,比如集合框架,都已經成為泛型化的了。這帶來了很多好處:

· 類型安全。泛型的主要目標是提高 Java 程序的類型安全。通過知道使用泛型定義的變數的類型限制,編譯器可以在一個高得多的程度上驗證類型假設。沒有泛型,這些假設就只存在於程序員的頭腦中(或者如果幸運的話,還存在於代碼注釋中)。

Java 程序中的一種流行技術是定義這樣的集合,即它的元素或鍵是公共類型的,比如「String列表」或者「String到String的映射」。通過在變數聲明中捕獲這一附加的類型信息,泛型允許編譯器實施這些附加的類型約束。類型錯誤現在就可以在編譯時被捕獲了,而不是在運行時當作ClassCastException展示出來。將類型檢查從運行時挪到編譯時有助於您更容易找到錯誤,並可提高程序的可靠性。

· 消除強制類型轉換。泛型的一個附帶好處是,消除源代碼中的許多強制類型轉換。這使得代碼更加可讀,並且減少了出錯機會。

儘管減少強制類型轉換可以降低使用泛型類的代碼的羅嗦程度,但是聲明泛型變數會帶來相應的羅嗦。比較下面兩個代碼例子。

該代碼不使用泛型:

List li = new ArrayList();

li.put(new Integer(3));

Integer i = (Integer) li.get(0);

該代碼使用泛型:

ListInteger li = new ArrayListInteger();

li.put(new Integer(3));

Integer i = li.get(0);

在簡單的程序中使用一次泛型變數不會降低羅嗦程度。但是對於多次使用泛型變數的大型程序來說,則可以累積起來降低羅嗦程度。

· 潛在的性能收益。泛型為較大的優化帶來可能。在泛型的初始實現中,編譯器將強制類型轉換(沒有泛型的話,程序員會指定這些強制類型轉換)插入生成的位元組碼中。但是更多類型信息可用於編譯器這一事實,為未來版本的JVM 的優化帶來可能。

由於泛型的實現方式,支持泛型(幾乎)不需要JVM 或類文件更改。所有工作都在編譯器中完成,編譯器生成類似於沒有泛型(和強制類型轉換)時所寫的代碼,只是更能確保類型安全而已。

泛型用法的例子

泛型的許多最佳例子都來自集合框架,因為泛型讓您在保存在集合中的元素上指定類型約束。考慮這個使用Map類的例子,其中涉及一定程度的優化,即Map.get()返回的結果將確實是一個String:

Map m = new HashMap();

m.put(“key”, “blarg”);

String s = (String) m.get(“key”);

如果有人已經在映射中放置了不是String的其他東西,上面的代碼將會拋出ClassCastException。泛型允許您表達這樣的類型約束,即m是一個將String鍵映射到String值的Map。這可以消除代碼中的強制類型轉換,同時獲得一個附加的類型檢查層,這個檢查層可以防止有人將錯誤類型的鍵或值保存在集合中。

下面的代碼示例展示了 JDK 5.0 中集合框架中的Map介面的定義的一部分:

public interface MapK, V {

public void put(K key, V value);

public V get(K key);

}

注意該介面的兩個附加物:

* 類型參數 K 和 V 在類級別的規格說明,表示在聲明一個 Map 類型的變數時指定的類型的佔位符。

* 在 get()、put() 和其他方法的方法簽名中使用的 K 和 V。

為了贏得使用泛型的好處,必須在定義或實例化Map類型的變數時為K和V提供具體的值。以一種相對直觀的方式做這件事:

MapString, String m = new HashMapString, String();

m.put(“key”, “blarg”);

String s = m.get(“key”);

當使用Map的泛型化版本時,您不再需要將Map.get()的結果強制類型轉換為String,因為編譯器知道get()將返回一個String。

在使用泛型的版本中並沒有減少鍵盤錄入;實際上,比使用強制類型轉換的版本需要做更多鍵入。使用泛型只是帶來了附加的類型安全。因為編譯器知道關於您將放進Map中的鍵和值的類型的更多信息,所以類型檢查從執行時挪到了編譯時,這會提高可靠性並加快開發速度。

向後兼容

在 Java 語言中引入泛型的一個重要目標就是維護向後兼容。儘管 JDK 5.0 的標準類庫中的許多類,比如集合框架,都已經泛型化了,但是使用集合類(比如HashMap和ArrayList)的現有代碼將繼續不加修改地在 JDK 5.0 中工作。當然,沒有利用泛型的現有代碼將不會贏得泛型的類型安全好處。

類型參數

在定義泛型類或聲明泛型類的變數時,使用尖括弧來指定形式類型參數。形式類型參數與實際類型參數之間的關係類似於形式方法參數與實際方法參數之間的關係,只是類型參數表示類型,而不是表示值。

泛型類中的類型參數幾乎可以用於任何可以使用類名的地方。例如,下面是java.util.Map介面的定義的摘錄:

public interface MapK, V {

public void put(K key, V value);

public V get(K key);

}

Map介面是由兩個類型參數化的,這兩個類型是鍵類型K和值類型V。(不使用泛型)將會接受或返回Object的方法現在在它們的方法簽名中使用K或V,指示附加的類型約束位於Map的規格說明之下。

當聲明或者實例化一個泛型的對象時,必須指定類型參數的值:

MapString, String map = new HashMapString, String();

注意,在本例中,必須指定兩次類型參數。一次是在聲明變數map的類型時,另一次是在選擇HashMap類的參數化以便可以實例化正確類型的一個實例時。

編譯器在遇到一個MapString, String類型的變數時,知道K和V現在被綁定為String,因此它知道在這樣的變數上調用Map.get()將會得到String類型。

除了異常類型、枚舉或匿名內部類以外,任何類都可以具有類型參數。

命名類型參數

推薦的命名約定是使用大寫的單個字母名稱作為類型參數。這與C++ 約定有所不同(參閱附錄 A:與 C++ 模板的比較),並反映了大多數泛型類將具有少量類型參數的假定。對於常見的泛型模式,推薦的名稱是:

* K —— 鍵,比如映射的鍵。

* V —— 值,比如 List 和 Set 的內容,或者 Map 中的值。

* E —— 異常類。

* T —— 泛型。

泛型不是協變的

關於泛型的混淆,一個常見的來源就是假設它們像數組一樣是協變的。其實它們不是協變的。ListObject不是ListString的父類型。

如果 A 擴展 B,那麼 A 的數組也是 B 的數組,並且完全可以在需要B[]的地方使用A[]:

Integer[] intArray = new Integer[10];

Number[] numberArray = intArray;

上面的代碼是有效的,因為一個Integer是一個Number,因而一個Integer數組是一個Number數組。但是對於泛型來說則不然。下面的代碼是無效的:

ListInteger intList = new ArrayListInteger();

ListNumber numberList = intList; // invalid

最初,大多數 Java 程序員覺得這缺少協變很煩人,或者甚至是「壞的(broken)」,但是之所以這樣有一個很好的原因。如果可以將ListInteger賦給ListNumber,下面的代碼就會違背泛型應該提供的類型安全:

ListInteger intList = new ArrayListInteger();

ListNumber numberList = intList; // invalid

numberList.add(new Float(3.1415));

因為intList和numberList都是有別名的,如果允許的話,上面的代碼就會讓您將不是Integers的東西放進intList中。但是,正如下一屏將會看到的,您有一個更加靈活的方式來定義泛型。

package com.ibm.course.generics;

import java.util.ArrayList;

import java.util.List;

public class GenericsExample {

public static void main(String[] args) {

Integer[] integer = new Integer[5];

Number[] number = integer;

System.out.println(number[0]);// null

number[0] = new Float(7.65);

System.out.println(number[0]);

System.out.println(integer[0]);

ListInteger list = new ArrayListInteger();

// Type mismatch: cannot convert from ListInteger to ListNumber

// ListNumber listObj = list;

}

}

ListNumber listObj = list;導致編譯錯誤:Type mismatch: cannot convert from ListInteger to ListNumber

而System.out.println(number[0]);和System.out.println(integer[0]);導致運行時異常:

Exception in thread “main” java.lang.ArrayStoreException: java.lang.Float

at com.ibm.course.generics.GenericsExample.main(GenericsExample.java:15)

類型通配符

假設您具有該方法:

void printList(List l) {

for (Object o : l)

System.out.println(o);

}

上面的代碼在 JDK 5.0 上編譯通過,但是如果試圖用ListInteger調用它,則會得到警告。出現警告是因為,您將泛型(ListInteger)傳遞給一個只承諾將它當作List(所謂的原始類型)的方法,這將破壞使用泛型的類型安全。

如果試圖編寫像下面這樣的方法,那麼將會怎麼樣?

void printList(ListObject l) {

for (Object o : l)

System.out.println(o);

}

它仍然不會通過編譯,因為一個ListInteger不是一個ListObject(正如前一屏泛型不是協變的 中所學的)。這才真正煩人——現在您的泛型版本還沒有普通的非泛型版本有用!

解決方案是使用類型通配符:

void printList(List? l) {

for (Object o : l)

System.out.println(o);

}

上面代碼中的問號是一個類型通配符。它讀作「問號」。List?是任何泛型List的父類型,所以您完全可以將ListObject、ListInteger或ListListListFlutzpah傳遞給printList()。

package com.ibm.course.generics;

import java.util.ArrayList;

import java.util.List;

public class GenericExample {

public static void main(String[] args) {

ListInteger integer = new ArrayListInteger();

integer.add(new Integer(0));

integer.add(new Integer(1));

ListString str = new ArrayListString();

str.add(new String(“Hello”));

str.add(new String(“World”));

List? li=integer;

li=str;

printList(integer);

printList(str);

}

public static void printList(List? l) {

for (Object o : l) {

System.out.println(o);

}

}

}

上面的例子程序沒有警告也沒有編譯錯誤。

類型通配符的作用

前一屏類型通配符 中引入了類型通配符,這讓您可以聲明List?類型的變數。您可以對這樣的List做什麼呢?非常方便,可以從中檢索元素,但是不能添加元素(可以添加null)。原因不是編譯器知道哪些方法修改列表哪些方法不修改列表,而是(大多數)變化的方法比不變化的方法需要更多的類型信息。下面的代碼則工作得很好:

ListInteger li = new ArrayListInteger();

li.add(new Integer(42));

List? lu = li;

System.out.println(lu.get(0));

為什麼該代碼能工作呢?對於lu,編譯器一點都不知道List的類型參數的值。但是編譯器比較聰明,它可以做一些類型推理。在本例中,它推斷未知的類型參數必須擴展Object。(這個特定的推理沒有太大的跳躍,但是編譯器可以作出一些非常令人佩服的類型推理,後面就會看到(在底層細節 一節中)。所以它讓您調用List.get()並推斷返回類型為Object。

另一方面,下面的代碼不能工作:

ListInteger li = new ArrayListInteger();

li.add(new Integer(42));

List? lu = li;

lu.add(new Integer(43)); // error

在本例中,對於lu,編譯器不能對List的類型參數作出足夠嚴密的推理,以確定將Integer傳遞給List.add()是類型安全的。所以編譯器將不允許您這麼做。

以免您仍然認為編譯器知道哪些方法更改列表的內容哪些不更改列表內容,請注意下面的代碼將能工作,因為它不依賴於編譯器必須知道關於lu的類型參數的任何信息:

ListInteger li = new ArrayListInteger();

li.add(new Integer(42));

List? lu = li;

lu.clear();

泛型方法

(在類型參數 一節中)您已經看到,通過在類的定義中添加一個形式類型參數列表,可以將類泛型化。方法也可以被泛型化,不管它們定義在其中的類是不是泛型化的。

泛型類在多個方法簽名間實施類型約束。在ListV中,類型參數V出現在get()、add()、contains()等方法的簽名中。當創建一個MapK, V類型的變數時,您就在方法之間宣稱一個類型約束。您傳遞給add()的值將與get()返回的值的類型相同。

類似地,之所以聲明泛型方法,一般是因為您想要在該方法的多個參數之間宣稱一個類型約束。例如,下面代碼中的ifThenElse()方法,根據它的第一個參數的布爾值,它將返回第二個或第三個參數:

public T T ifThenElse(boolean b, T first, T second) {

return b ? first : second;

}

注意,您可以調用ifThenElse(),而不用顯式地告訴編譯器,您想要T的什麼值。編譯器不必顯式地被告知 T 將具有什麼值;它只知道這些值都必須相同。編譯器允許您調用下面的代碼,因為編譯器可以使用類型推理來推斷出,替代T的String滿足所有的類型約束:

String s = ifThenElse(b, “a”, “b”);

類似地,您可以調用:

Integer i = ifThenElse(b, new Integer(1), new Integer(2));

但是,編譯器不允許下面的代碼,因為沒有類型會滿足所需的類型約束:

String s = ifThenElse(b, “pi”, new Float(3.14));

為什麼您選擇使用泛型方法,而不是將類型T添加到類定義呢?(至少)有兩種情況應該這樣做:

* 當泛型方法是靜態的時,這種情況下不能使用類類型參數。

* 當 T 上的類型約束對於方法真正是局部的時,這意味著沒有在相同類的另一個 方法簽名中使用相同 類型 T 的約束。通過使得泛型方法的類型參數對於方法是局部的,可以簡化封閉類型的簽名。

有限制類型

在前一屏泛型方法 的例子中,類型參數V是無約束的或無限制的類型。有時在還沒有完全指定類型參數時,需要對類型參數指定附加的約束。

考慮例子Matrix類,它使用類型參數V,該參數由Number類來限制:

public class MatrixV extends Number { … }

編譯器允許您創建MatrixInteger或MatrixFloat類型的變數,但是如果您試圖定義MatrixString類型的變數,則會出現錯誤。類型參數V被判斷為由Number限制。在沒有類型限制時,假設類型參數由Object限制。這就是為什麼前一屏泛型方法 中的例子,允許List.get()在List?上調用時返回Object,即使編譯器不知道類型參數V的類型。

原創文章,作者:LWGPW,如若轉載,請註明出處:https://www.506064.com/zh-tw/n/318037.html

(0)
打賞 微信掃一掃 微信掃一掃 支付寶掃一掃 支付寶掃一掃
LWGPW的頭像LWGPW
上一篇 2025-01-11 16:28
下一篇 2025-01-11 16:28

相關推薦

  • Java JsonPath 效率優化指南

    本篇文章將深入探討Java JsonPath的效率問題,並提供一些優化方案。 一、JsonPath 簡介 JsonPath是一個可用於從JSON數據中獲取信息的庫。它提供了一種DS…

    編程 2025-04-29
  • java client.getacsresponse 編譯報錯解決方法

    java client.getacsresponse 編譯報錯是Java編程過程中常見的錯誤,常見的原因是代碼的語法錯誤、類庫依賴問題和編譯環境的配置問題。下面將從多個方面進行分析…

    編程 2025-04-29
  • Java Bean載入過程

    Java Bean載入過程涉及到類載入器、反射機制和Java虛擬機的執行過程。在本文中,將從這三個方面詳細闡述Java Bean載入的過程。 一、類載入器 類載入器是Java虛擬機…

    編程 2025-04-29
  • Java騰訊雲音視頻對接

    本文旨在從多個方面詳細闡述Java騰訊雲音視頻對接,提供完整的代碼示例。 一、騰訊雲音視頻介紹 騰訊雲音視頻服務(Cloud Tencent Real-Time Communica…

    編程 2025-04-29
  • Java Milvus SearchParam withoutFields用法介紹

    本文將詳細介紹Java Milvus SearchParam withoutFields的相關知識和用法。 一、什麼是Java Milvus SearchParam without…

    編程 2025-04-29
  • Java 8中某一周的周一

    Java 8是Java語言中的一個版本,於2014年3月18日發布。本文將從多個方面對Java 8中某一周的周一進行詳細的闡述。 一、數組處理 Java 8新特性之一是Stream…

    編程 2025-04-29
  • Java判斷字元串是否存在多個

    本文將從以下幾個方面詳細闡述如何使用Java判斷一個字元串中是否存在多個指定字元: 一、字元串遍歷 字元串是Java編程中非常重要的一種數據類型。要判斷字元串中是否存在多個指定字元…

    編程 2025-04-29
  • VSCode為什麼無法運行Java

    解答:VSCode無法運行Java是因為默認情況下,VSCode並沒有集成Java運行環境,需要手動添加Java運行環境或安裝相關插件才能實現Java代碼的編寫、調試和運行。 一、…

    編程 2025-04-29
  • Java任務下發回滾系統的設計與實現

    本文將介紹一個Java任務下發回滾系統的設計與實現。該系統可以用於執行複雜的任務,包括可回滾的任務,及時恢復任務失敗前的狀態。系統使用Java語言進行開發,可以支持多種類型的任務。…

    編程 2025-04-29
  • Java 8 Group By 會影響排序嗎?

    是的,Java 8中的Group By會對排序產生影響。本文將從多個方面探討Group By對排序的影響。 一、Group By的概述 Group By是SQL中的一種常見操作,它…

    編程 2025-04-29

發表回復

登錄後才能評論