1. 程式人生 > >Java 泛型知識點

Java 泛型知識點

1、在你建立引數化型別的一個例項時,編譯器會為你負責轉型操作,並且保證型別的正確性。泛型的主要目的之一就是用來指定容器要持有什麼型別的物件,而且由編譯器來保證型別的正確性。示例:

public class Holder<T>{

private T a;

public Holder(T a){

this.a = a;

}

public T get(){

return a;

}

public void set(T a){

this.a = a;

}

public static void main(String[] args){

Holder<Test> h = new Holder<Test>(new Test());

Test t = h.get();

//h.set("test a stirng");error

}

}

現在,當你建立Holder物件時,必須指明想持有什麼型別的物件,將其置於尖括號內。就像main()中那樣。那麼,你就只能在Holder中存入該型別(或其子類)了。並且,在你從Hodler中去取出它持有的物件時,自動地就是正確的型別。

這就是Java泛型的核心概念:告訴編譯器想使用什麼型別,然後編譯器幫你處理一切細節。

2、Java泛型的侷限性:基本型別無法作為型別引數。不過,Java SE5具備了自動打包和拆包的功能,可以很方便的在基本型別和包裝類之間進行轉換。

3、是否擁有泛型方法,與其所在的類是否是泛型類沒有關係。

4、關於泛型方法的一個基本的指導原則:無論何時,只要你能做到,你就應該使用泛型方法。也就是說,如果使用泛型方法可以取代整個類泛型化,那麼就應該只使用泛型方法,因為它可以使事情更清楚明白。

5、型別推斷只對賦值操作有效,其他的時候並不起作用。示例:

List<String> l = new ArrayList<>();

而public static <k,v> Map<k,v> map(){

return new HashMap<k,v>();

}

f(Test.Map());由此此時並非賦值操作,這時編譯器並不會執行型別推斷,編譯器認為:呼叫泛型方法後,其返回值被賦值給了一個Object型別的變數。解決方式是:使用顯式的型別說明。f(Test.<String,String>Map());只有在編寫非賦值語句的時候,我們才需要這樣的額外說明。

6、在泛型程式碼內部,你無法獲得任何有關泛型引數型別的資訊。即:你可以知道諸如型別引數識別符號和泛型型別邊界這類的資訊,你無法知道用來建立某個特定例項的實際的型別引數。原因是Java泛型是通過擦除來實現的,因為這任何具體的型別資訊都會被擦出了,你唯一知道的是你在使用一個物件,因此List<String>和List<Long>在執行時事實上是相同的型別,這兩種形式都被擦除成它們的原始型別:List。

7、型別邊界

與Java相比(Java的設計靈感來源於C++),C++模板實現了真正意義上的泛型:

template<Class T> class Test{

T object;

public :Test(T t){

this.object = t;

}

void operate(){

object.f();

}}

class HasF{

public:void  f(){

.....

}

}

當例項化這個模板的時候,C++編譯器將進行檢查,因此在Test<HasF>被例項化的這一刻,它會看到HasF擁有一個f()方法。如果情況並非如此,就會得到一個編譯器錯誤,這樣型別安全就得到了保障。但是,Java泛型就不同了:

