亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Java?SE之了解泛型

 更新時間:2023年01月16日 15:46:45   作者:程序猿教你打籃球  
這篇文章主要介紹了Java?SE之了解泛型,文章內(nèi)容詳細(xì),簡單易懂,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧

如何創(chuàng)建可以存放各種類型的數(shù)組?

根據(jù)JavaSE的語法知識儲備,如果現(xiàn)在讓你們創(chuàng)建如標(biāo)題一樣的數(shù)組,你會怎么創(chuàng)建呢?

答案是:使用 Object 類來定義數(shù)組,因為 Object 是所有類的父類, 可以接收任意子類對象,也即實現(xiàn)了向上轉(zhuǎn)型,于是我們就寫出了這樣的代碼:

private Object[] array = new Object[3];

那么這種方法可取嗎?

顯然是可取的,但只是使用起來會很不方便,具體不方便在哪,我們接著往后看,在這里我們要寫一個類,里面提供了獲取array指定下標(biāo)的數(shù)據(jù),和設(shè)置array指定下標(biāo)的數(shù)據(jù),于是寫出了這樣的代碼:

public class DrawForth {
    private Object[] array = new Object[3];
 
    public void setPosArray(int pos, Object o) {
        this.array[pos] = o;
    }
    public Object getPosValue(int pos) {
        return this.array[pos];
    }
}

代碼到這里仍然是正確的,那我們就要去使用這個類,也就是在main方法中用這個類實例對象,去操作里面的數(shù)組,所以main方法的代碼就是這個樣子:

public static void main(String[] args) {
        DrawForth draw = new DrawForth();
        draw.setPosArray(0, 123);
        draw.setPosArray(1, "hello");
        draw.setPosArray(2, 12.5);
 
        int a = (int)draw.getPosValue(0);
        String str = (String)draw.getPosValue(1);
        double d = (double)draw.getPosValue(1);
    }

看到這里,你是不是就發(fā)現(xiàn)這樣做很不方便呢?

當(dāng)我們往數(shù)組里面設(shè)置數(shù)據(jù)的時候開心了,想設(shè)置成什么類型就是什么類型,但是!當(dāng)我們要獲取對應(yīng)位置的元素就麻煩了,我們必須知道他是什么類型,然后進(jìn)行強制類型轉(zhuǎn)換才能接收,(返回是Object類型所以需要強轉(zhuǎn)),難道往后每次取數(shù)據(jù)的時候我還得看一看是什么類型嗎?

泛型的概念

淺聊泛型

泛型是在JDK1.5引入的新的語法,通過上面的例子,由此我們就引出了泛型,泛型簡單來說就是把類型當(dāng)成參數(shù)傳遞,指定當(dāng)前容器,你想持有什么類型的對象,你就傳什么類型過去,讓編譯器去做類型檢查!

從而實現(xiàn)類型參數(shù)化(不能是基本數(shù)據(jù)類型)

泛型的簡單語法

class Test1<類型形參列表> {
 
}
class Test2<類型形參1, 類型形參2, ...> {
 
}

類型形參列表的命名規(guī)范

類名后面的 <類型形參列表> 這是一個占位符,表示當(dāng)前類是一個泛型類,形參列表里面如何寫?

通常用一個大寫字母表示,當(dāng)然,你也可以怎么開心怎么來,但是小心辦公室談話警告哈(dog),這里有幾個常用的名稱:

  • E:表示 Element
  • K:表示 Key
  • V:表示 Value
  • N:表述 Number
  • T:表示 Type
  • S,U,V表示,第二,第三,第四個類型

使用泛型知識創(chuàng)建數(shù)組

這里就來修改一下剛開始的代碼,使用到泛型的知識,那么我們就可以這樣修改:

public class DrawForth<T> {
    //private T[] array = new T[3]; error
    private T[] array = (T[])new Object[3];
 
    public void setPosArray(int pos, T o) {
        this.array[pos] = o;
    }
 
    public T getPosValue(int pos) {
        return this.array[pos];
    }
 
