1. 程式人生 > >Java核心技術第八章-泛型

Java核心技術第八章-泛型

摘要

本文根據《Java核心技術 卷一》一書的第八章總結而成,部分文章摘抄書內,作為個人筆記。
文章不會過於深入,望讀者參考便好。

為什麼要使用泛型程式設計

泛型程式設計(Generic programming) 意味著編寫的程式碼可以被很多不同型別的物件所重用。

型別引數的好處

在沒有泛型類之前,ArrayList類只維護一個Object引用的陣列:

public class ArrayList {
   private Object[] elementData; // 用於存放資料
   public Object get(int i) { . . . }
   public void add(Object o) { . . . }
     ...
}

問題

1.獲取一個值時必須進行強制型別轉換
2.這裡沒有錯誤檢査。可以向陣列列表中新增任何類的物件,如果陣列的型別不一致,將 get 的結果進行強制強制型別,就會錯誤。

泛型提供了一個更好的解決方案: 型別引數:

ArrayList<String> array = new ArrayList<String>():

利用型別引數的資訊,我們就可以在新增資料的時候保持型別統一,呼叫get方法時候也不需要進行強制型別轉換,因為我們在初始化的時候就定義了型別,編譯器識別返回值的型別就會幫我們轉換該型別。

定義一個簡單泛型類

public class Pair<T> {
    private T num;

    public Pair(T num) { this.num = num; }
    public T getNum() { return num; }
    public void setNum(T num) { this.num = num; }
}

我們可以看到,在Pair類名後面添加了一個,這個是泛型類的型別變數,而且還可以有多個型別變數,如

public class Pair<T,U> {
    ...
}

如果我們例項化Pair類,例如:

new Pair<String>;

那麼我們就可以把上述的Pair類想象成如下:

public class Pair<String> {
    private String num;

    public Pair(String num) { this.num = num; }
    public String getNum() { return num; }
    public void setNum(String num) { this.num = num; }
}

是不是很簡單呢?在Java庫中,使用變數E表示集合的元素型別,K和V分別表示表的關鍵字與值的型別,T、U、S表示任意型別。

泛型方法

定義一個帶有型別引數的方法

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

可以看到型別變數(< T >)放在修飾符( public static )的後面,返回型別(T)的前面。泛型方法可以定義在普通類或泛型類中。

型別變數的限定

如果我們需要對型別變數加以約束,例如:傳入的變數必須實現Comparable介面,因為需要該變數呼叫compareTo的方法。這樣我們就可以使用extends關鍵字對變數進行限定。

    public <T extends Comparable> T max(T a) {
        a.compareTo(...);
        ...
    }

無論變數需要限定為繼承某個類或者實現某個介面,都是使用extends關鍵字進行限定。

泛型程式碼和虛擬機器

型別擦除

無論我們在程式碼中怎麼定義一個泛型類、泛型方法,都提供了一個相應的原始型別。原始型別的名字就是刪去型別引數後的泛型類姓名。如 <T> 的原始型別為 Object<T extends MyClass>的原始型別為MyClass
程式碼就像下面這樣:

public class Pair<T> {
    private T property;

    public Pair(T property) {
        this.property = property;
    }
}

型別擦除後:

public class Pair<Object> {
    private Object property;

    public Pair(Object property) {
        this.property = property;
    }
}

翻譯泛型表示式

如果擦除返回型別,編譯器會插入強制型別轉換,就像下面這樣:

Pair<Employee> buddies = . .
Employee buddy = buddies.getFirst();

擦除getFirst的返回型別後將返回Object型別,但是編譯器將自動幫我們強制型別轉換為Employee。
所以:編譯器把這個方法執行操作分為兩條指令:

對原始方法Pair.getFirst的呼叫
將返回的Object型別強制轉換為Employee型別

小節總結:

虛擬機器中沒有泛型,只有普通的類和方法
所有的型別引數都用他們的限定型別替換
為保持型別安全性,必要時插入強制型別轉換
橋方法被合成來保持多型(本文沒有講到,不過橋方法可以忽略,Java編寫不規範才會有橋方法生成)

