自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Java 泛型編程所說(shuō)的類型擦除到底是什么?

開發(fā) 前端
正是類型擦除的機(jī)制幫助 Java 實(shí)現(xiàn)了泛型編程,讓我們作為開發(fā)者能夠更好的了解和控制我們正在使用類型的是什么,而不是 Object 滿天飛。

大部分語(yǔ)言都支持泛型,泛型是一種語(yǔ)言機(jī)制,各種語(yǔ)言的實(shí)現(xiàn)機(jī)制都不太一樣,例如C++使用模板方式來(lái)實(shí)現(xiàn)泛型,而 Java 中用類型擦除機(jī)制來(lái)實(shí)現(xiàn)泛型。

什么是泛型

在 Java 中,不會(huì)泛型,寸步難行。泛型可能是一個(gè) Java 初學(xué)者需要攻克的第一個(gè)難點(diǎn)。隨便跟著一門教程或 任何一本《Java入門到精通》,前面關(guān)于變量、關(guān)鍵字、語(yǔ)法(if、while、for等等)這些基本上是一看就懂,而當(dāng)內(nèi)容來(lái)到泛型的時(shí)候,大部分人可能就突然感覺(jué)沒(méi)那么輕松了。

如果沒(méi)有編程經(jīng)驗(yàn)的話,可能需要練習(xí)一段時(shí)間才能完全掌握泛型編程概念和技巧,這么說(shuō)吧,有些人寫了好幾年代碼,碰到泛型的時(shí)候可能還是不太熟練。

說(shuō)到Java泛型,最明顯的標(biāo)志就是 <> 。

泛型是什么呢?通俗的說(shuō)就是一個(gè)類型是沒(méi)有固定類型的,即可以是Integer 也可以是 Long,還可能是你自定義的類。

泛型使類型(類和接口)能夠在定義類、接口和方法時(shí)成為參數(shù)。與方法聲明中使用的更熟悉的形式參數(shù)非常相似,類型參數(shù)為您提供了一種通過(guò)不同輸入重復(fù)使用相同代碼的方法。區(qū)別在于形式參數(shù)的輸入是值,而類型參數(shù)的輸入是類型。

例如在類定義中使用泛型,最常見的 ArrayList

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
//... code
}

例如在方法參數(shù)中使用泛型,來(lái)一個(gè)復(fù)雜的例子

public static <T extends Number & Comparable<T>, U extends List<T>, R extends T> R complexMethod(U list, T element) {

}

在這個(gè)例子中,有兩個(gè)傳入?yún)?shù) U list, T element,而這兩個(gè)參數(shù)需要在方法的返回類型前用<>做出說(shuō)明,也就是 <T extends Number & Comparable<T>, U extends List<T>, R extends T>這一部分。

返回值也是一個(gè)泛型 R。

為什么是 T、U、R

經(jīng)??吹椒盒皖愋陀?T、U、R,還有K、V 這樣的符號(hào)表示。我們肯定知道不用T也完全沒(méi)問(wèn)題,用 X 也可以。

之所以這么統(tǒng)一是因?yàn)檫@是官方比較推薦的寫法,推薦的規(guī)則如下:

  • E - 表示一個(gè)元素,例如集合元素、數(shù)組元素
  • K - 表示一個(gè) Key,鍵值對(duì)經(jīng)常用到,與之對(duì)應(yīng)的是 V
  • V - 表示一個(gè) Value,鍵值對(duì)經(jīng)常用
  • N - 表示 Number(數(shù)字類型)
  • T - 這個(gè)見得最多,表示一個(gè)類型 Type,不管是基礎(chǔ)類型還是自定義的類

泛型的作用

前面也說(shuō)了,當(dāng)一個(gè)參數(shù)預(yù)期可能有多種類型的時(shí)候,就會(huì)用到泛型,那既然是類型不確定,那直接用 Object 不就行了嗎,何必費(fèi)事兒呢?一會(huì)兒講到類型擦除的時(shí)候會(huì)發(fā)現(xiàn),本身類型擦除的核心就是把泛型類型轉(zhuǎn)為 Object。但是這是編譯器干的,為了給JVM看的。而作為開發(fā)者和編譯器,使用泛型還是有很大好處的。

