第不知道多少天 泛型

什麼是泛型

Java 泛型(generics)是 JDK 5 中引入的一個新特性, 泛型提供了編譯時類型安全檢測機制,該機制允許程序員在編譯時檢測到非法的類型。

 

泛型的本質是參數化類型,也就是說所操作的數據類型被指定爲一個參數。

 

廣泛的類型,,接口和方法代碼可以應用於非常廣泛的類型,代碼與他們能夠操作的數據類型不再綁定在一起,同一套代碼,可以用於多種數據類型,這樣不僅可以複用代碼,降低耦合,同時可以提高代碼的可讀性和安全性.

 

使用場景:數據類型不確定

例如:現在要存儲一個學生的成績,但是成績有可能想存爲數字,小數,或者字符串(優秀,良好,好)之類的數據。這種數據都是類型不確定的。 
可以使用Object來存儲該成績,但是這樣存儲的話會把所有類型都當做Object來對待。從而”丟失”了實際的數據類型。獲取數據的時候也需要轉換類型,效率低,還容易出錯。

 

一個簡單的泛型類

public class Pair<T> {

 

    T first;

    T second;

    

    public Pair(T first, T second){

        this.first = first;

        this.second = second;

    }

    

    public T getFirst() {

        return first;

    }

    

    public T getSecond() {

        return second;

    }

}

Pair類引入了類型變量T <>括起來,並放在類名之後.

類的屬性的類型也是T

T也是類方法的中返回類型

 

怎麼使用這個泛型類

用具體的類型代替類型變量就可以實例化泛型

Pair<Integer> minmax = new Pair<Integer>(1,100);

Integer min = minmax.getFirst();

Integer max = minmax.getSecond();

<Integer>表示傳入的類型是Integer

這個類型是可以變化的

