欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java?SE之了解泛型

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

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

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

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

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

那么這種方法可取嗎?

顯然是可取的,但只是使用起來會(huì)很不方便,具體不方便在哪,我們接著往后看,在這里我們要寫一個(gè)類,里面提供了獲取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];
    }
}

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

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

泛型的概念

淺聊泛型

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

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

泛型的簡(jiǎn)單語法

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

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

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

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

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

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

這里就來修改一下剛開始的代碼,使用到泛型的知識(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);
    }
}

如上修改之后的代碼,我們可以得到以下知識(shí)點(diǎn):

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

什么是裸類型

裸類型就是指在實(shí)例化泛型類對(duì)象的時(shí)候,沒有傳類型實(shí)參,比如下面的代碼就是一個(gè)裸類型:

DrawForth draw = new DrawForth();

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

泛型是如何編譯的?

泛型的擦除機(jī)制

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

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

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

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

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

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

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

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

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

什么是泛型的上界?

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

語法:

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

這里我們來舉兩個(gè)例子:

例1:

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

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

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

例2:

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

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

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

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

再談擦除機(jī)制

如果給泛型設(shè)置了上界,則會(huì)擦除到邊界處,也就不會(huì)擦除成 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è)定了泛型的上界,傳的類型實(shí)參必須是Person的子類,所以編譯時(shí)會(huì)不會(huì)被擦除成 Person呢?下面我們查看一下對(duì)應(yīng)的字節(jié)碼文件: 

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

包裝類的知識(shí)

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

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

裝箱和拆箱

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

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

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

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

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

自動(dòng)裝箱和拆箱

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

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

一道面試題

以下代碼輸出什么? 

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)タ匆幌聦?duì)應(yīng)的字節(jié)碼文件再分析:

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

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

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

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

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

泛型方法

定義泛型方法的語法:

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

        //...code

}  

普通泛型方法

這里我們就舉一個(gè)很簡(jiǎn)單的例子:

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);
    }
}

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

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

靜態(tài)泛型方法

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

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,也就是說,形參能接收的對(duì)象的類型參數(shù)必須是String類型。

所以如果我們 new Missage對(duì)象時(shí),類型實(shí)參傳的是 Integer 呢?fun方法就會(huì)報(bào)錯(cuò):

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

認(rèn)識(shí)通配符

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

通配符:? 

現(xiàn)在我們就把上面的代碼更改一下,運(yù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);
    }
}

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

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

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

通配符的上界

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

表示只能接收的實(shí)參類型是 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);
}

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

因?yàn)?temp 接收的是 Person 或 Person的子類,此時(shí)接收的是哪個(gè)子類無法確定,也就無法設(shè)置對(duì)象的屬性。

因?yàn)槲覀冎乐荒芙邮?Person以及他的子類,所以我們就可以拿 Person 類型來接收 getMessage 的對(duì)象,因?yàn)?Person是他們的父類,獲取的是子類對(duì)象就可以實(shí)現(xiàn)向上轉(zhuǎn)型,是安全的。

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

通配符的下界

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

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

圖例: 

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

public static void fun(Message<? super Person> temp){
        temp.setMessage(new Student()); //可以修改,因?yàn)樘砑拥氖撬淖宇?
        temp.setMessage(new Person()); //可以修改,因?yàn)樘砑拥氖撬旧?
        //Person person = temp.getMessage(); // 不能接收,不知道獲取的是哪個(gè)父類
        System.out.println(temp.getMessage()); //只能輸出
}

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

因?yàn)橹荒芙邮毡旧硪约案割惖念愋停晕覀兛梢詓etMessage 傳子類對(duì)象,但是不能傳遞父類,因?yàn)樾薷某勺宇悓?duì)象是向上轉(zhuǎn)型是安全的,如果 setMessaget 傳父類對(duì)象的話就是向下轉(zhuǎn)型則不安全!

為啥不能 getMessage呢?因?yàn)槟悴恢佬螀⒔邮盏念愋褪悄膫€(gè)父類,只能去輸出內(nèi)容!

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

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

相關(guān)文章

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

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

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

    springboot解決XSS存儲(chǔ)型漏洞問題

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

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

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

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

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

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

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

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

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

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

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

    springmvc與mybatis集成配置實(shí)例詳解

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

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

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

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

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

最新評(píng)論