1、在編譯時(shí)提供更嚴(yán)格的類型檢查,如果代碼違反類型安全,編譯器可以及時(shí)發(fā)現(xiàn),而不是等到運(yùn)行的時(shí)候拋出運(yùn)行時(shí)異常。

2、使程序員能夠?qū)崿F(xiàn)通用算法。通過(guò)使用泛型,程序員可以實(shí)現(xiàn)適用于不同類型集合的泛型算法,可以自定義,并且類型安全且更易于閱讀。

例如下面這個(gè)方法,只接受Number 類型的參數(shù),用來(lái)比較兩數(shù)的大小。

public static <T extends Number> Boolean compare(T first, T second) {
        double firstValue = first.doubleValue();
        double secondValue = second.doubleValue();

        return firstValue > secondValue;
    }

3、消除不必要的類型轉(zhuǎn)換。

例如下面不用泛型的情況,每次取數(shù)據(jù)的時(shí)候都要轉(zhuǎn)換一下類型。

List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0);

而用了泛型后,就不用自己轉(zhuǎn)換了。

List<String> list = new ArrayList<String>();
list.add("hello");
String s = list.get(0);

類型擦除

Java 中的泛型實(shí)現(xiàn)可以說(shuō)就是用的類型擦除原理。通俗一點(diǎn)說(shuō),類型只在編譯期存在,在運(yùn)行時(shí)就不在了,都變?yōu)榱?Object,一視同仁。

在我們寫好代碼進(jìn)行編譯時(shí),編譯器會(huì)將泛型參數(shù)的類型進(jìn)行替換,大部分情況下會(huì)將類型替換為 0bject 類型。這種行為模式用類型擦除來(lái)描述就非常形象。

類型擦除原理

在類型擦除過(guò)程中,Java 編譯器會(huì)擦除所有類型參數(shù),如果類型參數(shù)有界,則用其第一個(gè)邊界替換每個(gè)參數(shù);如果類型參數(shù)無(wú)界,則用 Object 替換。

在類型擦除過(guò)程中,編譯器會(huì)按照以下規(guī)則來(lái)處理泛型類型參數(shù):

如果類型參數(shù)有界(bounded type),即使用了extends關(guān)鍵字限定了類型的上界,例如<T extends Number>,則編譯器會(huì)用該類型的第一個(gè)邊界來(lái)替換類型參數(shù)。

例如下面這個(gè)例子,泛型 T 繼承了Number類型,又實(shí)現(xiàn)了 Displayable 接口(沒(méi)錯(cuò),泛型可以這樣定義)

interface Displayable {
    void display();
}

public class Result<T extends Number & Displayable> {
    private T value;

    public Result(T value) {
        this.value = value;
    }

    public T getValue() {
        return value;
    }
    
 public void show() {
        value.display();
    }
}

在編譯器進(jìn)行類型擦除后會(huì)變成下面這樣,因?yàn)?T 的上限是 Number,所以直接將 T 替換為 Number。

public class Result {
    private Number value;

    public Result(Number value) {
        this.value = value;
    }

    public Number getValue() {
        return value;
    }
}

如果類型參數(shù)無(wú)界(unbounded type),即沒(méi)有限定類型的上界,例如<T>,則編譯器會(huì)用Object類型來(lái)替換類型參數(shù)。

例如下面方法,沒(méi)有指定類型上限類型。

public static <T> int count(T[] anArray, T elem) {
    int cnt = 0;
    for (T e : anArray)
        if (e.equals(elem))
            ++cnt;
        return cnt;
}

經(jīng)過(guò)編譯器的擦除處理后,就變成下面這樣,都替換成了 Object。

public static int count(Object[] anArray, Object elem) {
    int cnt = 0;
    for (Object e : anArray)
        if (e.equals(elem))
            ++cnt;
        return cnt;
}

橋接方法

來(lái)看一下下面這段代碼

public class Node<T> {

    public T data;

    public Node(T data) { this.data = data; }

    public void setData(T data) {
        this.data = data;
    }
}

public class SubNode extends Node<Integer> {
    public SubNode(Integer data) { super(data); }

    public void setData(Integer data) {
        super.setData(data);
    }
    