約束與侷限性

不能用基本型別例項化型別引數

不可以用八大基本資料型別去例項化型別引數,你也沒見過ArrayList<int>這樣的程式碼吧。只有ArrayList<Integer>。原因是因為基本資料型別是不屬於Object的。所以只能用他們的包裝型別替換。

執行時型別查詢只適用於原始型別

所有的型別查詢只產生原始型別,因為在虛擬機器沒有所謂的泛型型別。
例如:

    Pair<String> pair = new Pair<String>("johnson");
    if (pair instanceof Pair<String>) {} // Error
    if (pair instanceof Pair<T>) {} // Error
    if (pair instanceof Pair) {} // Pass

不能建立引數化型別的陣列

    Pair<String>[] pairs = new Pair<String>[10]; //error

為什麼不能這樣定義呢?因pairs的型別是Pair[],可以轉換為Object[],如果試圖儲存其他型別的元素,就會丟擲ArrayStoreException異常,

    pairs[0] = 10L; //拋異常

總之一句話,不嚴謹。所以不能建立引數化型別的陣列。

不能例項化型別變數

不能使用 new T(...)、new T[...] 或 T.class。因為型別擦除後,T將變成Object,而且我們肯定不是希望例項化Object。
不過在Java8之後,我們可以使用Supplier<T>介面實現,這是一個函式式介面,表示一個無引數而且有返回型別為T的函式:

public class Pair<T> {
    private T first;
    private T second;

    private Pair(T first, T second) {
        this.first = first;
        this.second = second;
    }

    public static <T> Pair<T> makePair(Supplier<T> constr) {
        return new Pair<>(constr.get(), constr.get());
    }
    
    public static void main(String[] args) {
        Pair<String> pair = Pair.makePair(String::new);
    }
}

泛型類中的靜態上下文中型別變數無效

不能在靜態域或方法中引用型別變數。例如:

public class Pair<T> {
    private static T instance; //Error

    public static T getInstance() { //Error
        if (instance == null) 
            instance = new Pair<T>();
        return instance;
    }
}

因為類中的型別變數(<T>)是在物件中的作用域有效,而不是在類中的作用域有效。如果要使用泛型方法,可以參照文章上面的泛型方法哦~

不能丟擲或捕獲泛型類的例項

即不能丟擲也不能捕獲泛型類的物件,甚至擴充套件Throwable都是不合法的:

public class Pair<String> extend Exceotion {} //Error
public static <T extends Throwable> void doWork(Class<T> t) {
    try {
        ...
    } catch (T e) { //Error
        ...
    }
}

但是在丟擲異常後對異常使用型別變數是允許的(個人感覺沒見過這樣的程式碼)。

public static <T extends Throwable> void doWork(Class<T> t) throw T { //Pass
    try {
        ...
    } catch (Exception e) {
        throw e;
    }
}

泛型型別的繼承規則

Manager類繼承Employee類。但是Pair<Employee>Pair<Manager>是沒有關聯的。就像下面的程式碼,就會提示報錯,傳遞失敗:

        Pair<Manager> managerPair = new Pair<Manager>();
        Pair<Employee> employeePair = managerPair; //Error

萬用字元型別

萬用字元概念

萬用字元型別中,允許型別引數變化,使用 ? 標識萬用字元型別:

Pair<? extends Employee>

若Pair類如下

public class Pair<T> {
    private T object;

    public void setObject(T object) {
        this.object = object;
    }
}

那麼使用萬用字元可以解決泛型型別的繼承規則問題,如:

        Pair<Manager> managerPair = new Pair<Manager>();
        Pair<? extends Employee> employeePair = managerPair; //Pass
        Manager manager = new Manager();
        employeePair.setObject(manager); //Error

使用<? extends Employee>,編譯器只知道employeePair 是Employee的子類,但是不清楚具體是什麼子類,所以最後一步employeePair.setObject(manager)不能執行。

