㈠ 你真的了解java中的泛型E、T、K、V嗎
Java 泛型(generics)是 JDK 5 中引入的一個新特性, 泛型提供了編譯時類型安全檢測機制,該機制允許開發者在編譯時檢測到非法的類型。泛型的本質是參數化類型,也就是說所操作的數據類型被指定為一個參數。
但是如果換成其他的字母代替 T ,在可讀性上可能會弱一些。通常情況下,T,E,K,V,?是這樣約定的:?表示不確定的 java 類型。T (type) 表示具體的一個java類型。K V (key value) 分別代表java鍵值中的Key Value。E (element) 代表Element。
㈡ java泛型
java泛型是1.5引進的一個新概念.
本題對於"? super T"和"? extends T",我從書上摘個經典的例子給你看看,如果不能理解,那麼你就參考以下書籍慢慢體會,循序漸進!
"? super T"和"? extends T",都是java泛型通配符,而用法又有區別,
還有super 和extends 不是java類關系中的超類和繼承的意思,他是通配符的下限和上限限制.
下面看一個通配符得高級用法:
在這一部分,我們來考慮一些通配符得高級用法。我們已經看到了上限通配符在從一個數據結構中進行讀取的幾個例子。現在考慮相反的情況,一個只寫的數據結構。
介面Sink是這種情況的一個簡單例子。
interface Sink<T> {
void flush(T t);
}
我們可以想像他被如下面的代碼一樣使用。方法writeAll() 被設計來把集合coll的所有元素flush到sink snk,並且返回最後一個flush的元素。
public static <T> T writeAll(Collection<T> coll, Sink<T> snk) {
T last = null;
for (T t : coll) {
last = t;
snk.flush(last);
}
return last;
}
Sink<Object> s;
Collection<String> cs;
String str = writeAll(cs, s); // 非法的調用!!
像上面所寫,writeAll() 的調用是非法的,因為沒有有效的類型參數可以被推斷出來。String 或 Object都不是T的合適的類型,因為Collection的元素和 Sink的元素必須是同樣的類型。
我們可以解決這個問題,通過使用通配符來修改writeAll()的方法簽名,如下:
<T> T writeAll(Collection<? extends T> coll, Sink<T> snk) { … }
String str = writeAll(cs, s); //可以調用但是返回值類型錯誤
這個調用現在是合法的,但是賦值產生錯誤,因為推斷出的返回值類型是 Object因為T 匹配了Sink的類型,Object。
解決方案是使用一種我們還沒有見過的有限制的通配符:有下限的通配符。語法 ? super T 表示T的一個未知的父類(或者是T自己)。這跟我們用? extends T 表示T的一個未知的子類是對應的。
<T> T writeAll(Collection<T> coll, Sink<? super T> snk) { … }
String str = writeAll(cs, s); // YES!!!
使用這個語法,這個調用是合法的,推斷出來的T是String,正是我們想要的。
現在讓我們看一個更現實的例子。一個 java.util.TreeSet<E> 代表一個有序的元素是E類型的樹。創建一個TreeSet的一個方法是傳遞一個 Comparator 對象給構造函數。這個Comparator將會用來按照需要對TreeSet進行排序。
TreeSet(Comparator<E> c)
Comparator 介面是核心:
interface Comparator<T> { int compare(T fst, T snd); }
假定我們要創建一個 TreeSet<String> 並傳遞一個合適的 Comparator,我們需要傳一個能比較String的Comparator。這可以是一個 Comparator<String>,也可以是一個 Comparator<Object>。然而我們不能用Comparator<Object>來調用上面的構造函數。我們可以使用一個有下限的通配符來得到我們需要的靈活性:
TreeSet(Comparator<? super E> c)
這允許任何可用的Comparator被傳遞進去。
作為使用下限通配符最終的例子,讓我們來看看方法 Collections.max(),它返回一個集合中的最大的元素。
現在,為了讓max()能工作,傳進來的集合中的所有元素必須實現 Comparatable介面。而且,他們必須都能夠被彼此比較(all be comparable to each other)。第一個嘗試是:
public static <T extends Comparable<T>> T max(Collection<T> coll)
就是說,方法的參數是某一個能和自己進行比較的T的集合。這限制太嚴格了。
為什麼?考慮一個能和任何對象進行比較的類型:
class Foo implements Comparable<Object> {...} ...
Collection<Foo> cf = ...;
Collections.max(cf); // 應該能工作
cf 中的每個元素都可以和每個cf中的其他元素進行比較,因為每個這樣的元素都是一個Foo,它可以和任意的對象進行比較,也可以和另一個Foo進行比較。
但是,使用上面的方法簽名,我們發現這個調用被拒絕。推斷出來的類型必須是Foo,但是Foo沒有實現介面 Comparable<Foo>。
T 精確的(exactly)和自己能比較是不需要的。所需要的是 T能夠和它的父類中的一個進行比較,這導出:(註:Collections.max()的實際方法簽名更復雜,我們在第10部分再討論。)
public static <T extends Comparable<? super T>> T max(Collection<T> coll)
這個推論對大多數想讓 Comparable 對任意類型生效的用法中都有效:你總是應該使用 Comparable<? super T>。
總之,如果你有一個只使用類型參數T作為參數的API,它的使用應該利用下限通配符( ? super T )的好處。相反的,如果API只返回T,你應該使用上限通配符( ? extends T )來給你的客戶端更大的靈活性。
(原文:This reasoning applies to almost any usage of Comparable that is intended to work for arbitrary types: You always want to use Comparable<? super T>.
In general, if you have an API that only uses a type parameter T as an argument, its uses should take advantage of lower bounded wildcards (? super T). Conversely, if the API only returns T, you'll give your clients more flexibility by using upper bounded wildcards (? extends T). )。
如果你想比較深刻的了解java泛型那麼
建議你看看<Java1.5泛型指南>
中文鏈接地址:http://blog.csdn.net/explorers/archive/2005/08/15/454837.aspx#_Toc111865968
英文pdf格式地址:http://java.sun.com/j2se/1.5/pdf/generics-tutorial.pdf
㈢ java中什麼是泛型,怎麼用泛型
最簡單的運用:List<String> list = new ArrayList<String>();
這個是什麼意思?
意思就是list只裝String類型的數據,別的,裝不進去
然後你就會覺得這個好像有點封裝的意思,比如LIst<Student>,封裝學生類
所以,所謂泛型就是廣泛的數據類型,你可以把它理解成封裝
㈣ 學習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);
該代碼使用泛型:
List<Integer> li = new ArrayList<Integer>();
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 Map<K, V> {
public void put(K key, V value);
public V get(K key);
}
注意該介面的兩個附加物:
* 類型參數 K 和 V 在類級別的規格說明,表示在聲明一個 Map 類型的變數時指定的類型的佔位符。
* 在 get()、put() 和其他方法的方法簽名中使用的 K 和 V。
為了贏得使用泛型的好處,必須在定義或實例化Map類型的變數時為K和V提供具體的值。以一種相對直觀的方式做這件事:
Map<String, String> m = new HashMap<String, 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 Map<K, V> {
public void put(K key, V value);
public V get(K key);
}
Map介面是由兩個類型參數化的,這兩個類型是鍵類型K和值類型V。(不使用泛型)將會接受或返回Object的方法現在在它們的方法簽名中使用K或V,指示附加的類型約束位於Map的規格說明之下。
當聲明或者實例化一個泛型的對象時,必須指定類型參數的值:
Map<String, String> map = new HashMap<String, String>();
注意,在本例中,必須指定兩次類型參數。一次是在聲明變數map的類型時,另一次是在選擇HashMap類的參數化以便可以實例化正確類型的一個實例時。
編譯器在遇到一個Map<String, String>類型的變數時,知道K和V現在被綁定為String,因此它知道在這樣的變數上調用Map.get()將會得到String類型。
除了異常類型、枚舉或匿名內部類以外,任何類都可以具有類型參數。
命名類型參數
推薦的命名約定是使用大寫的單個字母名稱作為類型參數。這與C++ 約定有所不同(參閱附錄 A:與 C++ 模板的比較),並反映了大多數泛型類將具有少量類型參數的假定。對於常見的泛型模式,推薦的名稱是:
* K —— 鍵,比如映射的鍵。
* V —— 值,比如 List 和 Set 的內容,或者 Map 中的值。
* E —— 異常類。
* T —— 泛型。
泛型不是協變的
關於泛型的混淆,一個常見的來源就是假設它們像數組一樣是協變的。其實它們不是協變的。List<Object>不是List<String>的父類型。
如果 A 擴展 B,那麼 A 的數組也是 B 的數組,並且完全可以在需要B[]的地方使用A[]:
Integer[] intArray = new Integer[10];
Number[] numberArray = intArray;
上面的代碼是有效的,因為一個Integer是一個Number,因而一個Integer數組是一個Number數組。但是對於泛型來說則不然。下面的代碼是無效的:
List<Integer> intList = new ArrayList<Integer>();
List<Number> numberList = intList; // invalid
最初,大多數 Java 程序員覺得這缺少協變很煩人,或者甚至是「壞的(broken)」,但是之所以這樣有一個很好的原因。如果可以將List<Integer>賦給List<Number>,下面的代碼就會違背泛型應該提供的類型安全:
List<Integer> intList = new ArrayList<Integer>();
List<Number> 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]);
List<Integer> list = new ArrayList<Integer>();
// Type mismatch: cannot convert from List<Integer> to List<Number>
// List<Number> listObj = list;
}
}
List<Number> listObj = list;導致編譯錯誤:Type mismatch: cannot convert from List<Integer> to List<Number>
而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 上編譯通過,但是如果試圖用List<Integer>調用它,則會得到警告。出現警告是因為,您將泛型(List<Integer>)傳遞給一個只承諾將它當作List(所謂的原始類型)的方法,這將破壞使用泛型的類型安全。
如果試圖編寫像下面這樣的方法,那麼將會怎麼樣?
void printList(List<Object> l) {
for (Object o : l)
System.out.println(o);
}
它仍然不會通過編譯,因為一個List<Integer>不是一個List<Object>(正如前一屏泛型不是協變的 中所學的)。這才真正煩人——現在您的泛型版本還沒有普通的非泛型版本有用!
解決方案是使用類型通配符:
void printList(List<?> l) {
for (Object o : l)
System.out.println(o);
}
上面代碼中的問號是一個類型通配符。它讀作「問號」。List<?>是任何泛型List的父類型,所以您完全可以將List<Object>、List<Integer>或List<List<List<Flutzpah>>>傳遞給printList()。
package com.ibm.course.generics;
import java.util.ArrayList;
import java.util.List;
public class GenericExample {
public static void main(String[] args) {
List<Integer> integer = new ArrayList<Integer>();
integer.add(new Integer(0));
integer.add(new Integer(1));
List<String> str = new ArrayList<String>();
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)。原因不是編譯器知道哪些方法修改列表哪些方法不修改列表,而是(大多數)變化的方法比不變化的方法需要更多的類型信息。下面的代碼則工作得很好:
List<Integer> li = new ArrayList<Integer>();
li.add(new Integer(42));
List<?> lu = li;
System.out.println(lu.get(0));
為什麼該代碼能工作呢?對於lu,編譯器一點都不知道List的類型參數的值。但是編譯器比較聰明,它可以做一些類型推理。在本例中,它推斷未知的類型參數必須擴展Object。(這個特定的推理沒有太大的跳躍,但是編譯器可以作出一些非常令人佩服的類型推理,後面就會看到(在底層細節 一節中)。所以它讓您調用List.get()並推斷返回類型為Object。
另一方面,下面的代碼不能工作:
List<Integer> li = new ArrayList<Integer>();
li.add(new Integer(42));
List<?> lu = li;
lu.add(new Integer(43)); // error
在本例中,對於lu,編譯器不能對List的類型參數作出足夠嚴密的推理,以確定將Integer傳遞給List.add()是類型安全的。所以編譯器將不允許您這么做。
以免您仍然認為編譯器知道哪些方法更改列表的內容哪些不更改列表內容,請注意下面的代碼將能工作,因為它不依賴於編譯器必須知道關於lu的類型參數的任何信息:
List<Integer> li = new ArrayList<Integer>();
li.add(new Integer(42));
List<?> lu = li;
lu.clear();
泛型方法
(在類型參數 一節中)您已經看到,通過在類的定義中添加一個形式類型參數列表,可以將類泛型化。方法也可以被泛型化,不管它們定義在其中的類是不是泛型化的。
泛型類在多個方法簽名間實施類型約束。在List<V>中,類型參數V出現在get()、add()、contains()等方法的簽名中。當創建一個Map<K, 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 Matrix<V extends Number> { ... }
編譯器允許您創建Matrix<Integer>或Matrix<Float>類型的變數,但是如果您試圖定義Matrix<String>類型的變數,則會出現錯誤。類型參數V被判斷為由Number限制。在沒有類型限制時,假設類型參數由Object限制。這就是為什麼前一屏泛型方法 中的例子,允許List.get()在List<?>上調用時返回Object,即使編譯器不知道類型參數V的類型。
㈤ 請教關於java的泛型方法
Java泛型詳解
概述
在引入范型之前,Java類型分為原始類型、復雜類型,其中復雜類型分為數組和類。引入范型後,一個復雜類型
就可以在細分成更多的類型。
例如原先的類型List,現在在細分成List<Object>, List<String>等更多的類型。
注意,現在List<Object>, List<String>是兩種不同的類型,
他們之間沒有繼承關系,即使String繼承了Object。下面的代碼是非法的
List<String> ls = new ArrayList<String>();
List<Object> lo = ls;
這樣設計的原因在於,根據lo的聲明,編譯器允許你向lo中添加任意對象(例如Integer),但是此對象是
List<String>,破壞了數據類型的完整性。
在引入范型之前,要在類中的方法支持多個數據類型,就需要對方法進行重載,在引入范型後,可以解決此問題
(多態),更進一步可以定義多個參數以及返回值之間的關系。
例如
public void write(Integer i, Integer[] ia);
public void write(Double d, Double[] da);
的范型版本為
public <T> void write(T t, T[] ta);
2. 定義&使用
類型參數的命名風格為:
推薦你用簡練的名字作為形式類型參數的名字(如果可能,單個字元)。最好避免小寫字母,這使它和其他的普通
的形式參數很容易被區分開來。
使用T代表類型,無論何時都沒有比這更具體的類型來區分它。這經常見於泛型方法。如果有多個類型參數,我們
可能使用字母表中T的臨近的字母,比如S。
如果一個泛型函數在一個泛型類裡面出現,最好避免在方法的類型參數和類的類型參數中使用同樣的名字來避免混
淆。對內部類也是同樣。
2.1 定義帶類型參數的類
在定義帶類型參數的類時,在緊跟類命之後的<>內,指定一個或多個類型參數的名字,同時也可以對類型參數的取
值范圍進行限定,多個類型參數之間用,號分隔。
定義完類型參數後,可以在定義位置之後的類的幾乎任意地方(靜態塊,靜態屬性,靜態方法除外)使用類型參數,
就像使用普通的類型一樣。
注意,父類定義的類型參數不能被子類繼承。
public class TestClassDefine<T, S extends T> {
....
}
2.2 定義待類型參數方法
在定義帶類型參數的方法時,在緊跟可見范圍修飾(例如public)之後的<>內,指定一個或多個類型參數的名字,同時也可以對類型參數的取值范圍進行限定,多個類型參數之間用,號分隔。
定義完類型參數後,可以在定義位置之後的方法的任意地方使用類型參數,就像使用普通的類型一樣。
例如:
public <T, S extends T> T testGenericMethodDefine(T t, S s){
...
}
注意:定義帶類型參數的方法,騎主要目的是為了表達多個參數以及返回值之間的關系。例如本例子中T和S的繼承關系, 返回值的類型和第一個類型參數的值相同。
如果僅僅是想實現多態,請優先使用通配符解決。通配符的內容見下面章節。
public <T> void testGenericMethodDefine2(List<T> s){
...
}
應改為
public void testGenericMethodDefine2(List<?> s){
...
}
3. 類型參數賦值
當對類或方法的類型參數進行賦值時,要求對所有的類型參數進行賦值。否則,將得到一個編譯錯誤。
3.1 對帶類型參數的類進行類型參數賦值
對帶類型參數的類進行類型參數賦值有兩種方式
第一聲明類變數或者實例化時。例如
List<String> list;
list = new ArrayList<String>;
第二繼承類或者實現介面時。例如
public class MyList<E> extends ArrayList<E> implements List<E> {...}
3.2 對帶類型參數方法進行賦值
當調用范型方法時,編譯器自動對類型參數進行賦值,當不能成功賦值時報編譯錯誤。例如
public <T> T testGenericMethodDefine3(T t, List<T> list){
...
}
public <T> T testGenericMethodDefine4(List<T> list1, List<T> list2){
...
}
Number n = null;
Integer i = null;
Object o = null;
testGenericMethodDefine(n, i);//此時T為Number, S為Integer
testGenericMethodDefine(o, i);//T為Object, S為Integer
List<Number> list1 = null;
testGenericMethodDefine3(i, list1)//此時T為Number
List<Integer> list2 = null;
testGenericMethodDefine4(list1, list2)//編譯報錯
3.3 通配符
在上面兩小節中,對是類型參數賦予具體的值,除此,還可以對類型參數賦予不確定值。例如
List<?> unknownList;
List<? extends Number> unknownNumberList;
List<? super Integer> unknownBaseLineIntgerList;
注意: 在Java集合框架中,對於參數值是未知類型的容器類,只能讀取其中元素,不能像其中添加元素,因為,其類型是未知,所以編譯器無法識別添加元素的類型和容器的類型是否兼容,唯一的例外是NULL
List<String> listString;
List<?> unknownList2 = listString;
unknownList = unknownList2;
listString = unknownList;//編譯錯誤
4. 數組范型
可以使用帶范型參數值的類聲明數組,卻不可有創建數組
List<Integer>[] iListArray;
new ArrayList<Integer>[10];//編譯時錯誤
5. 實現原理
5.1. Java范型時編譯時技術,在運行時不包含范型信息,僅僅Class的實例中包含了類型參數的定義信息。
泛型是通過java編譯器的稱為擦除(erasure)的前端處理來實現的。你可以(基本上就是)把它認為是一個從源碼到源碼的轉換,它把泛型版本轉換成非泛型版本。
基本上,擦除去掉了所有的泛型類型信息。所有在尖括弧之間的類型信息都被扔掉了,因此,比如說一個List<String>類型被轉換為List。所有對類型變數的引用被替換成類型變數的上限(通常是Object)。而且,無論何時結果代碼類型不正確,會插入一個到合適類型的轉換。
<T> T badCast(T t, Object o) {
return (T) o; // unchecked warning
}
類型參數在運行時並不存在。這意味著它們不會添加任何的時間或者空間上的負擔,這很好。不幸的是,這也意味著你不能依靠他們進行類型轉換。
5.2.一個泛型類被其所有調用共享
下面的代碼列印的結果是什麼?
List<String> l1 = new ArrayList<String>();
List<Integer> l2 = new ArrayList<Integer>();
System.out.println(l1.getClass() == l2.getClass());
或許你會說false,但是你想錯了。它列印出true。因為一個泛型類的所有實例在運行時具有相同的運行時類(class),
而不管他們的實際類型參數。
事實上,泛型之所以叫泛型,就是因為它對所有其可能的類型參數,有同樣的行為;同樣的類可以被當作許多不同的類型。作為一個結果,類的靜態變數和方法也在所有的實例間共享。這就是為什麼在靜態方法或靜態初始化代碼中或者在靜態變數的聲明和初始化時使用類型參數(類型參數是屬於具體實例的)是不合法的原因。
5.3. 轉型和instanceof
泛型類被所有其實例(instances)共享的另一個暗示是檢查一個實例是不是一個特定類型的泛型類是沒有意義的。
Collection cs = new ArrayList<String>();
if (cs instanceof Collection<String>) { ...} // 非法
類似的,如下的類型轉換
Collection<String> cstr = (Collection<String>) cs;
得到一個unchecked warning,因為運行時環境不會為你作這樣的檢查。
6. Class的范型處理
Java 5之後,Class變成范型化了。
JDK1.5中一個變化是類 java.lang.Class是泛型化的。這是把泛型擴展到容器類之外的一個很有意思的例子。
現在,Class有一個類型參數T, 你很可能會問,T 代表什麼?它代表Class對象代表的類型。比如說,
String.class類型代表 Class<String>,Serializable.class代表 Class<Serializable>。
這可以被用來提高你的反射代碼的類型安全。
特別的,因為 Class的 newInstance() 方法現在返回一個T, 你可以在使用反射創建對象時得到更精確的類型。
比如說,假定你要寫一個工具方法來進行一個資料庫查詢,給定一個SQL語句,並返回一個資料庫中符合查詢條件
的對象集合(collection)。
一個方法是顯式的傳遞一個工廠對象,像下面的代碼:
interface Factory<T> {
public T[] make();
}
public <T> Collection<T> select(Factory<T> factory, String statement) {
Collection<T> result = new ArrayList<T>();
/* run sql query using jdbc */
for ( int i=0; i<10; i++ ) { /* iterate over jdbc results */
T item = factory.make();
/* use reflection and set all of item』s fields from sql results */
result.add( item );
}
return result;
}
你可以這樣調用:
select(new Factory<EmpInfo>(){
public EmpInfo make() {
return new EmpInfo();
}
} , 」selection string」);
也可以聲明一個類 EmpInfoFactory 來支持介面 Factory:
class EmpInfoFactory implements Factory<EmpInfo> { ...
public EmpInfo make() { return new EmpInfo();}
}
然後調用:
select(getMyEmpInfoFactory(), "selection string");
這個解決方案的缺點是它需要下面的二者之一:
調用處那冗長的匿名工廠類,或為每個要使用的類型聲明一個工廠類並傳遞其對象給調用的地方,這很不自然。
使用class類型參數值是非常自然的,它可以被反射使用。沒有泛型的代碼可能是:
Collection emps = sqlUtility.select(EmpInfo.class, 」select * from emps」); ...
public static Collection select(Class c, String sqlStatement) {
Collection result = new ArrayList();
/* run sql query using jdbc */
for ( /* iterate over jdbc results */ ) {
Object item = c.newInstance();
/* use reflection and set all of item』s fields from sql results */
result.add(item);
}
return result;
}
但是這不能給我們返回一個我們要的精確類型的集合。現在Class是泛型的,我們可以寫:
Collection<EmpInfo> emps=sqlUtility.select(EmpInfo.class, 」select * from emps」); ...
public static <T> Collection<T> select(Class<T>c, String sqlStatement) {
Collection<T> result = new ArrayList<T>();
/* run sql query using jdbc */
for ( /* iterate over jdbc results */ ) {
T item = c.newInstance();
/* use reflection and set all of item』s fields from sql results */
result.add(item);
}
return result;
}
來通過一種類型安全的方式得到我們要的集合。
這項技術是一個非常有用的技巧,它已成為一個在處理注釋(annotations)的新API中被廣泛使用的習慣用法。
7. 新老代碼兼容
7.1. 為了保證代碼的兼容性,下面的代碼編譯器(javac)允許,類型安全有你自己保證
List l = new ArrayList<String>();
List<String> l = new ArrayList();
7.2. 在將你的類庫升級為范型版本時,慎用協變式返回值。
例如,將代碼
public class Foo {
public Foo create(){
return new Foo();
}
}
public class Bar extends Foo {
public Foo create(){
return new Bar();
}
}
採用協變式返回值風格,將Bar修改為
public class Bar extends Foo {
public Bar create(){
return new Bar();
}
}
要小心你類庫的客戶端。
㈥ 急求一個java泛型寫法
泛型可以用"<T>"代表,任意類型的。
解釋: 「<T>」是泛型的默認值,可以被任意類型所代替,如:
List<String> list = new ArayList<String>();這個就定義了一個String類型的」泛型「數組,那麼T的類型就是字元串。
List<T> list = new ArayList<T>();
可以賦值給list:list.add("StringBatch");
可以獲取到list的值:list.get(0),結果就是」StringBatch「;
這個時候T的類型也是String。也就是說T是動態的,可以被任意指定類型。
㈦ java泛型問題
泛型只針對沒有實例出來的對象。
如:Set<String> set=new TreeSet<String>();
new TreeSet<String>()是實例化之前加的泛型。
ps.keySet()是對象,對象可以加轉型,但不能加泛型。
㈧ 麻煩給我詳細介紹下JAVA中用了泛型的好處,是不是用了泛型就可以不用NEW去創建對象了
1. 泛型的卻很有用, 如果跟反射可以配合用好, 普通企業應用得代碼量可以減少非常多.
2. python之類type inference語言比java更適合做這種template抽象
舉個例子: 企業程序大部分程序是實體信息管理得, 真正涉及交易清算得只有極小一部分
對於所有得實體管理, 基本上都是增,刪, 改, 查看, 查找等等幾個功能
我現在用了幾個框架, 經典結合方式:webwork+spring+hibernate, 分析如下:
hibernate O/R Mapping沒什麼花頭, 為每個實體管理都要寫一個映射類, 一般跟實體得數目差不多, 有時候為了避免關聯, 建幾個視圖映射會多點. But, 這個能用hibernate自己寫得工具生成, 維護一個java類文件, 以及裡面得meta description.
或者自己寫個程序生成( 給定數據源,, 輸出java類文件, 以及hbm .xml文件 ), 這個只需要維護sql語句就興了, 我通常這么做.
1. 不用泛型, 不用反射
spring層要寫5個service(假設一個實體對應一個service), 分別實現讀實體, 加實體, 刪實體, 根據條件查找實體List這些功能.
要寫5個不同得實體類得Action, 然後Action裡面實現校驗, 增加, 修改, 刪除, 列表得功能, 每一個類要實現得功能同樣也類似.
這些都沒多少重構實質性得體現, 好處不明顯, 可以提取得公共代碼非常少.
2. 不用泛型, 用反射
在 1 得基礎之上, 可以重構提取出一個公共service, 將分散在5個service得公共代碼kill掉. 多出一個根據實體類參數操作得公共類, 但是類得數量增加一個.
同樣Action也可以提出一個baseaction來
重構消去了一部分代碼, 每個service得代碼還是存在重復代碼, 但是這個還可以通過反射合成一個service, 不過掉用者要用反射調用
對於action因為需要配置, 除非webwork支持類型得注入, 否則, 不好消去.
如果webwork spring 支持實體類型得注入, 那麼到此可以結束, 只寫一套邏輯, 代碼也沒有重復, 由於精力有限, 沒有去深入研究spring AOP跟webwork 得inteceptor機制, 不知道支持不支持.
3.用泛型, 用反射
在1得基礎上引入泛型, 寫一個泛型service, 寫一個泛型webwork action , 也沒有研究過xwork中泛型對象是怎麼配置產生得, 如果支持配置, 只需要寫一個action即可, 如果不支持, 需要為每個實體類寫一個webwork action
同樣頁面也是這個樣子
但是, java得泛型用了一把, 不說對很多東西支持得很差, 光寫法上來看, 跟C++ template一致, 仍然是一大堆得<>, 讓初學者吐血, 變成高手炫耀得奇技淫巧. 比起python之類動態類型安全語言type inference機制相差太遠, java泛型還有很長得路要走, 不過靜態語言走到type inference上來得話, 編譯類型檢查得好處就沒了, 所以java這方面永遠不可能~~~
通過上面考慮, 我覺得python更適合web管理程序, 對於很多entity management得程序完全可以寫出一個服務所有, 僅僅是O?Rmapping就沒辦法省. 不知道python有沒有類似java得反射, 如果有得話會更好, 可以寫出更通用, 量更少得代碼. 相信ruby on rails成功也有這方面得原因(我沒用過ruby, 錯了也別罵我), 畢竟代碼越少越好, 尤其是重復代碼
我得主張是kill掉all重復代碼, 只要能抽象統一得代碼重復就是罪過, 哈哈, 相信用python可以把這個做得更好, 越來越喜歡python語言得特性了, 可惜還沒拿他來做過web程序
㈨ java泛型的高級應用
在上面的例子中,由於沒有限制class GenericsFoo<T>類型持有者T的范圍,實際上這里的限定類型相當於Object,這和「Object泛型」實質是一樣的。限制比如我們要限制T為集合介面類型。只需要這么做:
class GenericsFoo<T extends Collection>,這樣類中的泛型T只能是Collection介面的實現類,傳入非Collection介面編譯會出錯。
注意:<T extends Collection>這里的限定使用關鍵字extends,後面可以是類也可以是介面。但這里的extends已經不是繼承的含義了,應該理解為T類型是實現Collection介面的類型,或者T是繼承了XX類的類型。
下面繼續對上面的例子改進,我只要實現了集合介面的類型: publicclassCollectionGenFoo<TextendsCollection>{privateTx;publicCollectionGenFoo(Tx){this.x=x;}publicTgetX(){returnx;}publicvoidsetX(Tx){this.x=x;}}實例化的時候可以這么寫: {publicstaticvoidmain(Stringargs[]){CollectionGenFoo<ArrayList>listFoo=null;listFoo=newCollectionGenFoo<ArrayList>(newArrayList());//出錯了,不讓這么干。//原來作者寫的這個地方有誤,需要將listFoo改為listFoo1//需要將CollectionGenFoo<Collection>改為CollectionGenFoo<ArrayList>//CollectionGenFoo<Collection>listFoo1=null;//listFoo1=newCollectionGenFoo<ArrayList>(newArrayList());System.out.println("實例化成功!");}}當前看到的這個寫法是可以編譯通過,並運行成功。可是注釋掉的兩行加上就出錯了,因為<T extends Collection>這么定義類型的時候,就限定了構造此類實例的時候T是確定的一個類型,這個類型實現了Collection介面,但是實現 Collection介面的類很多很多,如果針對每一種都要寫出具體的子類類型,那也太麻煩了,我乾脆還不如用Object通用一下。別急,泛型針對這種情況還有更好的解決方案,那就是「通配符泛型」。 雖然Java泛型簡單的用 extends 統一的表示了原有的 extends 和 implements 的概念,但仍要遵循應用的體系,Java 只能繼承一個類,但可以實現多個介面,所以你的某個類型需要用 extends 限定,且有多種類型的時候,只能存在一個是類,並且類寫在第一位,介面列在後面,也就是:
<T extends SomeClass & interface1 & interface2 & interface3>
這里的例子僅演示了泛型方法的類型限定,對於泛型類中類型參數的限制用完全一樣的規則,只是加在類聲明的頭部,如: publicclassDemo<TextendsComparable&Serializable>{//T類型就可以用Comparable聲明的方法和Seriablizable所擁有的特性了} 為了解決類型被限制死了不能動態根據實例來確定的缺點,引入了「通配符泛型」,針對上面的例子,使用通配泛型格式為<? extends Collection>,「?」代表未知類型,這個類型是實現Collection介面。那麼上面實現的方式可以寫為: {publicstaticvoidmain(Stringargs[]){CollectionGenFoo<ArrayList>listFoo=null;listFoo=newCollectionGenFoo<ArrayList>(newArrayList());//出錯了,不讓這么干。//原來作者寫的這個地方有誤,需要將listFoo改為listFoo1//CollectionGenFoo<Collection>listFoo1=null;//listFoo1=newCollectionGenFoo<ArrayList>(newArrayList());System.out.println("實例化成功!");}}注意:
1、如果只指定了<?>,而沒有extends,則默認是允許Object及其下的任何Java類了。也就是任意類。
2、通配符泛型不單可以向下限制,如<? extends Collection>,還可以向上限制,如<? super Double>,表示類型只能接受Double及其上層父類類型,如Number、Object類型的實例。
3、泛型類定義可以有多個泛型參數,中間用逗號隔開,還可以定義泛型介面,泛型方法。這些都與泛型類中泛型的使用規則類似。