    public static void main(String[] args) {
        DrawForth<Integer> draw = new DrawForth<>();
        draw.setPosArray(0, 123);
        //draw.setPosArray(1, "hello"); error
        //draw.setPosArray(2, 12.5); error
        draw.setPosArray(1, 1234);
        draw.setPosArray(2, 12345);
 
        int a = draw.getPosValue(0);
        int b = draw.getPosValue(1);
        int c = draw.getPosValue(2);
    }
}

如上修改之后的代碼,我們可以得到以下知識點:

  • <T> 是一個占位符,僅表示這個類是泛型類
  • 不能 new 泛型數(shù)組,此代碼的寫法也不是最好的方法!
  • 實例化泛型類的語法是:類名<類型實參>變量名 = new 泛型類<類型實參>(構(gòu)造方法實參);
  • 注意:new 泛型類<>尖括號中可以省略類型實參,編譯器可以根據(jù)上下文推導(dǎo)!
  • 編譯時自動進(jìn)行類型檢查和轉(zhuǎn)換。

什么是裸類型

裸類型就是指在實例化泛型類對象的時候,沒有傳類型實參,比如下面的代碼就是一個裸類型:

DrawForth draw = new DrawForth();

我現(xiàn)在可以告訴你,這樣做編譯完全正常,但我們不要去使用裸類型,因為這是為了兼容老版本的 API 保留的機制,畢竟泛型是 Java1.5 新增的語法。

泛型是如何編譯的?

泛型的擦除機制

如果我們要看泛型是如何編譯的,可以通過命令 javap -c 字節(jié)碼文件 來進(jìn)行查看:

如上代碼是 2.4 段落中的代碼,奇怪,明明傳的實參是 Integer 類型,最后所有的 T 卻變成了 Object 類型,這就是擦除機制

所以在Java中,泛型機制是在編譯級別實現(xiàn)的,運行期間不會包含任何泛型信息。

提示:類型擦除,不一定是把 T 變成 Object(泛型的上界會提到)

再談為什么不能實例化泛型數(shù)組?

知道了擦除機制后,那么 T[] array = new T[3]; 是不對的,編譯的時候,替換為Object,不是相當(dāng)于:Object[] array = new Object[3]嗎?

在Java中,數(shù)組是一個很特殊的類型,數(shù)組是在運行時存儲和檢查類型信息, 泛型則是在編譯時檢查類型錯誤。

而且Java設(shè)定擦除機制就只針對變量的類型和返回值的類型,所以在編譯時候壓根不會擦除 new T[3]; 這個 T ,所以自然編譯就會報錯!

我們前面通過強制類型轉(zhuǎn)換的方式創(chuàng)建了泛型數(shù)組,說過那樣寫并不好,正確的方式是通過反射創(chuàng)建指定類型的數(shù)組,由于現(xiàn)在沒學(xué)習(xí)到反射,這里先放著就行。

什么是泛型的上界?

有了擦除機制的學(xué)習(xí),泛型在運行時都會被擦除成 Object 但是并不是所有的都是這樣,泛型的上界就是對泛型類傳入的類型變量做一定的約束,可以通過類型邊界來進(jìn)行約束。

語法:

class 泛型類名稱<類型形參 extends 類型邊界> {
    //...code
}

這里我們來舉兩個例子:

例1:

這里簡單分析一下,Student 繼承了 Person 類,而 Teacher 沒有繼承 Person 類,接著 Test 類給定了泛型的上界, 那么 Test 類中 <> 里面是什么意思呢?

表示只接收 Person 或 Person 的子類作為 T 的類型實參。

通過 main 方法中的例子也可也看出,類型傳參只能傳 Person 或 Person 的子類。

例2:

還是簡單分析一下,Student 類實現(xiàn)了 Comparable 接口,而 Teacher 類并沒有實現(xiàn), 接著 Test 類給定了泛型的上界, 那么 Test 類中 <> 里面是什么意思呢?

表示 T 接收的類型必須是實現(xiàn) Comparable 這個接口的!

通過 main 方法中的例子也可也看出,類型傳參只能傳實現(xiàn)了 Comparable 接口的類 。

注意:如果泛型類沒有指定邊界,則可以默認(rèn)視為 T extends Object。

再談擦除機制

如果給泛型設(shè)置了上界,則會擦除到邊界處,也就不會擦除成 Object!

class Person {}
 
