泛型的使用

[TOC]

泛型的使用

集合沒(méi)有泛型的時(shí)候,集合存放數(shù)據(jù)時(shí)都會(huì)丟失原來(lái)的類型,全部改為Object。這樣可以獲得良好的通用性。但是取出的時(shí)候,就需要做類型轉(zhuǎn)換,如果類型寫錯(cuò)了,轉(zhuǎn)換就會(huì)出現(xiàn)異常。為了有更好的安全性和可讀性,Java在JDK1.5的時(shí)候加入了泛型。

泛型的應(yīng)用非常重要,在教學(xué)中,務(wù)必讓學(xué)生學(xué)會(huì)基本的使用:

  1. 在集合(List、Set、Map)上使用泛型
  2. 在通用類或者接口上使用泛型
  3. 在方法上使用泛型
  4. 明白什么是泛型擦除

泛型的作用

使用泛型機(jī)制編寫的程序代碼要比那些雜亂地使用Object 變量 ,然后再進(jìn)行強(qiáng)制類型轉(zhuǎn)換的代碼具有更好的安全性和可讀性 。泛型對(duì)于集合類尤其有用 ,例如 ,ArrayList就是一個(gè)無(wú)處不在的集合類

沒(méi)有泛型的代碼:

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ù)類型只有字符串怎么辦 ?沒(méi)有代碼的情況下代碼是這樣的:

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ù)就是一個(gè)浮點(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-f584f5-1623674227005)]

發(fā)生了類型轉(zhuǎn)換錯(cuò)誤~~

為了解決這類問(wèn)題,使用泛型是不二之選。通過(guò)泛型可以限制集合中數(shù)據(jù)的類型,只有符合的類型才能放到集合中

格式

在聲明的集合類型后面跟上一對(duì)尖括號(hào),實(shí)現(xiàn)的類型構(gòu)造器的小括號(hào)前面跟上一對(duì)尖括號(hào)。里面寫上需要存放的數(shù)據(jù)類型

[圖片上傳失敗...(image-e98948-1623674227005)]

可以看到,在定義泛型后,添加 123.123 浮點(diǎn)數(shù)時(shí)編譯器就開(kāi)始報(bào)錯(cuò)了。在使用泛型后,代碼也不需要做類型強(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());
}

簡(jiǎn)單的泛型類

在定義類 Pair 時(shí),在類名后跟上 <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=孔明]
}

使用泛型,就如同將原來(lái)類定義的 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ù)一樣。就像一個(gè)方法有形式參數(shù)(formal value parameters)來(lái)描述它操作的參數(shù)的種類一樣,一個(gè)泛型聲明也有形式類型參數(shù)(formal type parameters)。當(dāng)一個(gè)方法被調(diào)用,實(shí)參(actual arguments)替換形參,方法體被執(zhí)行。當(dāng)一個(gè)泛型聲明被調(diào)用,實(shí)際類型參數(shù)(actual type arguments)取代形式類型參數(shù)。

泛型方法

在使用前,我們先明確一下泛型的各種通配符:

  1. T:type 數(shù)據(jù)類型
  2. E:element 元素
  3. K:key 鍵
  4. V:value 值
  5. ?:未知類型

示例:

class ArrayAlg {

    public static <T> T getMiddle(T... a) {
        return a[a.length / 2];
    }
}

測(cè)試

同一個(gè)類的方法,使用不同類型的數(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) );

有限制的通配符

考慮一個(gè)簡(jiǎn)單的畫圖程序,它可以用來(lái)畫各種形狀,比如矩形和圓形。 為了在程序中表示這些形狀,你可以定義下面的類繼承結(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() {
        // ... 
    }
}

