Java中的泛型 --- Java 程式設計思想
我一直都認為泛型是程式語言設計中一個非常基礎,重要的概念,Java 中的泛型到底是怎麼樣的,為什麼會有泛型,泛型怎麼發展出來的。通透了解泛型是學好基礎裡面中非常重要的。于是,我對《Java程式設計思想》這本書中泛型章節進行了研讀。可惜遺憾的是,自己沒有太多的經驗,有些東西看了幾次也是有點懵。隻能以後有機會,再進行學習了。但是自己也了解了挺多的。下面就是自己對于泛型的了解與感悟。如有不對,望指出。
2 | 概念
由來: Java 一開始設計之初是沒有泛型這個特性的,直到jdk 1.5中引入了這個特性。Java 的泛型是由擦除來實作的。要知道擦除是什麼?往下看。
概念:一般的類和方法,隻能使用具體的類型;要麼是基本類型,要麼是自定義的類。如果要編寫可以應用于多種類型的代碼,這種刻闆的限制對代碼的束縛就會很大。泛型實作了參數化類型的概念,使代碼應用于多個類型。泛型在程式設計語言中出現時,其最初的目的是希望類和方法具有廣泛的表達能力。
3 簡單泛型
有很多原因促成泛型的出現,其中最重要的一個原因就是為了創造容器類。我們暫時不指定類型,而是稍後再決定具體使用什麼類型。要達到這個目的,需要使用類型參數,用尖括号包覆,放在類名的後面。然後使用這個類的時候,再用實際的類型替換此類型參數。在下面例子中,T就是類型參數。代碼如下:
public class Holder<T> {
private T a;
public Holder(T a) {
this.a = a;
}
public void set(T a) {
this.a = a;
}
public T get(){
return a;
}
public static void main(String[] args) {
Holder<String> h = new Holder<>();
h3.set("hello");
String a = h3.get();
}
}
class Automobile{}
但是往往很多的源碼中一些通用的類都是有多個泛型參數,譬如 java.util.function.BiFunction 中就有三個類型參數 T,U,R 。
4 接口泛型
泛型在接口上的運用是非常多的,例如疊代器(Iterable)中的 Iterator 。
public interface Itreator<T>{
// 判斷是否還有元素
boolean hasNext();
// 洗一個元素
E next();
....
}
這個是我們都很常用的吧,其實接口使用泛型和類使用泛型沒什麼差別。
5 泛型方法
泛型方法,在傳回參數類型前面添加泛型參數清單,由<>括着
eg:
// 泛型方法 兩個泛型參數,T,R 傳回類型為 R
public <T, R> R test(T t, R r){
return r;
}
泛型方法使得該方法獨立于類而産生變化。在需要編寫泛型代碼的時候,基本的的指導原則是:無論何時,隻要你能做到,就盡量使用泛型方法。意思是如果使用泛型方法可以代替整個類的泛型化,那就用泛型方法,因為它可以使事情更加清楚明白。另外對于static的方法而言,無法通路泛型類的類型參數,是以如果static方法需要使用泛化能力,就必須使其成為泛型方法。
6 泛型的擦除
在看 《Java程式設計思想》 中泛型章節中 ’擦除的神秘之處‘ 這一小節的時候,看的我特别的暈暈乎乎的,然後再往下面看時就越來越混了。 特别是看到’邊界‘,’通配符‘ 這塊了就有點懵了。首先看下什麼是擦除。在泛型代碼内部,無法獲得有關泛型參數類型的資訊。 Java 的泛型是由擦拭來實作的,這意味着當你使用泛型的時,任何具體的類型都被擦除了,你唯一知道的就是你在使用一個對象。由于 Java 一開始沒有引入 泛型這個特性,在為了相容 JDK 老版本的情況下。擦除是 Java 泛型實作的一種折中。 是以你在運作時
List<String>
和
List<Integer>
是一樣的,注意是在運作時,但是在編譯時,
List<String>
表示這個 String 類型的 List 容器,
List<Integer>
表示這個時 Integer 類型的List容器。 舉個例子,例子來源于 Java程式設計思想
#include <iostream>
using namespace std;
temple<class T> class Manipulator{
T obj;
public :
Manipulator(T x){obj = x;}
void manipylate() {obj.f();}
};
class HasF{
public:
void f(){cout<<"HasF::f()"<< endl;}
};
int main(){
HasF hf;
Manipulator<HasF> manipulator(hf);
manipulator.manipylate();
}
輸出 HasF:f()
Manipulator 類存儲了一個類型T的對象,在 manipylate 方法裡,他在 obj上調用了方法 f() ; 它是怎麼知道 f() 是類型參數T 中有的方法呢? 當你執行個體化這個模闆的時,c++編譯器将進行檢查,如果他在
Manipulator<HasF>
被執行個體化的這刻,它看到HasF如果擁有這個方法 f(), 就編譯通過,否則不通過。 這個代碼就算不會 c++ 的人應該也看的懂吧。我們看下 Java 的版本:
public class HasF{
public void f(){
System.out.println("HasF::f()");
}
}
class Manipulator<T>{
private T obj;
public Manipulator(T obj){
this.obj = obj;
}
public void manipulator(){
//錯誤: 這個是不可以調用 f() 這個方法的。
// obj.f();
}
public static void main(String[] args){
HasF hf = new HasF();
Manipulator<HasF> manipulator = new Manipulator<>(hf);
manipulator.manipulator();
}
}
看到沒有,Java 中有了擦除, Java 編譯器不知道 obj 中有沒有 f() 這個方法的事情。
7 泛型的邊界
1 T extends Class
Java 中的泛型,在編譯時,T代表一種類型,如果沒有指定任何的邊界,那麼他就相當于 Object 。 我們可以通過 extends 關鍵字,給泛型指定邊界。 上面代碼我們為了能夠調用f(), 我們可以協助泛型類,給定泛型類的邊界,告訴編譯器必須接受遵循這個邊界的類型。這裡用 extends 關鍵字。 将上面代碼改成
public class HasF{
public void f(){
System.out.println("HasF::f()");
}
}
class Manipulator<T extends HasF>{
private T obj;
public Manipulator(T obj){
this.obj = obj;
}
public void manipulator(){
obj.f();
}
public static void main(String[] args){
HasF hf = new HasF();
Manipulator<HasF> manipulator = new Manipulator<>(hf);
manipulator.manipulator();
}
}
輸出:HasF::f()
這樣子在編譯的時候就相當告訴編譯器 Manipulator 類中 obj 的參數類型 為 HasF 或着它的子類。
? extends T
? 是泛型表達式中的通配符。 ? extends T 表示: T 資料類型或着 T 的子類資料類型。 舉個例子
class Vegetables{}
// 白菜
class Cabbage extends Vegetables{}
// 白菜
class Pakchoi extends Cabbage{}
//大蒜
class Garlic extends Vegetables{}
public class Main{
public static void main(String[] args) {
// 這個是會報錯的。
// ArrayList<Vegetables> vegetables = new ArrayList<Cabbage>();
}
}
上面的中 main 方法裡面是報錯的因為,
ArrayList<Vegetables>
表示這個ArrayList容器中隻能存放蔬菜,
new ArrayList<Cabbage>()
表示 這個使一個隻能放白菜的容器。這兩個容器是不可以相等的,類型不一樣。但是我們可以通過 ? extends T 來解決這個問題,代碼如下:
public class Main{
public static void main(String[] args) {
ArrayList<? extends Vegetables> vegetables = new ArrayList<Cabbage>();
// 報錯 不能添加白菜進去
// vegetables.add(new Cabbage());
}
}
我們可以用 vegetables 表示
new ArrayList<Cabbage>()
, 這是向上轉型。但是,為什麼
vegetables.add(new Cabbage())
; 會報錯,因為
ArrayList<? extends Vegetables>
表示這個 ArrayList 容器中能夠存放任何蔬菜。 但是 ArrayList 具體是什麼容器,完全不知道,add 的時候,你添加什麼東西進去到這個容器中都是不安全的。 這個時候,我們可以用 ? super T 來進行操作,具體往下看。
? super T
? super T 表示: T 資料類型 或着 T的超類資料類型, super 表示超類通配符。 上面代碼可以用以下表示
public class Main{
public static void main(String[] args) {
ArrayList<? super Cabbage> cabbages = new ArrayList<Vegetables>();
cabbages.add(new Cabbage());
cabbages.add(new Pakchoi());
// cabbages.add(new Vegetables());
System.out.println(cabbages);
}
}
上面
ArrayList<? super Cabbage>
表示ArrayList這個容器中怎麼都可以存放 Cabbage 以及Cabbage子類的資料類型。 cabbages 指向的是 蔬菜的容器類。 add 進去的是白菜以及白菜的子類型資料。這個當然是支援的。
? extends T VS ? super T
? extends T ,? super T 一般用于方法參數清單。
public class Main{
public static void main(String[] args) {
List<Cabbage> cabbages = new ArrayList<>();
cabbages.add(new Cabbage());
cabbages.add(new Cabbage());
cabbages.add(new Cabbage());
extendsTest(cabbages);
List<? super Cabbage> list = superTest(new ArrayList<Vegetables>());
System.out.println(list);
}
public static void extendsTest(List<? extends Vegetables> list){
for (Vegetables t : list){
System.out.println(t);
}
}
public static List<? super Cabbage> superTest(List<? super Cabbage> list){
list.add(new Cabbage());
list.add(new Pakchoi());
return list;
}
}
? extends T 表示消費者 list 然後把裡面的資料消費掉。
? super T 表示生産者 傳入一個list 然後往裡面添加資料,進行其他操作。
8 總結
對于 ? extends Class ,? extends T,? super T,不是很了解的,可以自己把例子寫一下,然後想一想。Java 泛型的特性在很多開源的架構上是用的非常多的。這快需要深入的了解一下,我想随着敲代碼的年限上,應該到了後面會有不一樣得了解吧。現在通過書上能夠知道,了解得就隻有這麼多了。
__EOF__
作 者:
家裡那隻橘貓出 處:
https://www.cnblogs.com/Krloypower/p/10454507.html