偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

深入解析 Java 泛型的魅力與機(jī)制

開(kāi)發(fā) 前端
泛型的出現(xiàn),不僅僅是一種語(yǔ)法上的創(chuàng)新,更是對(duì)編程思維和代碼質(zhì)量的一次重大提升。它讓我們?cè)谔幚砀鞣N數(shù)據(jù)類型時(shí)能夠更加得心應(yīng)手,賦予了代碼更強(qiáng)的通用性、安全性和可讀性。

在 Java 的廣袤編程世界中,有一個(gè)特性如同閃耀的星辰,為代碼的編寫(xiě)帶來(lái)了深刻的變革和無(wú)盡的優(yōu)勢(shì),它就是泛型。當(dāng)我們踏入 Java 泛型的領(lǐng)域,就仿佛打開(kāi)了一扇通往更高層次編程境界的大門。

泛型的出現(xiàn),不僅僅是一種語(yǔ)法上的創(chuàng)新,更是對(duì)編程思維和代碼質(zhì)量的一次重大提升。它讓我們?cè)谔幚砀鞣N數(shù)據(jù)類型時(shí)能夠更加得心應(yīng)手,賦予了代碼更強(qiáng)的通用性、安全性和可讀性。無(wú)論是構(gòu)建復(fù)雜的數(shù)據(jù)結(jié)構(gòu),還是實(shí)現(xiàn)高效的算法邏輯,泛型都在背后默默發(fā)揮著至關(guān)重要的作用。

一、泛型使用示例

1. 泛型接口

接口定義,可以看到我們只需在接口上增加泛型聲明<T>即可,后續(xù)我們?cè)诶^承時(shí)可以具體指明類型約束實(shí)現(xiàn)類,同樣也可以不指明。

/**
 * 泛型接口
 *
 * @param <T>
 */
public interface GeneratorInterface<T> {
    T getVal();
}

下面就是不指明類型的實(shí)現(xiàn)類,通過(guò)這種抽象的方式在后續(xù)的使用時(shí),我們就可以靈活設(shè)置類型了。

/**
 * 實(shí)現(xiàn)泛型接口不指定類型
 * @param <T>
 */
public class GeneratorImpl<T> implements GeneratorInterface<T> {
    @Override
    public T getVal() {
        return null;
    }
}

就像下面這樣,在創(chuàng)建示例時(shí)指明:

public class Main {
    public static void main(String[] args) {
        GeneratorInterface<String> generatorInterface=new GeneratorImpl<>();
        generatorInterface.getVal();
    }
}

當(dāng)然我們也可以在創(chuàng)建這個(gè)類時(shí)指明:

/**
 * 泛型接口指定類型
 */
public class GeneratorImpl2 implements GeneratorInterface<String> {
    @Override
    public String getVal() {
        return null;
    }

}

2. 泛型方法

聲明泛型方法的方式很簡(jiǎn)單,只需在返回類型前面增加一個(gè)<E> 即可:

public class GeneratorMethod {
    /**
     * 泛型方法
     * @param array
     * @param <E>
     */
    public static <E> void printArray(List<E> array){
        for (E e : array) {
            System.out.println(e);
        }
    }
    
}

如此一來(lái),我們就可以在使用時(shí)靈活指定元素類型:

 public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("11");
        list.add("11");
        list.add("11");
        list.add("11");
        list.add("11");
        list.add("11");
        GeneratorMethod.printArray(list);
    }

3. 泛型類

與泛型接口用法差不多,在類名后面增加<T>即可。

/**
 * 泛型類的用法
 * @param <T>
 */
public class GenericObj<T> {
    private T key;

    public T getKey() {
        return key;
    }

    public void setKey(T key) {
        this.key = key;
    }

   
}

通過(guò)抽象泛型,在創(chuàng)建時(shí)靈活指令類型,從而約束泛型類的參數(shù)類型:

public class Main {
    public static void main(String[] args) {
        GenericObj<Integer> obj=new GenericObj();
        obj.setKey(1);

    }
}

二、泛型的使用場(chǎng)景

泛型大部分是應(yīng)用于項(xiàng)目開(kāi)發(fā)中通用對(duì)象例如我們常用的Map:

public interface Map<K,V> {
 //......略
 //通過(guò)泛型接口的泛型約束鍵值對(duì)類型
 V put(K key, V value);
}

三、詳解java泛型常見(jiàn)知識(shí)點(diǎn)

1. 為什么說(shuō)Java是一門偽泛型語(yǔ)言

Java本質(zhì)就一門偽泛型語(yǔ)言,泛型的作用僅僅在編譯期間進(jìn)行類型檢查的,一旦生成字節(jié)碼之后,關(guān)于泛型的一切都會(huì)消失。

如下所示,Integer類型數(shù)組我們完全可以通過(guò)反射將字符串存到列表中。

public static void main(String[] args) throws Exception {
        List<Integer> list=new ArrayList<>();
        list.add(1);
//        list.add("s"); 報(bào)錯(cuò)
        Class<? extends List> clazz=list.getClass();
//        java的泛型時(shí)偽泛型,運(yùn)行時(shí)就會(huì)被擦除
        Method add = clazz.getDeclaredMethod("add", Object.class);
        add.invoke(list,"k1");
        System.out.println(list);

    }

同樣的,設(shè)計(jì)者將Java泛型在編譯器后擦除的原因還有如下原因:

  • 避免引入泛型創(chuàng)建沒(méi)必要的新類型
  • 節(jié)約虛擬機(jī)開(kāi)銷

這一點(diǎn)我們用如下的例子就能看出,相同參數(shù)不通泛型的方法根本不能重載

2. 泛型存在的意義

說(shuō)到這里很多讀者可能會(huì)問(wèn):既然編譯器要把泛型擦除,為什么還要用泛型呢?用Object不行嘛?

使用泛型后便于集合的取操作,且提高的代碼的可讀性。

如下代碼所示,雖然一下代碼在編譯后會(huì)擦除為Object類型,但是通過(guò)泛型限定后,JVM就會(huì)自動(dòng)將其強(qiáng)轉(zhuǎn)為Comparable類型,減少我們編寫(xiě)一些沒(méi)必要的代碼。

public class Test2 {
    public static void main(String[] args) {
        List<? extends Comparable> list=new ArrayList<>();
        for (Comparable comparable : list) {
            comparable.compareTo("1");
        }
    }
}

3. 橋方法是什么

橋方法其實(shí)并不是什么高大上的概念,無(wú)非是繼承泛型類并指定泛型類型,IDE會(huì)自動(dòng)為我們創(chuàng)建構(gòu)造方法調(diào)用父類的有參構(gòu)造函數(shù)確保泛型多態(tài)類。

泛型類,指定了一個(gè)有參構(gòu)造函數(shù):

public class Node<T> {

    public T data;

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

    public void setData(T data) {
        System.out.println("Node.setData");
        this.data = data;
    }
}

實(shí)現(xiàn)類,自動(dòng)補(bǔ)充構(gòu)造方法并調(diào)用父類構(gòu)造方法確保實(shí)現(xiàn)泛型的多態(tài)性。

public class MyNode extends Node<Integer>{

    //繼承泛型類后自動(dòng)添加的,用于保證泛型的多態(tài)性
    public MyNode(Integer data) {
        super(data);
    }
}

4. 泛型的限制

泛型不可以被實(shí)例化:泛型會(huì)在編譯器擦除,所以泛型在編譯器還未知,所以不可被實(shí)例化。

泛型參數(shù)不可以是基本類型:我們都知道泛型僅在編譯器存在,當(dāng)編譯結(jié)束泛型就會(huì)被擦除,對(duì)象就會(huì)編程Object類型,所以基本類型作為泛型參數(shù)ide就會(huì)直接報(bào)錯(cuò)。

泛型無(wú)法被實(shí)例化,無(wú)論是泛型變量還是泛型數(shù)組,從上文我們就知道泛型會(huì)在編譯期完成后被擦除,這正是因?yàn)镴VM不想為泛型創(chuàng)建新的類型造成沒(méi)必要的開(kāi)銷。

