1、為什么需要泛型?
1.1可以為多種數(shù)據(jù)類型執(zhí)行相同的代碼
public class NonGeneric {
public int addInt(int x,int y){
return x + y;
}
public float addFloat(float x,float y){
return x + y;
}
public static void main(String[] args) {
NonGeneric nonGeneric = new NonGeneric();
System.out.println(nonGeneric.addInt(1,2));
System.out.println(nonGeneric.addFloat(1f,2f));
}
}
我們可以看到上面這段代碼搜立,int類型以躯,float類型都有各自的加法方法,如果我們還要增加double類型的話啄踊,我們還要再加一個double的加法方法忧设,這樣顯然是麻煩的。
1.2可以在編譯期間指定數(shù)據(jù)類型社痛,不需要強制類型轉換见转,以及插入錯誤的數(shù)據(jù)類型在編譯期間就能發(fā)現(xiàn)。
public class NonGeneric {
public static void main(String[] args) {
List list = new ArrayList();
list.add("String");
list.add(100);
int size = list.size();
for (int i = 0; i < size; i++) {
String name = (String) list.get(i);
System.out.println("name:" + name);
}
}
}
運行后報錯2蒜哀、泛型類斩箫、泛型接口和泛型方法
2.1泛型方法
完全獨立的吏砂,不一定非要定義在泛型類或泛型接口中,在普通類中同樣可以定義乘客。
在訪問修飾符和返回值之間聲明<T>狐血,這種方法就是泛型方法,這是必要的易核,否則不是泛型方法匈织。
public static <T> T genericMethod(T... a){
return a[0];
}
2.2泛型類和泛型接口的定義
//泛型類
public class NormalGeneric<T> {
private T data;
//該方法不是泛型方法 只不過返回值是泛型T 只有加入<T>的方法才是泛型方法
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
//泛型接口
public interface IGenertor<T> {
T next();
}
//泛型接口實現(xiàn)類1 不指明確切類型
public class ImpGenertor<T> implements IGenertor<T> {
@Override
public T next() {
return null;
}
}
//泛型接口實現(xiàn)類2 明確具體類型
public class ImpGenertor2 implements IGenertor<String> {
@Override
public String next() {
return null;
}
}
泛型方法和泛型類或泛型接口的區(qū)別
泛型類或泛型接口在創(chuàng)建實例對象時傳入具體類型,泛型方法在調用時傳入(不傳入也可以牡直,編譯器會自動識別)
public class NonGeneric {
public static <T> T genericMethod(T... a){
return a[0];
}
public static void main(String[] args) {
//泛型類
ImpGenertor<String> impGenertor = new ImpGenertor();
//泛型接口 自動識別類型
System.out.println(genericMethod("111","2222"));
}
}
泛型方法和泛型類或泛型接口 可以有多個泛型
例如:
public interface IGenertor<T,K> {
T next();
K firrs();
}
下面我們看一段代碼
public class NonGeneric {
static class Fruit{
@Override
public String toString() {
return "水果";
}
}
static class Apple extends Fruit{
@Override
public String toString() {
return "蘋果";
}
}
static class Person{
@Override
public String toString() {
return "人";
}
}
static class GenericTest<T> {
public void show_1(T t) {
System.out.println(t.toString());
}
public <E> void show_2(E e) {
System.out.println(e.toString());
}
public <T> void show_3(T t) {
System.out.println(t.toString());
}
}
public static void main(String[] args) {
Fruit fruit = new Fruit();
Apple apple = new Apple();
Person person = new Person();
GenericTest<Fruit> genericTest = new GenericTest<>();
genericTest.show_1(fruit);
genericTest.show_1(apple);
//編譯器會報錯
// genericTest.show_1(person);
genericTest.show_2(apple);
genericTest.show_2(fruit);
genericTest.show_2(person);
genericTest.show_3(apple);
genericTest.show_3(fruit);
genericTest.show_3(person);
}
}
由上面的代碼可知:
1缀匕、不是聲明在泛型類里面的就是泛型方法
2、在泛型類中使用泛型方法碰逸,也要在訪問修飾符和返回值之間將泛型<T>聲明出來
3乡小、泛型方法聲明的泛型是獨立的,所以show_2可以傳入任意類型饵史,不和泛型類中定義的泛型有任何關系
4满钟、如果泛型方法聲明的泛型和泛型類中聲明的泛型名稱一致,也遵循上一條胳喷,所以show_3使用上與show_2沒有任何區(qū)別
5湃番、泛型類聲明的泛型只影響泛型類中使用該泛型的部分,不會影響獨立的泛型方法吭露。
3吠撮、如何限定類型變量
public static <T> T min(T a,T b){
if (a.compareTo(b) > 0) return a; else return b;
}
上面這段代碼編譯器會報錯,因為編譯器不能確定T類型一定含有compareTo方法奴饮,那么我們如何保證執(zhí)行泛型這個類型變量一定有某個方法或遵循某種規(guī)則呢纬向?
我們如果在傳入?yún)?shù)時,限制只有實現(xiàn)compareTo方法才可以傳入是不是就解決了這個問題戴卜,這個思想就是類型變量的限定逾条。
我們將代碼改為下面這種形式
public static <T extends Comparable> T min(T a,T b){
if (a.compareTo(b) > 0) return a; else return b;
}
這樣就沒問題了,限制T必須實現(xiàn)或者派生自Comparable這個接口
T表示應該綁定類型的子類型
Comparable表示綁定類型也就是限定類型
T和Comparable 可以是類 也可以是接口
限定類型可以是多個投剥,用&連接师脂,但是要注意,如果限定類型有接口江锨,也有類吃警,類必須要寫在限定類型的第一個,而且必須是有且僅有一個類啄育。
//這么寫會報錯
public static <T extends Comparable& ArrayList> T min(T a, T b){
if (a.compareTo(b) > 0) return a; else return b;
}
//這么寫沒問題
public static <T extends ArrayList&Comparable> T min(T a, T b){
if (a.compareTo(b) > 0) return a; else return b;
}
為什么只能有一個類?
因為Java是單繼承的
為什么一定要寫在第一個?
問Java去吧 我也不知道
注意:上面寫的示例是泛型方法酌心,但是規(guī)則對泛型類和泛型接口同樣適用。
4挑豌、泛型使用中的約束和局限性
1安券、不能實例化類型變量
2墩崩、靜態(tài)域或者靜態(tài)方法里不能使用,需要注意的是靜態(tài)方法本身是泛型方法就可以使用
3侯勉、基本數(shù)據(jù)類型不可以鹦筹,可以傳入基礎類型的包裝類,為什么不可以,因為T只能是對象址貌,基礎數(shù)據(jù)類型不是對象
4铐拐、不能使用instanceof關鍵字 判斷具體類型
5、泛型在使用的時候 關于這個類的類型不管你傳入的是什么類型參數(shù) 打印出來的類名都是一樣的
6练对、可以定義泛型數(shù)組 但是不能初始化
7遍蟋、泛型類不能extends Exception/Throwble
8、不能捕獲泛型類對象
public class Restrict<T> {
private T data;
//1锹淌、不能實例化類型變量
// public Restrict() {
// this.data = new T();
// }
//2匿值、靜態(tài)域或者靜態(tài)方法里不能使用
// private static T instance;
//靜態(tài)方法本身是泛型方法就可以使用
// private static <T> T getInstance(){
// T a = null;
// return a;
// };
public static void main(String[] args) {
//3赠制、基本數(shù)據(jù)類型不可以會報錯
// Restrict<double>
Restrict<Double> restrict = new Restrict<>();
//4赂摆、不能使用instanceof關鍵字 判斷具體類型 這里會報錯
// if(restrict instanceof Restrict<Double>)
//5、泛型在使用的時候 關于這個類的類型不管你傳入的是什么類型參數(shù) 打印出來的類名都是一樣的
Restrict<String> restrictStr = new Restrict<>();
//這里打印結果為true
System.out.println(restrict.getClass() == restrictStr.getClass());
//下面打印的名字是一樣的 Restrict類名 泛型在使用的時候 關于這個類的類型不管你傳入的是什么類型參數(shù) 打印出來的類名都是一樣的
System.out.println(restrict.getClass().getName());
System.out.println(restrictStr.getClass().getName());
//打印結果
// true
// com.hot.lib.day1.Restrict
// com.hot.lib.day1.Restrict
//6可以定義泛型數(shù)組 但是不能初始化
Restrict<Double>[] restrictArray;
Restrict<Double>[] restricts = new Restrict<Double>[10];
}
//7钟些、泛型類不能extends Exception/Throwble 這里編譯器會報錯
// private class Problem<T> extends Exception{}
//8烟号、不能捕獲泛型類對象
// public <T extends Throwable> void dowork(T x){
// try {
//
// }catch (T t){
//
// }
// }
//這種方式可以
public <T extends Throwable> void dowork(T x) throws T{
try {
}catch (Throwable t){
throw x;
}
}
}
5、泛型類型的繼承規(guī)則
這里我們用代碼來提現(xiàn)
class Employee {
}
class Worker extends Employee {
}
public class Pair<T> {
public static void main(String[] args) {
Pair<Worker> workerPair = new Pair<>();
Pair<Employee> employeePair = new Pair<>();
//我們平時這么寫是沒問題的 因為Worker extends Employee
Employee employee = new Worker();
//1政恍、這里編譯器會報錯 workerPair和employeePair 不存在繼承關系
// Pair<Employee> employeePair1 = new Pair<Worker>();
//這里是沒問題的
Pair<Employee> pair = new ExtendPair<>();
}
//2汪拥、泛型類可以繼承或者擴展其他泛型類 比如List和ArrayList
private static class ExtendPair<T> extends Pair<T>{}
}
6、泛型中的通配符類型
只能用在方法中篙耗,類中是不可以使用的
這里比較復雜迫筑,首先我們先看幾個測試類。
public class GenericType<T> {
private T data;
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
public class Food {
}
public class Fruit extends Food{
}
public class Apple extends Fruit{
}
public class Orange extends Fruit{
}
public class Tomatoes extends Apple {
}
注意上面這些類的繼承關系宗弯。
我們首先看第一種情況
public static void println(GenericType<Fruit> p){
System.out.println(p.getData());
}
public static void use(){
GenericType<Fruit> fruitGenericType = new GenericType<>();
GenericType<Apple> appleGenericType = new GenericType<>();
println(fruitGenericType);
//編譯器報錯
// println(appleGenericType);
}
這種情況很簡單脯燃,我們上面說過,泛型參數(shù)之間存在繼承關系蒙保,但是泛型類沒有任何關系辕棚,所以這里傳入GenericType<Apple>類型會報錯。正是因為如此邓厕,才引入了通配符的概念逝嚎。
接下來我們看下面這段代碼
public static void println(GenericType<? extends Fruit> p){
System.out.println(p.getData());
}
public static void use(){
GenericType<Fruit> fruitGenericType = new GenericType<>();
GenericType<Apple> appleGenericType = new GenericType<>();
GenericType<Food> foodGenericType = new GenericType<>();
println(fruitGenericType);
//這時可以編譯通過
println(appleGenericType);
//編譯報錯
// println(foodGenericType);
GenericType<? extends Fruit> genericType = new GenericType<>();
//不會報錯
Fruit fruit = genericType.getData();
//下面兩行代碼都會報錯
// genericType.setData(new Apple());
// genericType.setData(new Fruit());
}
這里我們可以看到打印方法的泛型改為? extends Fruit,详恼?就是通配符补君,extends則是我們上面講的限定符(上界限定符),這里的意思是昧互,限定泛型類型為Fruit的派生類挽铁,包括Fruit本身她紫。所以我們看到,println傳入Fruit的子類型都沒問題了屿储,但是傳入Food就會報錯贿讹,因為Food是Fruit的父類。
接下來我們看到够掠,我們創(chuàng)建了一個genericType對象民褂,然后分別調用了getData和setData方法。
getData方法返回的是Fruit類型疯潭,因為我們知道傳入的上限就是Fruit類型赊堪,如果傳入的是Fruit派生類型,也是可以轉為Fruit類型竖哩,所以這里會返回Fruit類型哭廉。
然后我們發(fā)現(xiàn),setData方法會報錯相叁,因為我們沒有指定確切的類型遵绰,雖然我們傳入的是Fruit的子類,但是編譯器不知道是哪個子類增淹,所以會報錯椿访。
extends限定符的作用就是可以安全的訪問數(shù)據(jù),也就是extends后面的類型數(shù)據(jù)虑润,即例子中的Fruit類型數(shù)據(jù)成玫。
接下來我們看另外一種情況
public static void println(GenericType<? super Apple> p){
System.out.println(p.getData());
}
public static void use(){
GenericType<Fruit> fruitGenericType = new GenericType<>();
GenericType<Apple> appleGenericType = new GenericType<>();
GenericType<Orange> orangeGenericType = new GenericType<>();
GenericType<Tomatoes> tomatoesGenericType = new GenericType<>();
println(fruitGenericType);
println(appleGenericType);
//下面兩行編譯報錯
// println(orangeGenericType);
// println(Tomatoes);
GenericType<? super Apple> genericType = new GenericType<>();
//不會報錯
Object fruit = genericType.getData();
//不會報錯
genericType.setData(new Apple());
//編譯報錯
// genericType.setData(new Fruit());
}
這種情況與上面的區(qū)別就是將extends換成了super,super是下界限定符拳喻,即只能是Apple的父類及其自身哭当。所以我們看到只能傳入父類型,傳入子類型或平級的類型會報錯冗澈。
這時調用getData方法钦勘,會返回Object類型,不會返回具體類型渗柿,這很好理解个盆,因為我們限制了只能傳入Apple的父類型,但是我們并不知道具體是哪個父類型朵栖,因為Object是所有類的基類颊亮,所以這里返回了Object。
然后我們看setData調用陨溅,這里的邏輯可能會有些讓人不舒服终惑,我們可以看到傳入Apple的父類型,也就是Fruit類型會報錯门扇,大家一定會疑惑這是為什么雹有,我們明明限定的是只能是Apple的父類型啊偿渡,為什么傳入父類型還會報錯呢?
我們可以換個角度想一想溜宽,我們傳入一個對象進來,可定要調用它的某個方法质帅,但是我們如果傳入的是父類适揉,可能會沒有子類的方法剪侮,但是子類一定擁有父類的方法,這樣可能邏輯上就說的通了洛退,這里是我自己的理解瓣俯,如有不對大家請指出。
通過上面的代碼我們可以發(fā)現(xiàn)不狮,extends是為了安全的訪問數(shù)據(jù)降铸,而super是為了安全的寫入數(shù)據(jù)。
7摇零、虛擬機是如何實現(xiàn)泛型的
java實現(xiàn)泛型使用了類型擦除
public class GenericType<T> {
private T data;
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
這段代碼在虛擬機中會變成下面這樣
public class GenericType<Object> {
private Object data;
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
}
也就是泛型T變成了Object,這也就是所謂的泛型擦除
如果是下面這種狀況
public class GenericType<T extends ArrayList & Comparable> {
private T data;
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
如果是上面這種情況桶蝎,T則會變成ArrayList驻仅。如果使用了限定符,則會轉為限定符后面的第一個類型登渣。
如果調用了Comparable相關的方法噪服,則會在變量前面進行強轉,即下面這種情況胜茧。
public void setData(T data) {
this.data = data;
// (Comparable)data.compareTo()
}
接下來我們看下面這段代碼
public static void ListMethod(List<String> list){
}
public static void ListMethod(List<Integer> list){
}
我們知道方法的重載是參數(shù)列表不同粘优,但是上面的代碼編譯器是會報錯的,也是因為泛型擦除呻顽,即List參數(shù)類型都變?yōu)榱薒ist<Object>了雹顺。
最后我們看下泛型是怎么生效的
public static void main(String[] args) {
Map<String,String> map = new HashMap<>();
map.put("test","aaaa");
System.out.println(map.get("test"));
}
//編譯為class之后樣式
public static void main(String[] args) {
Map<String, String> map = new HashMap();
map.put("test", "aaaa");
System.out.println((String)map.get("test"));
}
我們發(fā)現(xiàn)在字節(jié)碼中對map.get("test")進行了強轉,這也就是泛型的實現(xiàn)方式廊遍,強轉。
泛型擦除并不是真正意義上的擦沒了,泛型的類型會保存在字節(jié)碼文件中阳柔,這樣才實際調用的時候回進行強轉。
以上就是泛型相關的一些知識了王财,如果有哪些不對的地方,希望大家指出裕便,共同進步绒净。