萬用字元的超型別限定

萬用字元還有一個附加的能力,就是可以指定一個超型別限定,如:

public class Pair<T> [
    ...
    public static void salary(Pair<? super Manager> result) {
        //...
    }
}

<? super Manager>這個萬用字元為Manager的所有超型別(包含Manger),例如:

        Pair<Manager> managerPair = new Pair<Manager>();
        Pair<Employee> employeePair = new Pair<Employee>();

        Pair.salary(managerPair); //Pass
        Pair.salary(employeePair); //Pass

        // 假如 ManagerChild為Manager子類
        Pair<ManagerChild> managerChildPair = new Pair<ManagerChild>();
        Pair.salary(managerChildPair); //Error

無限定萬用字元

無限定萬用字元,如:Pair<?>,當我們不需要理會他的實際型別時候,就可以使用無限定萬用字元,上程式碼:

    public static boolean hasNull(Pair<?> pair) {
        return pair.getObject() == null;
    }

說實話,萬用字元搞得我頭昏腦脹的,反覆不斷地看文章,才開始慢慢看懂,我太難了。。。


文章到這裡就結束啦,不知道各位小夥伴看懂了沒,沒看懂的話可能是我的功底和文章寫作能力還有待提高,小夥伴們也可以去看一下《Java核心技術 卷一》這本書呢,感覺還是挺不錯的。最近把這本書撿起來看也是發現基礎是非常重要的,先把基礎沉澱好了,再學習其他的技術點也會更容易入手,也會知其然知其所然。最近非常喜歡的一句話,送給大家:“萬丈高樓平地起,勿在浮沙築高臺”。

個人部落格網址: https://colablog.cn/

如果我的文章幫助到您,可以關注我的微信公眾號,第一時間分享文章給您

相關推薦

Java核心技術-

摘要 本文根據《Java核心技術 卷一》一書的第八章總結而成,部分文章摘抄書內,作為個人筆記。 文章不會過於深入,望讀者參考便好。 為什麼要使用泛型程式設計 泛型程式設計(Generic programming) 意味著編寫的程式碼可以被很多不同型別的物件所重用。 型別引數的好處 在沒有泛型類之前,Array

學習筆記之《Java核心技術卷I》---- 程式設計

泛型類的定義格式:class Pair<T>{ } 普通類中泛型方法的定義:public static <T> T getMiddle(T... a){ return a[a.length / 2]; } 呼叫方法時,可以使用:ClassName.getMi

程序設計

ali 編譯 可變 args ppr 泛型 會有 運行時 amp 為什麽要使用泛型 泛型的魅力是使程序有更好的安全性和可讀性 泛型類型限定 使用extends關鍵字限定泛型必須是某個接口的子類, 可以有多個類型如 T extends Comparable &

Java核心技術--(5)

控制流程 條件語句+迴圈結構 控制流程 條件語句 迴圈語句 switch語句(判斷多個值) 塊 用 { }括起來的若干條Java語句 塊可巢狀在另一個塊中 public static void main(String[] args)

java核心技術(8)程式設計

泛型程式設計(Generic programming) 意味著編寫的程式碼可以被很多不同型別的物件所重用。 比雜亂的使用Object物件然後再進行強制型別轉換具有更好的可讀性和安全性。尤其對於集合。 public static <T extends Comparab

Java核心技術-- 繼承(更新中)10/9

類、超類和子類 經理類與普通僱員類有很多相同之處,但還有一些差別。 經理在完成本職任務不僅可以獲得工資,還獲得獎金。而普通僱員只能獲取工資。故而,可以重用Employee類中已編寫的部分部分,還可在其中在增加一些新的功能。 每個經理都是一個僱員,是 is a

java核心技術 物件與類