Pair<String> kv = new Pair<String>("name",nm");

泛型類可以有多個類型變量

public class Pair<U, V> {

 

    U first;

    V second;

    

    public Pair(U first, V second){

        this.first = first;

        this.second = second;

    }

    

    public U getFirst() {

        return first;

    }

 

    public V getSecond() {

        return second;

    }

}

使用

Pair<String,Integer> pair = new Pair<String,Integer>("name",100);

簡單來說,泛型類可以看作普通類的工廠

 

泛型類型的命名

泛型類型變量使用大寫形式,並且比較短  在java中變量E表示集合的元素類型,KV分別表示表的關鍵字與值的類型. T(需要時還可以使用臨近的字母US)表示任意類型

注意:

Jdk1.7以後 構造函數中可以省略泛型類型 ArrayList<String> files  = new ArrayList<>();

泛型不能使用在靜態屬性或者靜態方法上也不能用在全局常量上

泛型的原理

JavaJava編譯器和Java虛擬機,編譯器將Java源代碼轉換爲.class文件,虛擬機加載並運行.class文件。對於泛型類,Java編譯器會將泛型代碼轉換爲普通的非泛型代碼,就像上面的普通Pair類代碼及其使用代碼一樣,將類型參數T擦除,替換爲Object,插入必要的強制類型轉換。Java虛擬機實際執行的時候,它是不知道泛型這回事的,它只知道普通的類及代碼。

泛型的好處

既然泛型是實現原理是將泛型變爲了普通類和Object就可以的,而且泛型最後也變成了普通類,那麼爲什麼還是要使用泛型呢?

 

 

 

栗子

只使用Object,代碼寫錯的時候,開發環境和編譯器不能幫我們發現問題,看代碼:

Pair pair = new Pair("name",1);

Integer id = (Integer)pair.getFirst();

String name = (String)pair.getSecond();

寫代碼時,不小心,類型弄錯了,不過,代碼編譯時是沒有任何問題的,但,運行時,程序拋出了類型轉換異常ClassCastException。

但是如果使用泛型,那麼開發環境如Eclipse會提示你類型錯誤,即使沒有好的開發環境,編譯時,Java編譯器也會提示你。

1類型安全 泛型的主要目標是提高 Java 程序的類型安全。通過知道使用泛型定義的變量的類型限制,編譯器可以在一個高得多的程度上驗證類型假設。沒有泛型,這些假設就只存在於程序員的頭腦中(或者如果幸運的話,還存在於代碼註釋中)。

將運行時期的ClassCastException,轉移到了編譯時期變成了編譯失敗。

2消除強制類型轉換 泛型的一個附帶好處是,消除源代碼中的許多強制類型轉換。這使得代碼更加可讀,並且減少了出錯機會。提高了代碼的可讀性


泛型有三種使用方式,分別爲:泛型類、泛型接口、泛型方法

 

泛型方法

是不是泛型方法和他在不在泛型類中沒有關係

只有聲明瞭<T>的方法纔是泛型方法,泛型類中的使用了泛型的成員方法並不是泛型方法

public static <T> int indexOf(T[] arr, T elm){

    for(int i=0; i<arr.length; i++){

        if(arr[i].equals(elm)){

            return i;

        }

    }

    return -1;

}

類型參數在方法修飾符之後,返回類型之前

調用indexOf(new Integer[]{1,3,5}, 10)

或者indexOf(new String[]{"hello","老馬","編程"}, "老馬")

和泛型類一樣泛型方法也可以有多個參數

public static <U,V> Pair<U,V> makePair(U first, V second){

    Pair<U,V> pair = new Pair<>(first, second);

    return pair;

}

在大多數情況下,方法調用中可以省略<T>參數的,編譯器有足夠的信息推斷出所調用的方法

例如makePair(1,a);

類型參數能被用來聲明返回值類型,並且能作爲泛型方法得到的實際參數類型的佔位符。

類中的泛型方法轉載https://blog.csdn.net/s10461/article/details/53941091

 

public class GenericFruit {

class Fruit {

@Override

public String toString() {

return "fruit";

}

}

class Apple extends Fruit {

@Override

public String toString() {

return "apple";

}

}

class Person {

@Override

public String toString() {

return "Person";

}

}

class GenerateTest<T> {

public void show_1(T t) {

System.out.println(t.toString());

}

// 在泛型類中聲明瞭一個泛型方法,使用泛型E,這種泛型E可以爲任意類型。可以類型與T相同,也可以不同。

// 由於泛型方法在聲明的時候會聲明泛型<E>,因此即使在泛型類中並未聲明泛型,編譯器也能夠正確識別泛型方法中識別的泛型。

public <E> void show_3(E t) {

System.out.println(t.toString());

}

 

// 在泛型類中聲明瞭一個泛型方法,使用泛型T,注意這個T是一種全新的類型,可以與泛型類中聲明的T不是同一種類型。

public <T> void show_2(T t) {

System.out.println(t.toString());

}

}

 

public static void main(String[] args) {

Apple apple = new Apple();

Person person = new Person();

 

GenerateTest<Fruit> generateTest = new GenerateTest<Fruit>();

// appleFruit的子類,所以這裏可以

generateTest.show_1(apple);

// 編譯器會報錯,因爲泛型類型實參指定的是Fruit,而傳入的實參類是Person

// generateTest.show_1(person);

 

// 使用這兩個方法都可以成功

generateTest.show_2(apple);

generateTest.show_2(person);

 

// 使用這兩個方法也都可以成功

generateTest.show_3(apple);

generateTest.show_3(person);

}

}

什麼時候使用泛型方法

無論何時,如果你能做到,你就該儘量使用泛型方法。也就是說,如果使用泛型方法將整個類泛型化,那麼就應該使用泛型方法。另外對於一個static的方法而已,無法訪問泛型類型的參數。所以如果static方法要使用泛型能力,就必須使其成爲泛型方法。

 

 

泛型接口

public interface Comparable<T> {

    public int compareTo(T o);

}

public interface Comparator<T> {

    int compare(T o1, T o2);

    boolean equals(Object obj);

}

和泛型類差不多

泛型接口的實現

      public interface List <E>{

          abstract boolean add(E e);

        }

       接口裏的常量不能是用泛型修飾,靜態方法也不能.

    實現類,先實現接口,不理會泛型

        public class ArrayList<E> implements List<E>{

        }

        調用者 : new ArrayList<String>() 後期創建集合對象的時候,指定數據類型

實現類,實現接口的同時,也指定了數據類型

       public class XXX implements List<String>{

        }

       new XXX()

泛型的限定

java中用通配符和邊界來限制T的範圍

extends:可讀不可寫  限制父類 上限限制   可以傳遞T,傳遞他的子類對象

super :可寫不可讀   限制子類  下限限制 可以傳遞T,傳遞他的父類對象

都沒有 :沒有繼承很開心,一遇繼承就完蛋

java是單繼承,所有繼承的類構成一棵樹。
T<? super B>T<? extends B>

假設AB都在一顆繼承樹裏(否則superextend這些詞沒意義)。
A super B 表示AB的父類或者祖先,在B的上面。
A extend B 表示AB的子類或者子孫,在B下面。

由於樹這個結構上下是不對稱的,所以這兩種表達區別很大。假設有兩個泛型寫在了函數定義裏,作爲函數形參(形參和實參有區別):

1) 參數寫成:T<? super B>,對於這個泛型,?代表容器裏的元素類型,由於只規定了元素必須是B的超類,導致元素沒有明確統一的“根”(除了Object這個必然的根),所以這個泛型你其實無法使用它,對吧,除了把元素強制轉成Object。所以,對把參數寫成這樣形態的函數,你函數體內,只能對這個泛型做插入操作,而無法讀

2) 參數寫成: T<? extends B>,由於指定了B爲所有元素的“根”,你任何時候都可以安全的用B來使用容器裏的元素,但是插入有問題,由於供奉B爲祖先的子樹有很多,不同子樹並不兼容,由於實參可能來自於任何一顆子樹,所以你的插入很可能破壞函數實參,所以,對這種寫法的形參,禁止做插入操作,只做讀取
鏈接:https://www.zhihu.com/question/20400700/answer/117624335
來源:知乎
?無限定通配符

所有的都可以傳,只能讀,不能寫

泛型的約束和侷限性

不能使用基本數據類型實例化類型參數

運行時類型信息不使用與泛型

不能創建參數化類型的數組

類型擦除可能會引發一些衝突

不能通過類型參數創建對象

不能用於靜態變量

(好多沒很好的理解先往後面學着應給能理解了吧)

 

好多來自https://www.cnblogs.com/swiftma/p/5847198.html


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章