class Student extends Person {}
 
public class Main<T extends Person> {
    T array[] = (T[])new Object[10];
    public static void main(String[] args) {
        Main<Student> main = new Main<>();
    }
}

這里 Main 方法中設(shè)定了泛型的上界,傳的類型實參必須是Person的子類,所以編譯時會不會被擦除成 Person呢?下面我們查看一下對應(yīng)的字節(jié)碼文件: 

顯而易見,確實被擦除成了泛型的上界! 

包裝類的知識

基本數(shù)據(jù)類型和包裝類

在Java中,由于基本類型不是繼承自O(shè)bject,為了在泛型代碼中可以支持基本類型,Java給每個基本類型都對應(yīng)了 一個包裝類型。

裝箱和拆箱

裝箱和拆箱也可也被稱為裝包和拆包。

裝箱:將一個基本數(shù)據(jù)類型值放入對象的某個屬性中。

拆箱:將一個包裝類型中的值取出放到一個基本數(shù)據(jù)類型中。

這里我們舉例來更清楚的認(rèn)識裝箱和拆箱:

public class Test {
    public static void main(String[] args) {
        int a = 10;
        Integer integer1 = new Integer(a); //手動裝箱
        Integer integer2 = Integer.valueOf(100); //手動裝箱
 
        int b = integer1.intValue(); //手動拆箱
    }
}

自動裝箱和拆箱

由上面的例子我們可以看出,手動裝箱和拆箱會帶來不少的代碼量,為了減少開發(fā)者的負(fù)擔(dān),Java中提供了自動轉(zhuǎn)換機制,比如:

public class Test {
    public static void main(String[] args) {
        Integer integer = 100; //自動裝箱
        int a = integer; //自動拆箱
    }
}

一道面試題

以下代碼輸出什么? 

public class Test {
 
    public static void main(String[] args) {
        Integer a1 = 100;
        Integer a2 = 100;
        System.out.println(a1 == a2);
        Integer a3 = 200;
        Integer a4 = 200;
        System.out.println(a3 == a4);
    }
}

結(jié)果是:true false 

為什么是這樣的答案?這里我們?nèi)タ匆幌聦?yīng)的字節(jié)碼文件再分析:

通過觀察字節(jié)碼文件,我們可以看到,在自動裝箱的過程中,調(diào)用了 Integer.valueOf 方法,那么我們就去看一看 valueOf 方法中做了一件什么事:

通過查看源碼,我們也能看出此方法將始終緩存 -128到127范圍內(nèi)的值, 通過查看對應(yīng)的 low 和 high 值也可也發(fā)現(xiàn) low為 -128,high為127,cache 是一個緩存數(shù)組。

接著我們來閱讀下這段代碼的操作,如果傳入的值是介于 -128和127 之間,則直接返回緩存數(shù)組對應(yīng)下標(biāo)的值,比如傳入的值是 -127 也就返回 chache[-127+(-(-128))],也即1下標(biāo)位置的值!

如果超出了 -128到127 的范圍則是新 new 一個對象返回,只要是 new 就一定是一個新對象,地址也是唯一的。

而且引用類型用 == 比較,比較的是引用的對象的地址,看完上面的介紹,你能弄明白為什么輸出 true 和 false 嗎?

泛型方法

定義泛型方法的語法:

方法限定符 <類型形參列表> 返回值類型 方法名稱(形參列表) {

        //...code

}  

普通泛型方法

這里我們就舉一個很簡單的例子:

public class Test {
    public <T> T getValue(T value) {
        return value;
    }
    public static void main(String[] args) {
        Test test = new Test();
        int ret = test.<Integer>getValue(150); //不使用類型推導(dǎo)
        System.out.println(ret);
 
        double d = test.getValue(12.5); //使用類型推導(dǎo)
        System.out.println(d);
    }
}

這就是泛型方法,這里面有個關(guān)鍵詞,類型推導(dǎo),什么是類型推導(dǎo)呢?

類型推導(dǎo)就是編譯器會根據(jù)你傳參的數(shù)據(jù),自動推斷出你要傳遞的類型實參,你也可以不使用類型推導(dǎo),他們的效果都是一樣的。

靜態(tài)泛型方法