所有的圖形通常都有很多個(gè)形狀。假定它們用一個(gè) list 來(lái)表示,Canvas 里有一個(gè)方法來(lái)畫出所有的形狀會(huì)比較方便

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 來(lái)調(diào)用。它不能,比如說(shuō)對(duì) List<Circle>來(lái)調(diào)用。 這很不幸, 因?yàn)檫@個(gè)方法所作的只是從這個(gè) list 讀取 shape,因此它應(yīng)該也能對(duì) List<Circle>調(diào)用。我們真正要的是這個(gè)方法能夠接受一個(gè)任意種類的 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>?,F(xiàn)在drawAll()可以接受任何 Shape 的子類的 List,所以我們可以對(duì) List<Circle>進(jìn)行調(diào)用

List<? extends Shape>是有限制通配符的一個(gè)例子。這里?代表一個(gè)未知的類型,就像我們前面看到的通配符一樣。但是,在這里,我們知道這個(gè)未知的類型實(shí)際上是Shape 的一個(gè)子類(它可以是 Shape本身或者 Shape 的子類而不必是 extends 自 Shape)。我們說(shuō) Shape是這個(gè)通配符的上限(upper bound)。
像平常一樣,要得到使用通配符的靈活性有些代價(jià)。這個(gè)代價(jià)是,現(xiàn)在向 shapes 中寫入是非法的。比如下面的代碼是不允許的

public void addRectangle(List<? extends Shape> shapes) { 
   //   編譯時(shí)會(huì)報(bào)錯(cuò) 
   shapes.add( new Rectangle()); 
}

shapes.add 的第二個(gè)參數(shù)類型是? extends Shape ——一個(gè) Shape 未知的子類。因此我們不知道這個(gè)類型是什么,我們不知道它是不是 Rectangle 的父類;它可能是也可能不是一個(gè)父類,所以這里傳遞一個(gè) 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-63351c-1623674227005)]

可以看到,程序類型的轉(zhuǎn)換異常,但是編譯器卻沒(méi)有報(bào)錯(cuò)

這樣的原因是,泛型是通過(guò) java 編譯器的稱為擦除(erasure)的前端處理來(lái)實(shí)現(xiàn)的。你可以(基本上就是)把它認(rèn)為是一個(gè)從源碼到源碼的轉(zhuǎn)換,它把泛型版本的 loophole()轉(zhuǎn)換成非泛型版本。 結(jié)果是,java 虛擬機(jī)的類型安全和穩(wěn)定性決不能冒險(xiǎn),即使在又unchecked warning 的情況下。

擦除去掉了所有的泛型類型信息。所有在尖括號(hào)之間類型信息都被扔掉了,因此,比如說(shuō)一個(gè) List<String>類型被轉(zhuǎn)換為 List。所有對(duì)類型變量的引用被替換成類的型變量的上限(通常是 Object)

Java 的泛型支持僅在語(yǔ)法級(jí)別

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 泛型的使用泛型的作用簡(jiǎn)單的泛型類泛型方法有限制的通配符擦除和翻譯 泛型的使用 集合沒(méi)有泛型的時(shí)候,集合存放數(shù)據(jù)時(shí)都...
    清風(fēng)A1閱讀 519評(píng)論 0 0
  • 集合沒(méi)有泛型的時(shí)候,集合存放數(shù)據(jù)時(shí)都會(huì)丟失原來(lái)的類型,全部改為Object。這樣可以獲得良好的通用性。但是取出的時(shí)...
    秋北_55a3閱讀 714評(píng)論 0 1
  • [TOC] 泛型的使用 集合沒(méi)有泛型的時(shí)候,集合存放數(shù)據(jù)時(shí)都會(huì)丟失原來(lái)的類型,全部改為Object。這樣可以獲得良...
    洪孝崢閱讀 83評(píng)論 0 0
  • 泛型是Java中一項(xiàng)十分重要的特性,在Java 5版本被引入,在日常的編程過(guò)程中,有很多依賴泛型的場(chǎng)景,尤其是在集...
    zhipingChen閱讀 3,088評(píng)論 1 6
  • 本部分主要介紹:Java 的泛型、泛型通配符使用的規(guī)則和注意事項(xiàng) 泛型總結(jié) 泛型是 JDK 1.5 出現(xiàn)的技術(shù),是...
    sshentree閱讀 922評(píng)論 0 2

友情鏈接更多精彩內(nèi)容