 public static void main(String[] args) {
        SubNode subNode = new SubNode(8);
        Node node = subNode;
        node.setData("Hello");
        Integer x = subNode.data;
    }

}

這段代碼大家一看就知道肯定是有問(wèn)題的,運(yùn)行的時(shí)候會(huì)出現(xiàn) ClassCastException,但是編譯是可以通過(guò)的。

而運(yùn)行時(shí)出現(xiàn)錯(cuò)誤的代碼是 node.setData("Hello");這一行,但是經(jīng)過(guò)前面對(duì)類型擦除的了解,Node 類的 setData 參數(shù)肯定被擦除成了 Object 類型了,既然是 Object,那Integer 和 String 都滿足啊,為啥還會(huì)報(bào)錯(cuò)呢。

這就要說(shuō)到橋接了。

當(dāng)編譯器對(duì)泛型擴(kuò)展的類或接口進(jìn)行編譯處理的時(shí)候,會(huì)根據(jù)實(shí)際的類型進(jìn)行方法的橋接處理。什么意思呢,還是拿上面的 Node 和 SubNode 類說(shuō)明。

類型擦除后的代碼是下面這樣的,多了一個(gè)橋接方法。

public class Node {

    public Object data;

    public Node(Object data) { this.data = data; }

    public void setData(Object data) {
        this.data = data;
    }
}

public class SubNode extends Node {

    public SubNode(Integer data) { super(data); }

 /**
 ** 橋接方法
 **/
 public void setData(Object data) {
        setData((Integer) data);
    }

    public void setData(Integer data) {
        super.setData(data);
    }
}

為什么需要這個(gè)橋接方法呢?

Node 類的 setData 方法入?yún)⑹?Object 類型。

public void setData(Object data) {
    this.data = data;
}

而 SubNode 的setData 方法入?yún)⑹?Integer。

public void setData(Integer data) {
 super.setData(data);
}

所以,SubNode 的 setData 方法并不會(huì)重寫父類 Node 的setData 方法,而想要重寫的話,就必須讓 SubNode 的setData 的入?yún)⒁彩?Object,這就是橋接方法的由來(lái)。

public void setData(Object data) {
 setData((Integer) data);
}

這樣一來(lái)重寫父類的方法,但是要把參數(shù)強(qiáng)轉(zhuǎn)成 Integer。

前面說(shuō)的 node.setData("Hello");這一行會(huì)報(bào)錯(cuò),那大家就知道為什么了吧,是因?yàn)榘?nbsp;Hello強(qiáng)轉(zhuǎn)為 Integer 的時(shí)候出現(xiàn)的錯(cuò)誤。

總結(jié)

正是類型擦除的機(jī)制幫助 Java 實(shí)現(xiàn)了泛型編程,讓我們作為開發(fā)者能夠更好的了解和控制我們正在使用類型的是什么,而不是 Object 滿天飛。

責(zé)任編輯:武曉燕 來(lái)源: 古時(shí)的風(fēng)箏
相關(guān)推薦

2023-03-29 08:24:46

Rune類型開源項(xiàng)目

2021-08-24 08:05:41

泛型類型擦除Class

2023-10-11 08:29:54

volatileJava原子性

2020-03-05 10:28:19

MySQLMRR磁盤讀

2022-10-08 00:00:00

Spring數(shù)據(jù)庫(kù)項(xiàng)目

2011-04-27 09:30:48

企業(yè)架構(gòu)

2020-10-14 06:22:14

UWB技術(shù)感知

2010-11-01 01:25:36

Windows NT

2020-09-22 08:22:28

快充

2020-09-27 06:53:57

MavenCDNwrapper

2025-01-13 07:00:00

Java泛型編程

2009-06-09 22:11:44

JavaScriptObject

2020-10-27 10:26:03

編程開發(fā)Java

2019-10-30 10:13:15

區(qū)塊鏈技術(shù)支付寶

2020-08-04 14:20:20

數(shù)據(jù)湖Hadoop數(shù)據(jù)倉(cāng)庫(kù)

2013-06-09 09:47:31

.NetPDBPDB文件

2021-09-03 09:12:09

Linux中斷軟件

2010-04-22 14:14:29

Live-USB

2021-01-21 21:24:34

DevOps開發(fā)工具

2023-07-12 15:32:49

人工智能AI
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)