不能拋出或者捕獲T類型的泛型異常,之所以catch使用泛型會(huì)編譯失敗,是因?yàn)槿粢敕盒秃?,編譯器無(wú)法直到這個(gè)錯(cuò)誤是否是后續(xù)catch類的父類。

不能聲明泛型錯(cuò)誤:如下所示,泛型會(huì)在編譯器被擦除,那么下面這段代碼的catch就等于catch兩個(gè)一樣的錯(cuò)誤,出現(xiàn)執(zhí)行矛盾。

try{
}catch(Problem<String> p){
}catch(Problem<Object> p){
}

不能聲明兩個(gè)參數(shù)一樣泛型不同的方法:編譯器擦除后,參數(shù)一樣,所以編譯失敗

泛型不能被聲明為static,泛型只有在類創(chuàng)建時(shí)才知曉,而靜態(tài)變量在類加載無(wú)法知曉,故無(wú)法通過(guò)編譯。

5. 泛型的通配符

(1) 什么是通配符

道通配符是解決泛型之間無(wú)法協(xié)變的問(wèn)題,當(dāng)我們使用一種類型作為泛型參數(shù)時(shí),卻無(wú)法使用他的父類或者子類進(jìn)行賦值,而通配符就是解決這種問(wèn)題的對(duì)策。

(2) 上界通配符

有時(shí)我們不知道子類的具體類型,上界通配符就是用于解決那些父類引用指向子類泛型引用的場(chǎng)景,所以上界通配符的設(shè)計(jì)增強(qiáng)了代碼的通用性。

對(duì)此我們給出一段示例,首先定義父類。

/**
 * 水果父類
 */
public class Fruit {
}

對(duì)應(yīng)的子類代碼如下。

/**
 * 水果的子類 蘋果
 */
public class Apple extends Fruit {
}

然后封裝水果類的容器代碼。

/**
 * 容器類
 * @param <T>
 */
public class Container<T> {
    private T data;

    public T getData() {
        return data;
    }

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

測(cè)試代碼如下,可以看到上界通配符使得蘋果類可以作為水果類的指向引用,即上界通配符是用于子類的上界,方便父類管理子類。

/**
 * 泛型測(cè)試
 */
public class TestParttern {
    public static void main(String[] args) {
     //上界通配符限定引用的父類,使得container可以指向繼承Fruit的Apple
        Container<? extends Fruit> container=new Container<Apple>();
        Fruit data = container.getData();
        //報(bào)錯(cuò),下文贅述
        container.setData(new Apple());
    }
}

那么問(wèn)題來(lái)了。為什么上界通配符只能get不能set?如上代碼所示,當(dāng)我們用上界通配符? extends Fruit,我們用其子類作為泛型參數(shù),這只能保證我們get到的都是這個(gè)子類的對(duì)象。

但我們卻忘了一點(diǎn),當(dāng)我們用子類apple作為泛型參數(shù)時(shí),泛型的工作機(jī)制僅僅是對(duì)這個(gè)對(duì)象加個(gè)一個(gè)編號(hào)CAP#1,當(dāng)我set一個(gè)新的對(duì)象,編譯器無(wú)法識(shí)別這個(gè)對(duì)象類型是否和編號(hào)匹配。

更通俗的理解,上界通配符決定可以指向的容器,但是真正使用是并不知曉這個(gè)容器是哪個(gè)子類容器。所以無(wú)法set。

(3) 下界通配符

還是以上文的例子進(jìn)行演示,只不過(guò)通配符改為下界通配符:

/**
 * 泛型測(cè)試
 */
public class TestParttern {

    public static void main(String[] args) {
     
        Container<? super Apple> container1=new Container<Fruit>();
    }
}

下界通配符決定了泛型的最大粒度的上限,通俗來(lái)說(shuō)只要是蘋果類的父親都可以作為被指向的引用,通過(guò)super聲明,它可以很直觀的告訴我們泛型參數(shù)必須傳super后的父類如下所示

 Container<? super Apple> container1=new Container<Fruit>();

為什么下界通配符只能set不能get(或者說(shuō)get的是object)?原因如下:

  • 下界通配符決定泛型的類型上限,所有水果類的父親都可以作為指向的引用
  • get時(shí)無(wú)法知曉其具體為哪個(gè)父親,所以取出來(lái)的類型只能是object
 Container<? super Apple> container1=new Container<Fruit>();
Object data = container1.getData();

6. 如何獲取泛型類型

public class GenericType<T> {
    private T data;

    public T getData() {
        return data;
    }

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

    public static void main(String[] args) {
    //注意這個(gè)類要使用子類,筆者為了方便期間使用了 {}
        GenericType<String> genericType = new GenericType<String>() {};
        Type superclass = genericType.getClass().getGenericSuperclass();
        //getActualTypeArguments 返回確切的泛型參數(shù), 如Map<String, Integer>返回[String, Integer]
        Type type = ((ParameterizedType) superclass).getActualTypeArguments()[0]; 
        System.out.println(type);//class java.lang.String
    }
}

7. 判斷泛型是否編譯通過(guò)

(1) 基于泛型類型比較

    public final class Algorithm {
        public static <T> T max(T x, T y) {
            return x > y ? x : y;
        }
    }
答:錯(cuò)誤,T類型未知,無(wú)法比較,編譯失敗

(2) 基于泛型創(chuàng)建靜態(tài)單例

    public class Singleton<T> {

        public static T getInstance() {
            if (instance == null)
                instance = new Singleton<T>();

            return instance;
        }

        private static T instance = null;
    }

答案

不能,泛型不能被static修飾

四、常見(jiàn)面試題

1. 什么是泛型?有什么好處

通過(guò)指定泛型的提升代碼抽象等級(jí),提升代碼復(fù)用性。

泛型界定元素的類型避免各種強(qiáng)轉(zhuǎn)操作,且在編譯器即可完成泛型檢查,提升程序安全性。

2. 什么是類型擦除

java是一門偽泛型語(yǔ)言,我們?yōu)樵刂付ǚ盒椭?,?shí)際執(zhí)行原理是編譯后將object強(qiáng)轉(zhuǎn)為泛型設(shè)定的類型。

3. 泛型中上下界限定符extends 和 super有什么區(qū)別

extend T 決定類型的上界,它決定這個(gè)泛型的類型必須是T或者T的子類,super決定傳入類型的超類型必須是指定的類型。

使用時(shí),我們也一般使用PECS原則,即生產(chǎn)時(shí)(從元素中獲取數(shù)據(jù))使用extends 這樣讀取時(shí)的類型可以指定為extends的父類及其子類型:

List<? extends Number> list = new ArrayList<>();
        //從列表中讀取,可以理解為列表在生產(chǎn),這里面拿到的元素可以是Number類型的子類
        for (Number number : list) {
            //do something 
        }

消費(fèi)時(shí)使用super。這樣添加元素時(shí)就可以可以指定超類型是T的類及其子類:

List<? super Number> list = new ArrayList<>();
        //add 寫(xiě)入可以理解為列表在消費(fèi)元素,通過(guò)super可以傳入超類型為Number的元素
        list.add(1);
        list.add(2.0);
責(zé)任編輯:趙寧寧 來(lái)源: 寫(xiě)代碼的SharkChili
相關(guān)推薦

2020-10-20 10:17:20

Java泛型Type

2009-08-26 09:36:03

C#泛型

2009-06-11 17:31:27

Java泛型

2024-12-18 21:37:24

2017-11-14 14:41:11

Java泛型IO

2009-06-16 11:32:00

Java泛型

2024-09-30 09:13:14

協(xié)調(diào)通信機(jī)制

2009-03-17 16:22:13

Java泛型接口

2024-11-05 09:11:09

TypeScript開(kāi)發(fā)者代碼

2010-10-08 10:42:30

2011-07-10 13:45:35

JAVA泛型

2011-07-12 16:00:39

java泛型

2025-06-03 04:10:00

2013-03-26 13:55:45

Android Bro

2011-04-13 09:16:55

泛型

2019-09-04 00:20:10

JSON泛型擦除

2020-11-10 07:09:59

Java泛型語(yǔ)言

2025-03-27 05:25:00

2009-09-25 10:03:51

Java泛型

2021-06-17 06:51:32

Java泛型Java編程
點(diǎn)贊
收藏

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