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

深入解析 Java 泛型的魅力與機制

開發(fā) 前端
泛型的出現(xiàn),不僅僅是一種語法上的創(chuàng)新,更是對編程思維和代碼質(zhì)量的一次重大提升。它讓我們在處理各種數(shù)據(jù)類型時能夠更加得心應手,賦予了代碼更強的通用性、安全性和可讀性。

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

泛型的出現(xiàn),不僅僅是一種語法上的創(chuàng)新,更是對編程思維和代碼質(zhì)量的一次重大提升。它讓我們在處理各種數(shù)據(jù)類型時能夠更加得心應手,賦予了代碼更強的通用性、安全性和可讀性。無論是構建復雜的數(shù)據(jù)結構,還是實現(xiàn)高效的算法邏輯,泛型都在背后默默發(fā)揮著至關重要的作用。

一、泛型使用示例

1. 泛型接口

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

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

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

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

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

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

當然我們也可以在創(chuàng)建這個類時指明:

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

}

2. 泛型方法

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

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

如此一來,我們就可以在使用時靈活指定元素類型:

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

   
}

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

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

    }
}

二、泛型的使用場景

泛型大部分是應用于項目開發(fā)中通用對象例如我們常用的Map:

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

三、詳解java泛型常見知識點

1. 為什么說Java是一門偽泛型語言

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

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

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

    }

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

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

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

2. 泛型存在的意義

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

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

如下代碼所示,雖然一下代碼在編譯后會擦除為Object類型,但是通過泛型限定后,JVM就會自動將其強轉為Comparable類型,減少我們編寫一些沒必要的代碼。

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

3. 橋方法是什么

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

泛型類,指定了一個有參構造函數(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;
    }
}

實現(xiàn)類,自動補充構造方法并調(diào)用父類構造方法確保實現(xiàn)泛型的多態(tài)性。

public class MyNode extends Node<Integer>{

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

4. 泛型的限制

泛型不可以被實例化:泛型會在編譯器擦除,所以泛型在編譯器還未知,所以不可被實例化。

泛型參數(shù)不可以是基本類型:我們都知道泛型僅在編譯器存在,當編譯結束泛型就會被擦除,對象就會編程Object類型,所以基本類型作為泛型參數(shù)ide就會直接報錯。

泛型無法被實例化,無論是泛型變量還是泛型數(shù)組,從上文我們就知道泛型會在編譯期完成后被擦除,這正是因為JVM不想為泛型創(chuàng)建新的類型造成沒必要的開銷。

不能拋出或者捕獲T類型的泛型異常,之所以catch使用泛型會編譯失敗,是因為若引入泛型后,編譯器無法直到這個錯誤是否是后續(xù)catch類的父類。

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

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

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

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

5. 泛型的通配符

(1) 什么是通配符

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

(2) 上界通配符

有時我們不知道子類的具體類型,上界通配符就是用于解決那些父類引用指向子類泛型引用的場景,所以上界通配符的設計增強了代碼的通用性。

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

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

對應的子類代碼如下。

/**
 * 水果的子類 蘋果
 */
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;
    }
}

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

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

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

但我們卻忘了一點,當我們用子類apple作為泛型參數(shù)時,泛型的工作機制僅僅是對這個對象加個一個編號CAP#1,當我set一個新的對象,編譯器無法識別這個對象類型是否和編號匹配。

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

(3) 下界通配符

還是以上文的例子進行演示,只不過通配符改為下界通配符:

/**
 * 泛型測試
 */
public class TestParttern {

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

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

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

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

  • 下界通配符決定泛型的類型上限,所有水果類的父親都可以作為指向的引用
  • get時無法知曉其具體為哪個父親,所以取出來的類型只能是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) {
    //注意這個類要使用子類,筆者為了方便期間使用了 {}
        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. 判斷泛型是否編譯通過

(1) 基于泛型類型比較

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

(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修飾

四、常見面試題

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

通過指定泛型的提升代碼抽象等級,提升代碼復用性。

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

2. 什么是類型擦除

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

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

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

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

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

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

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

2020-10-20 10:17:20

Java泛型Type

2009-08-26 09:36:03

C#泛型

2017-11-14 14:41:11

Java泛型IO

2009-06-16 11:32:00

Java泛型

2009-06-11 17:31:27

Java泛型

2024-12-18 21:37:24

2024-09-30 09:13:14

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

2009-03-17 16:22:13

Java泛型接口

2024-11-05 09:11:09

TypeScript開發(fā)者代碼

2010-10-08 10:42:30

2011-07-12 16:00:39

java泛型

2011-07-10 13:45:35

JAVA泛型

2011-04-13 09:16:55

泛型

2013-03-26 13:55:45

Android Bro

2019-09-04 00:20:10

JSON泛型擦除

2020-11-10 07:09:59

Java泛型語言

2009-09-25 10:03:51

Java泛型

2021-06-17 06:51:32

Java泛型Java編程

2017-03-06 16:51:52

Java泛型實現(xiàn)

2025-03-27 05:25:00

點贊
收藏

51CTO技術棧公眾號