4.1 面向物件概述 面向物件層序設計(OOP),Java是完全面向物件。 面向物件的程式是由物件組成的,每個物件包含對使用者公開的特定功能部分和隱藏的實現部分。 面向物件將資料放在第一位。 4.1.1 類 類(class)是構造物件的模板或藍圖。

Java核心技術12(2)

12.3    泛型方法     前面介紹瞭如何定義一個泛型類,實際上,還可以定義一個帶有型別引數的簡單方法.class ArrayAlg { public static <T> T getMiddle(T...a) { retu

java核心技術2總結

        本章主要介紹Java程式設計環境,下面一節一節的記錄:       2.1 安裝java工具箱(JDK):           2.1.1, 下載JDK:               這一節主要介紹了以下知識點:               (2)一些ja

Java核心技術6(1)

第6章   介面與內部類     首先介紹介面(interface)技術,這種技術主要用來描述類具有什麼功能,而並不給出每個功能的具體實現.一個類可以實現一個或多個介面,並在需要介面的地方,隨時使用實現了相應介面的物件.瞭解介面以後,再繼續看一下克隆物件.物件的克隆是指建立

《Effective Java5

增加 規範 註釋 line 元素 eric 有關 img shu 第23條:請不要在新代碼中使用原生態類型 聲明中具有一個或者多個類型參數( type parameter)的類或者接口,就是泛型(generic)類或者接口。 每種泛型定義一組參數化的類型(paramet

Java四天——核心技術(2)

繼續第三章的學習。。。 運算子 運算子+、-、*、/表示加、減、乘、除運算 %求餘操作 /運算 兩個運算元都是整數時,表示整數除法;否則,表示浮點數除法 例:15/2=7 15%2=1 15.0/2=7.5 整數被0除會產生

Java程式設計思想 :多

OOP語言中,多型是封裝、繼承之後的第三種基本特徵。 封裝:通過合併特徵和行為來建立新的資料型別,“實現隱藏”通過細節“私有化”把介面和實現分離 繼承:以複用介面方式從已有型別用extends關鍵字建立新型別,並允許向上轉型 多型:消除型別之間的耦合關係(分離做什麼和怎麼做),基

Java程式設計思想(三)15-

目錄: 泛型(generics)的概念是Java SE5的重大變化之一。泛型實現了引數化型別(parameterized types)的概念,使程式碼可以應用於多種型別。“泛型”這個術語的意思是:“適用於許多許多的型別”。 1 泛型方法   泛型方法與其所在的類

thinking in java “謎題”

謎題一,缺陷:域,靜態方法(構造器也是靜態方法,不過static宣告是隱式的),“覆蓋”私有方法 public class Shape { public int r = 1; public int getR() { return r; } pub

Java四天——核心技術(4)

字串 String類 雙引號括起來的 String s = “Enjoy!” ""表示空串 子串 String ss = s.substring(0,2); ss=“En” substring(a,b) 子串位

Java程式設計思想 讀書筆記-多

多型是面向物件三種基本特徵之一(繼承,抽象,多型) 多型存在的意義是什麼?自己的理解,多型消除了型別的耦合。如果一個方法的引數是基類物件,如果沒有多型,那麼我們需要判斷傳入的引數到底是基類的哪個衍生類,然後還掉統一方法名的方法,程式碼比較冗餘,和類的耦合度很高。有了多型,我

Java多執行緒程式設計核心技術--2 物件及變數的併發訪問

2.1 synchronized同步方法 方法中的變數不存在非執行緒安全問題,永遠都是執行緒安全的。這是方法內部的變數私有特性造成的。 2.1.2 例項變數非執行緒安全 使用多個執行緒併發訪問PrivateNum類中的addI方法。 publ

Java - 尚學堂常用類(將輸入的string類的值轉為整數、浮點、日期類

sco ger class log print string類 cep ase oid import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDate

編寫高質量程式碼:改善Java程式的151個建議(7:和反射___建議93~97)

我們最大的弱點在於放棄。成功的必然之路就是不斷的重來一次。 --達爾文 建議93:Java的泛型是可以擦除的 建議94