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 Java》第5章 泛型
增加 規範 註釋 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