public class HasF{

public void f(){

System.out.println("F()");

}

public class Test<T>{

private T object;

public Test(T t){

this.object  = t;

}

public void operate(){

objecte.f();//error

}

public static void main(String[] args){

HasF hasf = new HasF();

Test<HasF> test = new Test<HasF>(hasf);

test.operate();

}

由於有了擦出,Java編譯器無法將operate必須能夠在object上呼叫f()這一需求對映到HasF擁有f()這一事實上。為了可以呼叫f()方法,我們必須協助泛型類,給定泛型類的邊界,例如:T extends HasF。我們說泛型型別引數將擦出到它的第一個邊界(它可能會有多個邊界),編譯器會把型別引數替換為它的擦出,像T extends HasF,T擦出到HasF,就好像在類的宣告中用HasF替換了一樣。

擦出的代價是顯著的。泛型不能用於顯示地引用執行時型別的操作之中,例如轉型、instanceof操作和new表示式。因為所有 關於引數的型別資訊都丟失了。因此也就喪失了在泛型程式碼中執行某些操作的能力,任何在執行時需要知道確切型別資訊的操作都無法工作。

8、擦出的補償:型別標籤 -- Class物件,這意味著你需要顯式的傳遞你的型別的Class物件,以便你可以在型別表示式中使用它。例如,對instanceof的嘗試失敗了,是因為其型別資訊已經被擦出了,如果引入型別標籤,你就可以轉而使用動態的isInstance():

public class TestB<T>{

class<T> kind;

public boolean f(Object obj){

return kind.isInstance(obg);

}

}

同樣,new T()操作失敗,部分原因是因為擦出,而另一個原因 是因為編譯器不能驗證T是否有預設的建構函式。同樣可以使用型別標籤Class物件來解決這個問題:注意:因為newInstance會呼叫預設建構函式,但是並非所有的類都有預設建構函式的,比如Integer。因此Sun建議使用顯示的工廠模式:


9、泛型陣列

我們不能直接建立泛型陣列,一般的解決方案是在任何想建立泛型陣列的地方使用ArrayList,例如private List<T> array = new ArrayList<T>();這裡你將獲得陣列的行為,以及由泛型提供的編譯期的型別安全。

成功的建立泛型陣列的唯一方式是建立一個被擦出型別的新陣列,然後對其轉型。

T[] array = (T[])new Object[size];

因為有了擦除,陣列的執行時型別就只能是Object[]。如果我們立即將其轉型為T[],那麼在編譯期該陣列的實際型別就將丟失,而編譯器可能會錯過某些潛在的錯誤檢查。

正因為這樣,所以我們應該在使用陣列元素的時候,新增一個對T的轉型。

10、萬用字元

先看一下陣列的一種特殊的行為:可以向子型別陣列賦予基型別的陣列引用

class Fruit{}

class  Apple extends Fruit{}

class Jonathan extends Apple{}

class Orange extends Fruit{}

Fruit[] fruit = new Apple[10];

fruit[0] = new Apple();//ok

fruit[1] = new Jonathan();//ok

fruit[2] = new Fruit();//complier allows you to add fruit,but in runtime you will get a ArrayStoreException

最後一行程式碼為啥會得到一個異常呢?因為fruit的實際陣列型別是Apple[],你應該只能在其中放置Apple或Apple的子類。又因為fruit的宣告是一個Fruit[]引用 -- 它有什麼理由不允許將Fruit物件或者任何從Fruit繼承而來的物件,放置到這個陣列呢。因此,在編譯期,這是允許的。但是,執行時的陣列機制知道它處理的是Apple[],因此會在陣列中放置型別時丟擲異常。所以很明顯,陣列物件可以保留有關它們包含的物件型別的規則。就好像陣列對它們持有的物件是有意識的。

對陣列的這種賦值並不是那麼可怕,因為在執行時可以發現你已經插入了不正確的型別。但是,泛型的主要目標之一是將這種錯誤檢測移入到編譯期。因此,當我們檢視使用泛型容器代替陣列的時候,會發生什麼?

List<Fruit> flist = new ArrayList<Apple>();//很顯然,會產生編譯錯誤,由於編譯器不能對該行程式碼瞭解足夠多的資訊,因此拒絕向上轉型的,實際上這根本不是向上轉型,因為Apple的List在型別上不等價於Fruit的List。

與陣列不同,泛型沒有內建的協変型別。這是因為陣列在語言中是完全定義的,因此內建了編譯期和執行時的檢查,但是在使用泛型時,編譯器和執行時系統都不知道你想用什麼型別做些什麼,以及應該採用什麼樣的規則。

但是,有時你想要在兩個型別之間建立某種型別的向上轉型關係,這正是萬用字元所允許的。

List<? extends Fruit> flist = new ArrayList<Apple>();//ok

//complie error

flist.add(new Apple());

flist.add(new Fruit());

flist.add(new Object());

萬用字元引用的是明確的型別,你無法向flist放置任何一個物件,因為一旦執行這種型別的向上轉型,你就將丟失掉向其中傳遞任何物件的能力,甚至是傳遞Object都不行。

Fruit f = flist.get(0);

如上程式碼是安全的,因為這個List中的任何物件至少具有Fruit型別,因此編譯器允許這麼做。

11、超型別萬用字元

<? super MyClass>可以宣告萬用字元是由某個特定類的任何基類來界定的,甚至可以使用型別引數:<?super T>(儘管你不能對泛型引數給出一個超型別邊界:即不能宣告<T super Myclass>)。這使得你可以安全的傳遞一個型別物件到泛型型別中。因此,有了超型別萬用字元就可以向集合中寫入了:

public void weiteTo(List<? super Apple> apples){

apples.add(new Apple());

apples.add(new Jonathan());

apples.add(new Fruit());//Error

}

捕獲轉換:

有一種情況特別需要使用<?>而不是原生型別。如果向一個使用<?>的方法傳遞原生型別,那麼對編譯器來說,可能會推斷出實際的型別引數,使得這個方法可以迴轉並呼叫另一個使用這個確切型別的方法。這種技術被稱為“捕獲轉換”;

public class CaptureConversion{

static <T> void f1(Holder<T> holder){

T t = holder.get();

System.out.println(t.getClass().getSimpleName());

}

static void f2(Holder<?> holder){

f1(holder);

}

main方法中呼叫:

Holder raw = new Holder<Integer>(1);

f1(raw);//有warning

f2(raw);沒有warining

}

f1()中的型別引數都是確切的,沒有萬用字元或邊界。在f2()中,Holder引數是一個無界萬用字元,因此它看起來是未知的。但是,在f2()中,f1()被呼叫,而f1()需要一個已知引數。這裡發生的是:引數型別在呼叫f2()的過程中被捕獲,因此可以在對f1()的呼叫中被使用。捕獲轉換隻有在這樣的情況下可以工作:即在方法內部,你需要知道確切的型別。

12、Java泛型的限制之一是:不能將基本型別用作型別引數。解決之道是使用基本型別的包裝器類以及Java SE5的自動包裝機制。示例:

List<Integer> list = new ArrayList<Integer>();

list.add(5);

注意:自動包裝機制解決了一些問題,但是並不是解決了所有的問題:自動包裝機制不能應用於陣列,因此這無法工作。示例:

class FArray{

public static <T> T[]  fill(T[] a,Generator<T> gen){

for(int i=0;i<a.length;i++){

a[i] = gen.next();

}

return a;

}

main方法中呼叫:

Integer[] integer = FArray.fill(new Integer[7],new RandomGenerator.Integer();//ok

FArray.fill(new int[7],new RandomIntGenerator());//error,無法自動包裝

}

13、自限定型別

自限定所做的,就是要求在繼承關係中,向下面這樣使用這個類:(對使用自限定的每個類的要求)

class A extends SelfBounded<A>{}

這會強制要求將正在定義的類當做引數傳遞給基類。

示例:

class SelfBounded<T extends SeleBounded<T>>{

T element;

SelfBounded<T> set(T arg){

element = arg;

return this;

}

T get(){return element;}

}

class A extends SelfBounded<A>{}

class B extends SelfBounded<A>{}//also ok

class D{}

class E extends SelfBounds<D>{}//complie error:D 不是自限定型別

自限定的引數的意義是什麼呢?它可以保證型別引數必須與正在被定義的類相同。還有,自限定限制只能強制作用於繼承關係。還可以將自限定用於泛型方法。

14、由於擦除的原因,將泛型應用於異常是非常受限的。catch語句不能捕獲泛型型別的異常。因為在編譯期和執行時都必須知道異常的確切型別。泛型類也不能直接或間接繼承自Throwable。但是,型別引數可能會在一個方法的throws子句中用到。這使得你可以編寫隨檢查型異常的型別而發生變化的泛型程式碼:

interface Processor<T,E extends Exception>{

void process(List<T> result) throws E;

}

Processor執行process(),並且可能會丟擲具有型別E的異常。

相關推薦

JAVA知識點

system 成對 public inter err ber anti ceo 基本類型 JAVA泛型 1.概述 泛型:即“參數化類型”。將類型由原來的具體類型參數化,類似於方法中的變量參數,此時類型同樣定義為參數形式,只有在調用/運行時才傳入具體的類型。 泛型的本質:

Java 知識點

1、在你建立引數化型別的一個例項時,編譯器會為你負責轉型操作,並且保證型別的正確性。泛型的主要目的之一就是用來指定容器要持有什麼型別的物件,而且由編譯器來保證型別的正確性。示例: public class Holder<T>{ private T a; publ

黑馬程式設計師——Java知識點

一、Java沒有泛型之前 1.Java集合對元素型別沒有任何限制,這樣可能引發一些問題。例如,想建立一個儲存Dob物件的集合,但程式也可以輕易地將Cat物件“扔”進去,這樣可能會引發異常。 2.集合並不能儲存物件的狀態資訊,集合只知道它存放的是Object,因此取出集合元

java知識點總結

1.泛型的基本應用泛型可以解決資料型別的安全性問題,其主要原理是在類宣告時通過一個標識表示類中某個屬性的型別或者是某個方法的返回值及引數型別。這樣在類宣告或者例項化時只要指定好需要的型別即可。[訪問許可權] class 類名稱 <泛型型別標識1,泛型型別標識2,...

Java知識點整理

泛型的最大好處是避免了類轉化異常(ClassCastException)的發生 為什麼要使用泛型 要寫一個表示座標的類,可以有3種格式,int,float,String,為了避免寫3個類,統一可以寫

java的一些知識點Java--應用--接口、方法、數組、嵌套

泛型數組 light inf 返回值 通過 類實例化 this str set 感謝這位大神: http://blog.csdn.net/waldmer/article/details/12773021 1、泛型接口 1.1泛型接口的基本概念 1.2泛型接口實現的兩

關於Java的小知識點

java不允許直接例項化一個泛型陣列 Stack<String>[] a = new Stack<String>[N]; 上面這種寫法是錯誤的!類似的還有下面這種!

Java擦除

類型信息 png ive over tool 創建 edit sid 註意點 Java泛型擦除: 什麽是泛型擦除? 首先了解一下什麽是泛型?我個人的理解:因為集合中能夠存儲隨意類型的對象。可是集合中最先存儲的對象類型一旦確定後,就不能在存儲其它類型的

Java

object java 信息 1、泛型的由來  我們先看下面這段代碼:1234567891011121314 List list = new ArrayList();list.add(24); //向集合中添加一個 Integer 類型的數據list.add("Tom");

Java,通配符和C#對照

size list ack ace arr 類型通配符 語法 ++ net c#的泛型沒有類型通配符,原因是.net的泛型是CLR支持的泛型,而Java的JVM並不支持泛型,僅僅是語法糖,在編譯器編譯的時候都轉換成object類型 類型通配符在java中表示的是泛型

Java詳解

對象數組 整形 泛型方法 tty 接受 一個 div -m color 泛型的本質是參數化類型,也就是說所操作的數據類型被指定為一個參數。 假定我們有這樣一個需求:寫一個排序方法,能夠對整形數組、字符串數組甚至其他任何類型的數組進行排序,該如何實現? 答案是可以使用 Ja

JAVA(轉)

強制 off 實例 emp 思想 void 成了 意義 依然 一. 泛型概念的提出(為什麽需要泛型)? 首先,我們看下下面這段簡短的代碼: 1 public class GenericTest { 2 3 public static void

java

car .net arm man blog rman html http archive http://www.cnblogs.com/lwbqqyumidi/p/3837629.html http://blog.csdn.net/sunxianghuang/article

Java與集合筆記

第二章 情況 參數 編譯器 類型擦除 多個參數 一個 each ava 第一章 Java的泛型為了兼容性和防止代碼爆炸,在編譯成字節碼時會進行類型擦除,編譯器自動添加代碼做類型轉換(用到List<Integer>的地方用Integer來做轉換),自動做裝箱拆箱,

Java VS C# (偽 VS 真

功能 方法表 語法 一個 class msil 虛方法 strong 反射 一、泛型的本質 泛型是參數化類型的應用,操作的數據類型不限定於特定類型,可以根據實際需要設置不同的數據類型,以實現代碼復用。 二、Java泛型 Java 泛型是Java1.5新增的特性,JVM並

java中<?>和<T>區別

類型 父類 定義 ext 方法 oid tor 接收 通配符 public static void printColl(ArrayList<?> al){ Iterator<?> it = al.iterator();

第10篇-JAVA 集合框架-JAVA

java集合框架 java泛型 第10篇-JAVA 集合框架-JAVA 泛型每篇一句 :所有的不甘,都是因為還心存夢想初學心得: 不是每件事都註定會成功,但是每件事都值得一試(筆者:JEEP/711)[JAVA筆記 | 時間:2017-04-15| JAVA 集合框架/JAVA 泛型 ]1.JAVA

深入--java的繼承和實現、擦除

部分 end father 沒有 接口 子類 set int nal 泛型實現類: package generic; /** * 泛型父類:子類為“富二代”:子類的泛型要比父類多 * 1,保留父類的泛型-->子類為泛型類

java---通配符,嵌套

sys ram apple port sco java泛型 stat app 繼承鏈 package generic; import java.util.ArrayList; import java.util.List; /** * ? -->通

JAVA的基本使用

end ++ rc.d param details file println super turn Java1.5版本號推出了泛型,盡管這層語法糖給開發者帶來了代碼復用性方面的提升,可是這只是是編譯器所做的一層語法糖,在真正生成的字節碼中,這類信息卻被擦除了。筆者發