1. 什么是泛型?
泛型將接口的概念進一步延伸,“泛型”的字面意思就是廣泛的類型。類、接口和方法代碼可以應用於非常廣泛的類型,代碼與它們能夠操作的數據類型不再綁定在一起,同一套代碼可以用於多種數據類型,這樣不僅可以復用代碼,降低耦合性,而且還提高了代碼的可讀性以及安全性。講起來優點抽象,我們看個實際的例子。
2. 先來看一個簡單的泛型例子
package genericity.demo;
/**
* @author BastetCat
* @data 2019/8/8 21:14
*/
public class Pair<T> {
T one;
T two;
public Pair(T one, T two) {
this.one = one;
this.two = two;
}
public T getOne() {
return one;
}
public T getTwo() {
return two;
}
}
觀察和普通類的區別:
- 類名后面多了一個
<T>
- one 和 two的類型都是T
3. 那么T是什么呢?
T 表示類型參數
泛型就是類型參數化,處理的數據類型不是固定的,而是可以作為參數傳入。
現在我們知道了,泛型把類型作為了參數來使用。
4. 如何使用泛型,並將類型作為參數傳入呢?
如下代碼:我們分別new了三Pair對象,其中傳入了不同類型的類型參數(Integer、Character、String)。
package genericity.demo;
/**
* @author BastetCat
* @data 2019/8/8 21:22
*/
public class Test {
public static void main(String[] args) {
Pair<Integer> pairInteger = new Pair<Integer>(1,2);
int one1 = pairInteger.getOne();
int two1 = pairInteger.getTwo();
System.out.println("one:"+one1+",two:"+two1);
Pair<Character> pairCharacter = new Pair<Character>('一','二');
char one2 = pairCharacter.getOne();
char two2 = pairCharacter.getTwo();
System.out.println("one:"+one2+",two:"+two2);
Pair<String> pairString = new Pair<String>("I","II");
String one3 = pairString.getOne();
String two3 = pairString.getTwo();
System.out.println("one:"+one3+",two:"+two3);
}
}
結果如下:
one:1,two:2
one:一,two:二
one:I,two:II
當然我們不僅可以傳入一個類型參數,也可以傳入多個類型參數。多個類型參數之間用 逗號“,”隔開。如下面的例子:
package genericity.demo;
/**
* @author BastetCat
* @data 2019/8/8 21:37
*/
public class PairTwo <U,V> {
U one;
V two;
public PairTwo(U one, V two) {
this.one = one;
this.two = two;
}
public U getOne() {
return one;
}
public V getTwo() {
return two;
}
}
可以這么使用:
PairTwo<String,Integer> pairTwo = new PairTwo<>("牛牛",20);
注意:自 Java 7 開始,支持省略后面的類型參數,讓書寫更簡單些。
5. 泛型的基本原理
泛型類型參數到底是什么?為什么一定要定義類型參數呢?定義普通類,直接使用Object也是可以呀。如之前的Pair類我們可以寫成:
package genericity.demo;
/**
* @author BastetCat
* @data 2019/8/8 21:44
*/
public class PairObject {
Object one;
Object two;
public PairObject(Object one, Object two) {
this.one = one;
this.two = two;
}
public Object getOne() {
return one;
}
public Object getTwo() {
return two;
}
}
然后這樣使用,也是同樣的效果:
package genericity.demo;
/**
* @author BastetCat
* @data 2019/8/8 21:46
*/
public class TestPairObject {
public static void main(String[] args) {
PairObject pairObject1 = new PairObject(1,2);
int one1 =(int)pairObject1.getOne();
int two1 =(int)pairObject1.getTwo();
System.out.println("one:"+one1+",two:"+two1);
PairObject pairObject2 = new PairObject("yi","er");
String one2 =(String)pairObject2.getOne();
String two2 =(String)pairObject2.getTwo();
System.out.println("one:"+one2+",two:"+two2);
}
}
輸出結果:
one:1,two:2
one:yi,two:er
我們可以看到,確確實實我們的使用Object + 強制類型轉換也實現了相同的結果。事實上,我們Java泛型的內部原理就是這樣的。
我們使用JAD工具來反編譯我們的Pair.class 與 Test.class得到的結果如下:
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3)
// Source File Name: Pair.java
package genericity.demo;
public class Pair
{
public Pair(Object obj, Object obj1)
{
one = obj;
two = obj1;
}
public Object getOne()
{
return one;
}
public Object getTwo()
{
return two;
}
Object one;
Object two;
}
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3)
// Source File Name: Test.java
package genericity.demo;
import java.io.PrintStream;
// Referenced classes of package genericity.demo:
// Pair
public class Test
{
public Test()
{
}
public static void main(String args[])
{
Pair pair = new Pair(Integer.valueOf(1), Integer.valueOf(2));
int i = ((Integer)pair.getOne()).intValue();
int j = ((Integer)pair.getTwo()).intValue();
System.out.println((new StringBuilder()).append("one:").append(i).append(",two:").append(j).toString());
Pair pair1 = new Pair(Character.valueOf('\u4E00'), Character.valueOf('\u4E8C'));
char c = ((Character)pair1.getOne()).charValue();
char c1 = ((Character)pair1.getTwo()).charValue();
System.out.println((new StringBuilder()).append("one:").append(c).append(",two:").append(c1).toString());
Pair pair2 = new Pair("I", "II");
String s = (String)pair2.getOne();
String s1 = (String)pair2.getTwo();
System.out.println((new StringBuilder()).append("one:").append(s).append(",two:").append(s1).toString());
}
}
通過以上的分析:
我們可以得知,Java的編譯器將java源文件編譯成字節碼.class文件,虛擬機加載並運行。對於泛型類,java編譯器會將其轉換為普通的非泛型代碼。將類型T擦除,然后替換為Object,插入必要的強制類型轉換。Java虛擬機實際執行的時候,並不知道泛型這回事,只知道普通的類及代碼。
那么為什么泛型要這樣設計呢?
因為泛型是 Java 5 以后才支持的,這么設計是為了兼容性,而不得已的一個選擇。
6. 使用泛型的好處
- 代碼復用:我們一套代碼可以支持不同的類性。
- 降低了耦合性:代碼邏輯和數據類型之間分離,實現了解耦。
- 更好的可讀性:我們在使用集合的時候,定義了一個list 如
List<String>
,一看便知道這個一個存放String類型的list。 - 更高的安全性:語言和程序設計的一個重要目標就是將bug消滅在搖籃里,能在寫的時候消滅,就不要留在運行的時候。如我們定義一個
List<String>
這樣的一個list。當我們往list里面放其他非String類型的數據時,我們的IDE(如Eclipse)就會報錯提示。就算沒有IDE。編譯時,Java編譯器也會提示,這稱之為類型安全。這樣就為程序設置了一道安全防護。同樣的,使用泛型還可以省去使用普通對象時繁瑣的強制類型轉換。相反,使用普通對象,編譯時並不會提示。假如傳入的參數類型和最后強制類型轉換的類型不一致。運行時就會出現ClassCastException,使用泛型則不會。