Java5增加泛型支持在很大程度上都是為了讓集合能記住其元素的數(shù)據(jù)類型。在沒(méi)有泛型之前明未,一旦把一個(gè)對(duì)象放進(jìn)集合中,集合就會(huì)忘記對(duì)象的類型,把所有的對(duì)象當(dāng)成Object類型處理萍鲸。當(dāng)程序從集合中取出對(duì)象后,就需要進(jìn)行強(qiáng)制類型轉(zhuǎn)換擦俐。而泛型可以讓集合記住其元素的數(shù)據(jù)類型脊阴。
一、認(rèn)識(shí)泛型
java7之前的語(yǔ)法:
List<String> strList = new ArrayList<String>();
Map<String,String> scores = new HashMap<String,String>();
從Java7開(kāi)始使用菱形語(yǔ)法:
List<String> strList = new ArrayList<>()
二蚯瞧、深入泛型
泛型:允許在定義類嘿期、接口、方法時(shí)使用類型形參埋合,這個(gè)類型形參將在聲明變量备徐、創(chuàng)建對(duì)象、調(diào)用方法時(shí)動(dòng)態(tài)地指定甚颂。Java5為集合框架中的全部接口和類增加了泛型支持蜜猾。
定義泛型接口、類
//定義接口時(shí)指定類型形參
public interface List<E>{
void add(E x);
Iterator<E> iterator();
}
public interface Iterator<E>{
E next();
boolean hasNext();
}
public interface Map<K,V>{
Set<K> keySet();
V put(K key, V value){
}
}
Iterator<E>
振诬、Set<K>
表面他們是一種特殊的數(shù)據(jù)類型蹭睡,是一種和Iterator
、Set
不同的數(shù)據(jù)類型赶么,可以認(rèn)為是他們的子類肩豁。
包含泛型聲明的類型可以在定義變量、創(chuàng)建對(duì)象時(shí)傳入一個(gè)類型參數(shù)辫呻,從而可以動(dòng)態(tài)地生成無(wú)數(shù)多個(gè)邏輯上的子類清钥,但這種子類在物理上并不存在。
創(chuàng)建帶泛型聲明的自定義類放闺,為該類定義構(gòu)造器時(shí)循捺,構(gòu)造器名還是原來(lái)的類名,不要增加泛型聲明雄人。
從泛型類派生子類
//錯(cuò)誤的
public class A extends Apple<T>{}
//正確的
public class A extends Apple<String>{}
//會(huì)出現(xiàn)警告
public class A extends Apple{}
并不存在泛型類
//l1.getClass()==l2.getClass()
List<String> l1 = new ArrayList<>();
List<String> l1 = new ArrayList<>();
不管為泛型形參傳入哪一種類型的實(shí)參从橘,他們依然被當(dāng)作同一個(gè)類處理,在內(nèi)存中也只占用一塊內(nèi)存空間础钠,因此在靜態(tài)方法恰力、靜態(tài)初始化塊或者靜態(tài)變量的聲明和初始化中不允許使用類型形參。
public class R<T>{
//錯(cuò)誤旗吁,不能在靜態(tài)變量聲明中使用類型形參
static T info;
T age;
public void foo(T msg){}
//錯(cuò)誤踩萎,不能在靜態(tài)方法聲明中使用類型形參
public static void var(T msg){}
}
java.util.Collection<String> cs = new java.util.ArrayList<>();
//錯(cuò)誤,并不會(huì)生成泛型類
if (cs instanceof java.util.ArrayList<String>){}
三很钓、類型通配符
//會(huì)有泛型警告
public void test(List c){
for(int i=0; i<c.size(); i++){
System.out.println(c.get(i));
}
}
改成
//會(huì)有泛型警告
public void test(List<Object> c){
for(int i=0; i<c.size(); i++){
System.out.println(c.get(i));
}
}
List<String> str = new ArrayList<>();
test(str);
上面代碼產(chǎn)生異常香府。
無(wú)法將Test中的test(java.util.List<java.lang.Object>)應(yīng)用于(java.util.List<java.lang.String>)
如果Foo
是Bar
的子類型(子類或者子接口)董栽,而G
是一個(gè)具有泛型聲明的類或接口,G<Foo>
并不是G<Bar>
的子類型企孩。
數(shù)組與泛型不同锭碳,Foo[]
是bar[]
的子類型。
使用類型通配符
//其類型為Object
public void test(List<?> c){
for(int i=0; i<c.size(); i++){
System.out.println(c.get(i));
}
}
c中包含的是Object
勿璃。
這種帶通配符的List僅表明它是各種泛型List的父類擒抛,并不能把元素加入進(jìn)去。
List<?> c = new ArrayList<String>();
//編譯錯(cuò)誤
c.add(new Object());
c中放的類型是Object
补疑,而add的參數(shù)是E
類的對(duì)象或者其子類的對(duì)象歧沪。該例中不知道E
是什么類型,產(chǎn)生編譯錯(cuò)誤莲组。null
是所有引用類型的實(shí)例诊胞,它可以添加進(jìn)去。
get返回值是一個(gè)未知類型锹杈,可以賦值給Object
類型的變量厢钧。
設(shè)定類型通配符的上限
public abstract class Shape{
public abstract void draw();
}
public class Circle extends Shape{
public void draw(Canvas c){
System.out.println(c+"圓");
}
}
public class Rectangle extends Shape{
public void draw(Canvas c){
System.out.println(c+"長(zhǎng)方形");
}
}
public class Canvas{
public void drawAll(List<? extends Shape> shapes){
for (Shape shape:shapes){
shape.draw(this);
}
}
}
List<Circle> c = new ArrayList<>();
Canvas cv = new Canvas();
c.drawAll(c);
以上程序會(huì)將List<Circle>
對(duì)象當(dāng)成List<? extends Shape>
使用。List<? extends Shape>
可以表示他們的父類--只要List尖括號(hào)里的類型是Shape
的子類型即可嬉橙。
當(dāng)前程序無(wú)法確定這個(gè)受限制的通配符的具體類型早直。所以不能把Shape
對(duì)象或者其子類型的對(duì)象加入這個(gè)泛型集合中。
設(shè)定類型形參的上限
public class Apple<T extends Number>{
T col;
public static void main(String[] args){
Apple<Integer> a = new Apple<Integer>();
//報(bào)錯(cuò)
Apple<String> b = new Apple<String>();
}
}
程序可以為類型形參設(shè)置多個(gè)上限(至多一個(gè)父類上限市框,可以有多個(gè)接口上限)霞扬。
public class Apple<T extends Number&java.io.Serializable>{
}
和類同時(shí)繼承父類和實(shí)現(xiàn)接口相似,所有的接口上限必須位于類上限之后枫振。
四喻圃、泛型方法
泛型方法的格式如下:
修飾符 <T,S> 返回值類型 方法名(形參){
//方法體
}
import java.util.ArrayList;
import java.util.Collection;
import com.sun.org.apache.xpath.internal.operations.Number;
public class GenericMethodTest{
static <T> void fromArrayToCollection(T[] t, Collection<T> c){
for(T o: t){
c.add(o);
}
}
public static void main(String[] args) {
Object[] oa = new Object[100];
Collection<Object> co = new ArrayList<>();
String[] sa = new String[100];
Collection<String> cs = new ArrayList<>();
//T為Object類型
fromArrayToCollection(sa, co);
Integer[] ia = new Integer();
Collection<Number> cn = new ArrayList<>();
//T為Number類型
fromArrayToCollection(ia, cn);
}
}
泛型方法和類型通配符的區(qū)別
大多數(shù)時(shí)候可以使用泛型方法代替類型通配符。
public interface Collection<E>{
boolean containsAll(Collection<?> c);
boolean addAll(Collection<? extends E> c);
...
}
采用泛型方法實(shí)現(xiàn):
public interface Collection<E>{
<T> boolean containsAll(Collection<T> c);
<T extends E> boolean addAll(Collection<T> c);
...
}
通配符被設(shè)計(jì)用來(lái)支持靈活的子類化的粪滤。
泛型方法允許類型形參被用來(lái)表示方法的一個(gè)或多個(gè)參數(shù)之間的類型依賴關(guān)系斧拍,或者方法返回值與參數(shù)之間的類型依賴關(guān)系。如果沒(méi)有這樣的類型依賴關(guān)系杖小,就不應(yīng)該使用泛型方法肆汹。
如果又需要,也可以同時(shí)使用泛型方法和通配符:
public class Collections{
public static <T> void copy(List<T> dest, List<? extends T> src){}
}
可以使用下面的泛型方法替換:
public static <T, S extends T> void copy(List<T> dest, List<S> src)
S
僅使用了一次予权,其他參數(shù)的類型和方法返回值都不依賴于它昂勉,沒(méi)有存在的必要,可以用通配符替換扫腺。
顯著區(qū)別:
類型通配符既可以在方法簽名中定義形參的類型岗照,也可以用于定義變量的類型;但泛型方法中的類型形參必須在對(duì)應(yīng)方法中顯示聲明。
菱形語(yǔ)法與泛型構(gòu)造器
java允許在構(gòu)造器簽名中聲明類型形參攒至。
class Foo{
public <T> Foo(T t){
...
}
}
class Foo <E>{
public <T> Foo(T t){
}
}
public class GenericTest{
public static void main(String[] args){
Foo<String> s1 = new Foo<>(5);
Foo<String> s2 = new <Integer> Foo<String>(5);
//下面代碼出錯(cuò)
Foo<String> s3 = new <Integer> Foo<>();
}
}
設(shè)定通配符下限
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
int srcSize = src.size();
if (srcSize > dest.size())
throw new IndexOutOfBoundsException("Source does not fit in dest");
if (srcSize < COPY_THRESHOLD ||
(src instanceof RandomAccess && dest instanceof RandomAccess)) {
for (int i=0; i<srcSize; i++)
dest.set(i, src.get(i));
} else {
ListIterator<? super T> di=dest.listIterator();
ListIterator<? extends T> si=src.listIterator();
for (int i=0; i<srcSize; i++) {
di.next();
di.set(si.next());
}
}
}
java8改進(jìn)的類型推斷
- 可通過(guò)調(diào)用方法的上下文來(lái)推斷類型參數(shù)的目標(biāo)類型
- 可在方法調(diào)用鏈中厚者,將推斷得到的類型參數(shù)傳遞到最后一個(gè)方法。
五迫吐、擦除和轉(zhuǎn)換
如果沒(méi)有為一個(gè)泛型類制定實(shí)際的類型參數(shù)库菲,則該類型參數(shù)被成為raw type
,默認(rèn)是聲明該類型參數(shù)時(shí)制定的第一個(gè)上限類型渠抹。
public class Test{
public static void main(String[] args){
List<Integer> li = new ArrayList<>();
List list=li;
//擦除,提示"未經(jīng)檢查的轉(zhuǎn)換"
List<String> li = list;
//運(yùn)行時(shí)異常
System.out.println(li.get(0));
}
}
六闪萄、泛型與數(shù)組
數(shù)組的類型不可以是類型變量梧却,除非是采用通配符的方式。
數(shù)組元素類型不能包含泛型變量或者泛型形參败去,除非是無(wú)上限的泛型通配符放航,但可以聲明元素類型包含泛型變量或泛型形參的數(shù)組。
//不允許
List<String>[] lsa = new List<String>[10];
//允許
List<String>[] lsa = new ArrayList[10];
Object[] oa = (Object[])lsa;
List<integer> li = new ArrayList<>();
li.add(3);
oa[1] = li;
//下面代碼將不會(huì)有警告圆裕,但引發(fā)ClassCastException
String s = lsa[1].get(0);
Object target = lsa[1].get(0);
if(target instanceof String){
String s =(String)target;
}
創(chuàng)建元素類型是泛型變量的數(shù)組對(duì)象也會(huì)導(dǎo)致編譯錯(cuò)誤:
<T> T[] makeArray(Collection<T> coll){
//下面語(yǔ)句出錯(cuò)
return new T[coll.size()];
}