[TOC]
泛型的使用
集合沒有泛型的時候蜘渣,集合存放數(shù)據(jù)時都會丟失原來的類型,全部改為Object杰刽。這樣可以獲得良好的通用性菠发。但是取出的時候,就需要做類型轉(zhuǎn)換贺嫂,如果類型寫錯了滓鸠,轉(zhuǎn)換就會出現(xiàn)異常。為了有更好的安全性和可讀性第喳,Java在JDK1.5的時候加入了泛型糜俗。
泛型的應(yīng)用非常重要,在教學(xué)中曲饱,務(wù)必讓學(xué)生學(xué)會基本的使用:
- 在集合(List悠抹、Set、Map)上使用泛型
- 在通用類或者接口上使用泛型
- 在方法上使用泛型
- 明白什么是泛型擦除
泛型的作用
使用泛型機(jī)制編寫的程序代碼要比那些雜亂地使用Object 變量 扩淀,然后再進(jìn)行強(qiáng)制類型轉(zhuǎn)換的代碼具有更好的安全性和可讀性 楔敌。泛型對于集合類尤其有用 ,例如 驻谆,ArrayList就是一個無處不在的集合類
沒有泛型的代碼:
List list = new ArrayList();
list.add(123);
list.add("abc");
list.add(1>2);
list.add('E');
list.add(890.12);
for (int i = 0; i < list.size(); i++) {
System.out.println( list.get(i) );
}
輸入什么類型卵凑,就輸出什么類型。但是我希望list里面存放的數(shù)據(jù)類型只有字符串怎么辦 柒竞?沒有代碼的情況下代碼是這樣的:
List list = new ArrayList();
list.add("123.A");
list.add("abc.def");
for (int i = 0; i < list.size(); i++) {
String str = (String) list.get(i);
System.out.println( str.split("\\.")[0] ) ;
}
但是list中如果有其他類型呢竭沫?
List list = new ArrayList();
list.add("123.A");
list.add(123.123); // 這行數(shù)據(jù)就是一個浮點(diǎn)型
list.add("abc.def");
for (int i = 0; i < list.size(); i++) {
String str = (String) list.get(i);
System.out.println( str.split("\\.")[0] ) ;
}
[圖片上傳失敗...(image-881058-1621555283938)]
發(fā)生了類型轉(zhuǎn)換錯誤~~
為了解決這類問題往衷,使用泛型是不二之選。通過泛型可以限制集合中數(shù)據(jù)的類型黑忱,只有符合的類型才能放到集合中
格式
在聲明的集合類型后面跟上一對尖括號,實(shí)現(xiàn)的類型構(gòu)造器的小括號前面跟上一對尖括號织盼。里面寫上需要存放的數(shù)據(jù)類型
[圖片上傳失敗...(image-e90249-1621555283938)]
可以看到杨何,在定義泛型后,添加 123.123 浮點(diǎn)數(shù)時編譯器就開始報錯了沥邻。在使用泛型后危虱,代碼也不需要做類型強(qiáng)轉(zhuǎn)了。
List<String> list = new ArrayList<String>();
list.add("123.A");
list.add("abc.def");
for (int i = 0; i < list.size(); i++) {
String str = list.get(i);
System.out.println( str.split("\\.")[0] ) ;
}
Map的演示
Map<String, String> data = new HashMap<>();
data.put("name", "張三");
data.put("age", "11歲");
data.put("sex", "男");
for (Map.Entry<String, String> entry: data.entrySet()) {
System.out.println( entry.getKey() +":"+ entry.getValue());
}
簡單的泛型類
在定義類 Pair 時唐全,在類名后跟上 <T>
public class Pair<T> {
private T first;
private T second;
public Pair() {
first = null;
second = null;
}
public Pair(T first , T second){
this.first = first;
this.second = second;
}
public void setFirst(T newValue) {
first = newValue;
}
public void setSecond(T newValue) {
second = newValue;
}
public T getFirst() {
return first;
}
public T getSecond() {
return second;
}
@Override
public String toString() {
return "Pair [first=" + first + ", second=" + second + "]";
}
}
使用
public static void main(String[] args) {
System.out.println(new Pair<Integer>(1, 2));
//輸出: Pair [first=1, second=2]
System.out.println(new Pair<String>("諸葛", "孔明"));
//輸出: Pair [first=諸葛, second=孔明]
}
使用泛型埃跷,就如同將原來類定義的 T 替換為了指定的類型版本一樣蕊玷,比如:
public class Pair {
private Integer first;
private Integer second;
public Pair() {
first = null;
second = null;
}
public Pair(Integer first , Integer second){
this.first = first;
this.second = second;
}
//....
}
類型參數(shù)就跟在方法或構(gòu)造函數(shù)中普通的參數(shù)一樣。就像一個方法有形式參數(shù)(formal value parameters)來描述它操作的參數(shù)的種類一樣弥雹,一個泛型聲明也有形式類型參數(shù)(formal type parameters)垃帅。當(dāng)一個方法被調(diào)用,實(shí)參(actual arguments)替換形參剪勿,方法體被執(zhí)行贸诚。當(dāng)一個泛型聲明被調(diào)用,實(shí)際類型參數(shù)(actual type arguments)取代形式類型參數(shù)厕吉。
泛型方法
在使用前酱固,我們先明確一下泛型的各種通配符:
- T:type 數(shù)據(jù)類型
- E:element 元素
- K:key 鍵
- V:value 值
- ?:未知類型
示例:
class ArrayAlg {
public static <T> T getMiddle(T... a) {
return a[a.length / 2];
}
}
測試
同一個類的方法,使用不同類型的數(shù)組头朱,都可以正常得到數(shù)據(jù)
String[] array = {"123","345","456" , "567"};
String string = ArrayAlg.getMiddle(array);
System.out.println( string );
Integer[] array2 = {33,44,55,66,77,88};
System.out.println( ArrayAlg.getMiddle(array2) );
有限制的通配符
考慮一個簡單的畫圖程序运悲,它可以用來畫各種形狀,比如矩形和圓形项钮。 為了在程序中表示這些形狀班眯,你可以定義下面的類繼承結(jié)構(gòu):
// 抽象類
public abstract class Shape {
public abstract void draw();
}
// 畫布
public class Canvas {
public void draw(Shape s) {
s.draw();
}
}
/// ---- 抽象類的子類-----------------------
public class Circle extends Shape {
private int x, y, radius;
public void draw() {
// ...
}
}
public class Rectangle extends Shape {
private int x, y, width, height;
public void draw() {
// ...
}
}
所有的圖形通常都有很多個形狀。假定它們用一個 list 來表示烁巫,Canvas 里有一個方法來畫出所有的形狀會比較方便
import java.util.List;
public class Canvas {
public void draw(Shape s) {
s.draw();
}
public void drawAll(List<Shape> shapes) {
for (Shape s : shapes) {
s.draw();
}
}
}
現(xiàn)在署隘,類型規(guī)則導(dǎo)致 drawAll()只能使用 Shape的list 來調(diào)用。它不能程拭,比如說對 List<Circle>來調(diào)用定踱。 這很不幸, 因為這個方法所作的只是從這個 list 讀取 shape恃鞋,因此它應(yīng)該也能對 List<Circle>調(diào)用崖媚。我們真正要的是這個方法能夠接受一個任意種類的 shape
import java.util.List;
public class Canvas {
public void draw(Shape s) {
s.draw();
}
// 注意方法參數(shù)的變化
public void drawAll(List<? extends Shape> shapes) {
for (Shape s : shapes) {
s.draw();
}
}
}
我們把類型 List<Shape> 替換成了 List<? extends Shape>。現(xiàn)在drawAll()可以接受任何 Shape 的子類的 List恤浪,所以我們可以對 List<Circle>進(jìn)行調(diào)用
List<? extends Shape>是有限制通配符的一個例子畅哑。這里?代表一個未知的類型水由,就像我們前面看到的通配符一樣荠呐。但是,在這里砂客,我們知道這個未知的類型實(shí)際上是Shape 的一個子類(它可以是 Shape本身或者 Shape 的子類而不必是 extends 自 Shape)泥张。我們說 Shape是這個通配符的上限(upper bound)。
像平常一樣鞠值,要得到使用通配符的靈活性有些代價媚创。這個代價是,現(xiàn)在向 shapes 中寫入是非法的彤恶。比如下面的代碼是不允許的
public void addRectangle(List<? extends Shape> shapes) {
// 編譯時會報錯
shapes.add( new Rectangle());
}
shapes.add 的第二個參數(shù)類型是? extends Shape ——一個 Shape 未知的子類钞钙。因此我們不知道這個類型是什么鳄橘,我們不知道它是不是 Rectangle 的父類;它可能是也可能不是一個父類芒炼,所以這里傳遞一個 Rectangle 不安全
擦除和翻譯
先看下面的代碼
public static String loophole(Integer x) {
List<String> ys = new LinkedList<String>();
List xs = ys;
xs.add(x);
return ys.iterator().next();
}
public static void main(String[] args) {
loophole(123);
}
[圖片上傳失敗...(image-8e32db-1621555283938)]
可以看到瘫怜,程序類型的轉(zhuǎn)換異常,但是編譯器卻沒有報錯
這樣的原因是本刽,泛型是通過 java 編譯器的稱為擦除(erasure)的前端處理來實(shí)現(xiàn)的鲸湃。你可以(基本上就是)把它認(rèn)為是一個從源碼到源碼的轉(zhuǎn)換,它把泛型版本的 loophole()轉(zhuǎn)換成非泛型版本盅安。 結(jié)果是唤锉,java 虛擬機(jī)的類型安全和穩(wěn)定性決不能冒險,即使在又unchecked warning 的情況下别瞭。
擦除去掉了所有的泛型類型信息。所有在尖括號之間的類型信息都被扔掉了株憾,因此蝙寨,比如說一個 List<String>類型被轉(zhuǎn)換為 List。所有對類型變量的引用被替換成類型變量的上限(通常是 Object)
Java 的泛型支持僅在語法級別