既然有普通泛型方法,同理,也有靜態(tài)的泛型方法,也就是在修飾符后面加上 static,靜態(tài)泛型方法跟普通靜態(tài)方法一樣,都是通過類名訪問,不依賴于對象:

public class Test {
    public static<T> T getValue(T value) {
        return value;
    }
    public static void main(String[] args) {
        int ret = Test.<Integer>getValue(150); //不使用類型推導(dǎo)
        System.out.println(ret);
 
        double d = getValue(12.5); //使用類型推導(dǎo)(靜態(tài)方法可以直接訪問同類中靜態(tài)方法,可以不借助類名)
        System.out.println(d);
    }
}

通配符 

引出通配符 

我們先來看這樣的一段代碼:

class Message<T> {
    private T message ;
    public T getMessage() {
        return message;
    }
    public void setMessage(T message) {
        this.message = message;
    }
}
public class TestDemo {
    public static void fun(Message<String> temp){
        System.out.println(temp.getMessage());
    }
    public static void main(String[] args) {
        Message<String> message = new Message<>();
        message.setMessage("歡迎來到籃球哥的博客!");
        fun(message);
    }
}

如果你仔細(xì)觀察,TestDemo 類中的 fun 方法是有局限性的,他的形參就限制了傳過來的 Missage類的類型必須是String,也就是說,形參能接收的對象的類型參數(shù)必須是String類型。

所以如果我們 new Missage對象時,類型實參傳的是 Integer 呢?fun方法就會報錯:

所以為了解決以上的問題,就有了通配符的概念!

認(rèn)識通配符

泛型T是確定的類型,一旦傳類型了,就定下來了,而通配符的出現(xiàn),就會使得更靈活,或者說更不確定,就好像他是一個垃圾箱,可以接收所有的泛型類型,但又不能讓用戶隨意更改!

通配符:? 

現(xiàn)在我們就把上面的代碼更改一下,運用上通配符:

public class TestDemo {
    public static void fun(Message<?> temp){
        System.out.println(temp.getMessage());
    }
    public static void main(String[] args) {
        Message<Integer> message1 = new Message<>();
        message1.setMessage(123);
        fun(message1);
        Message<String> message2 = new Message<>();
        message2.setMessage("歡迎來到籃球哥的博客!");
        fun(message2);
    }
}

這樣我們的代碼就不會出錯,但是,你不能通過 fun 方法去修改你傳遞對象的內(nèi)容,為什么呢?

站在 fun 的角度,他使用了 ? 接收可以任意泛型類,所以他不能確定自己接收了什么對象的!也就無法對對象的值進(jìn)行更改! 

這樣代碼還是不夠好,如果真的什么泛型類都能接收,那不是亂套了,所以在此基礎(chǔ)上,又增加了通配符的上界和下界!

通配符的上界

語法:<? extends 上界>   例如:<? extends Person>

表示只能接收的實參類型是 Person 或者 Person的子類

圖例:

這里我們寫一段偽代碼,更改上面用例的方法:

public static void fun(Message<? extends Person> temp){
        //temp.setMessage(new Student()); //仍然無法修改!
        //temp.setMessage(new Person()); //仍然無法修改!
        Person person = temp.getMessage();
        System.out.println(person);
}

為什么還是不能修改對象的屬性呢?

因為 temp 接收的是 Person 或 Person的子類,此時接收的是哪個子類無法確定,也就無法設(shè)置對象的屬性。

因為我們知道只能接收 Person以及他的子類,所以我們就可以拿 Person 類型來接收 getMessage 的對象,因為 Person是他們的父類,獲取的是子類對象就可以實現(xiàn)向上轉(zhuǎn)型,是安全的。

總結(jié): 通配符的上界,不能進(jìn)行寫入數(shù)據(jù),只能進(jìn)行讀取數(shù)據(jù)。

通配符的下界

語法:<? extends 下界>   例如:<? super Person> 

表示只能接收的實參類型是 Person 或者 Person的父類!

圖例: 

這里我們寫一段偽代碼,更改上面用例的方法:

public static void fun(Message<? super Person> temp){
        temp.setMessage(new Student()); //可以修改,因為添加的是他的子類
        temp.setMessage(new Person()); //可以修改,因為添加的是他本身
        //Person person = temp.getMessage(); // 不能接收,不知道獲取的是哪個父類
        System.out.println(temp.getMessage()); //只能輸出
}

為啥下界就可以設(shè)置對象的屬性呢?

因為只能接收本身以及父類的類型,所以我們可以setMessage 傳子類對象,但是不能傳遞父類,因為修改成子類對象是向上轉(zhuǎn)型是安全的,如果 setMessaget 傳父類對象的話就是向下轉(zhuǎn)型則不安全!

為啥不能 getMessage呢?因為你不知道形參接收的類型是哪個父類,只能去輸出內(nèi)容!

總結(jié):通配符的下界,不能進(jìn)行讀取數(shù)據(jù),只能寫入數(shù)據(jù)。 

以上就是Java SE之了解泛型的詳細(xì)內(nèi)容,更多關(guān)于Java SE泛型的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • SpringBoot應(yīng)用jar包啟動原理詳解

    SpringBoot應(yīng)用jar包啟動原理詳解

    本文主要介紹了SpringBoot應(yīng)用jar包啟動原理詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-03-03
  • springboot解決XSS存儲型漏洞問題

    springboot解決XSS存儲型漏洞問題

    這篇文章主要介紹了springboot解決XSS存儲型漏洞問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-05-05
  • 純Java代碼實現(xiàn)流星劃過天空

    純Java代碼實現(xiàn)流星劃過天空

    本文給大家介紹純java代碼實現(xiàn)流星劃過天空,包括流星個數(shù),流星飛行的速度,色階,流星大小相關(guān)變量設(shè)置。對java流星劃過天空特效代碼感興趣的朋友可以參考下本文
    2015-10-10
  • Mybatis實現(xiàn)數(shù)據(jù)的增刪改查實例(CRUD)

    Mybatis實現(xiàn)數(shù)據(jù)的增刪改查實例(CRUD)

    本篇文章主要介紹了Mybatis實現(xiàn)數(shù)據(jù)的增刪改查實例(CRUD),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-05-05
  • Java實力彈彈球?qū)崿F(xiàn)代碼

    Java實力彈彈球?qū)崿F(xiàn)代碼

    這篇文章主要為大家詳細(xì)介紹了Java實力彈彈球?qū)崿F(xiàn)代碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2016-08-08
  • Lucene實現(xiàn)索引和查詢的實例講解

    Lucene實現(xiàn)索引和查詢的實例講解

    下面小編就為大家分享一篇Lucene實現(xiàn)索引和查詢的實例講解,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2017-12-12
  • Java設(shè)計模式之中介模式

    Java設(shè)計模式之中介模式

    這篇文章主要介紹了Java設(shè)計模式之中介模式,中介模式(Mediator?Pattern),屬于行為型設(shè)計模式,目的是把系統(tǒng)中對象之間的調(diào)用關(guān)系從一對多轉(zhuǎn)變成一對一的調(diào)用關(guān)系,以此來降低多個對象和類之間的通信復(fù)雜性,需要的朋友可以參考下
    2023-12-12
  • springmvc與mybatis集成配置實例詳解

    springmvc與mybatis集成配置實例詳解

    這篇文章主要介紹了springmvc與mybatis集成配置實例詳解的相關(guān)資料,非常不錯,具有參考借鑒價值,需要的朋友可以參考下
    2016-09-09
  • MyBatis快速入門(簡明淺析易懂)

    MyBatis快速入門(簡明淺析易懂)

    MyBatis是支持普通SQL查詢,存儲過程和高級映射的優(yōu)秀持久層框架。mybatis的學(xué)習(xí)是程序員的必修課。今天小編通過分享本教程幫助大家快速入門mybatis,對mybatis入門知識感興趣的朋友參考下吧
    2016-11-11
  • Java項目開發(fā)中實現(xiàn)分頁的三種方式總結(jié)

    Java項目開發(fā)中實現(xiàn)分頁的三種方式總結(jié)

    這篇文章主要給大家介紹了關(guān)于Java項目開發(fā)中實現(xiàn)分頁的三種方式,通過這一篇文章可以很快的學(xué)會java分頁功能,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-02-02

最新評論