1. 程式人生 > 程式設計 >Java 包教不包會系列——泛型

Java 包教不包會系列——泛型

前言

第一次接觸 java 的時候,一次編譯,到處執行,給人的感覺就很強大,必須入坑啊。不過現在 java 主流使用場景還是後端這塊,圖形化介面開發並不擅長。java 在後端語言的競爭中依然屹立不倒,和 java 的生態有很大關係,但不可否認的是,java 在高併發場景下的穩定性確實可靠。今天還看了一個關於夠浪(golang) 語言的評價,也準備考慮入門下作為技術儲備。有時間學習的時候,我一定要多學點。

最近打算花時間重拾 java,決定看看 jdk 原始碼,再提升一個層次。也想寫一個關於 java 和 SpringBoot 的系列部落格。

泛型

JDK 1.5 之前

當我入坑 java 的時候,那時候還是 jdk 1.7 的天下。不過泛型確實是個好東西。沒有泛型之前我們可能會這樣寫程式碼

    @Test
    public void run1() {
        // 有個集合你想儲存 String 型別資料
        List list =new ArrayList();

        list.add("1111");

        String o = (String) list.get(0);
        System.out.println(o.length());

        // 可能會導致別的開發人員儲存的是 int 型別
        list.add(2222);
    }
複製程式碼

上述程式碼存在的問題:

  • 集合儲存的元素需要強制型別轉換,才能使用對應的 Api。
  • 編譯期語法檢測不到我提到的這個問題,在執行時期會有隱患

JDk 1.5

1.7 增加的特性 菱形泛型我摻雜到一起說了

        // 有個集合你想儲存 String 型別資料
        List<String> list =new ArrayList();

        list.add("1111");

        String s = list.get(0);
        System.out.println(s.length());

        // 下列會在編譯時期就會報錯
        // list.add(2222);
複製程式碼

是不是喜大普奔,不那麼反人類了

泛型

泛型:引數型別化

  • 泛型可以在類上或方法宣告,方法上的泛型優先順序高於類上的優先順序。為便於理解最好不和類上宣告相同命名。
  • 靜態方法不能使用類上泛型宣告,只能在方法上宣告
  • 常用的泛型宣告(T,E,K,V,?)
    • ?表示不確定的 java 型別
    • T (type) 表示具體的一個java型別
    • K V (key value) 分別代表java鍵值中的Key Value
    • E (element) 代表Element
    • S、U、V 等:多引數情況中的第 2、3、4 個型別

程式碼驗證

  • 泛型可以在類上或方法宣告,方法上的泛型優先順序高於類上的優先順序
public class ClientTest<T> {

    /**
     * 驗證泛型可以在類上和方法上宣告
     * 不能這樣 <K1 super Parent>
     */

    public <T2 extends ArgsParent> T2 run2(T k) {
        System.out.println(k.getName());
        System.out.println(k);
        return (T2) k;
    }
}
複製程式碼

以上驗證了泛型可以在類上和方法上宣告

package com.fly.study.java.generics;

import org.junit.Before;
import org.junit.Test;

import java.util.ArrayList;
import java.util.List;

/**
 * @author 張攀欽
 * @date 2019-09-16-00:04
 * @description 泛型驗證
 */
public class ClientTest<T> {

    private ClientTest clientTest;

    private Parent parent;

    private Son son;

    private ClientTest<Son> sonClientTest;

    private ArgsParent argsParent;

    private ArgsSon argsSon;


    /**
     * 限制方法引數的泛型和返回值型別,泛型優先使用方法上的,當方法上沒有使用類上的
     * 不能這樣 <K1 super Parent>
     */

    public <T extends ArgsParent,T2 extends ArgsParent> T2 run2(T k) {
        System.out.println(k.getName());
        System.out.println(k);
        return (T2) k;
    }



    @Before
    public void before() {
        clientTest = new ClientTest();
        parent = new Parent();
        parent.setName("parent");

        son = new Son();
        son.setAge(18);

        sonClientTest = new ClientTest<>();

        argsParent = new ArgsParent();
        argsParent.setName("argsParent");
        argsSon = new ArgsSon();
    }

    @Test
    public void test2() {
        System.out.println(clientTest.run2(argsParent));
        System.out.println(clientTest.run2(argsSon));
        // 驗證方法上的泛型優先於類上的泛型
        sonClientTest.run2(argsSon);
//        下面用法錯誤
//        sonClientTest.run2(son);
    }
}

@Data
public class ArgsParent {
    private String name;
}

@Data
public class ArgsSon extends ArgsParent {
    private Integer age;
}

@Data
public class Parent {
    private String name;
}

@Data
public class Son extends Parent {
    private Integer age;
}
複製程式碼

我在類上和方法上使用了同一個泛型 T ,但是型別限制不同,類上泛型 T 我在 new 物件的時候是 ClientTest sonClientTest,而我在方法上指定了T 為 ,執行方法的時候,如果引數只能傳入 ArgsParent及其子類即可驗證我的結論。

        // public class ClientTest<T> 
        // public <T extends ArgsParent,T2 extends ArgsParent> T2 run2(T k)
        // argsSon 為 ArgsParent 的子類物件
        sonClientTest.run2(argsSon);
        //  下面用法錯誤,son 為 Son 的例項物件
        //  sonClientTest.run2(son);
複製程式碼
  • 靜態方法不能使用類上泛型宣告,只能再方法上宣告
public class ClientTest<T> {
    /**
     * 靜態方法上的泛型不能使用類上的,只能再方法上宣告泛型
     */
    public static <T extends ArgsParent,T2 extends ArgsParent> void run4(T k) {
        System.out.println(k);
    }
  	
  	// 語法錯誤
    public static  void run4(T k) {
        System.out.println(k);
    }
}
複製程式碼
  • 泛型限定符

字面意思理解泛型限定符很方便,程式碼中的註釋即可說明意思

// 限定引數型別只能為 ArgsParent及其子類
<S extends ArgsParent>

// 語法錯誤,jdk 1.8
<S super ArgsParent>

// 限定引數型別只能為 ArgsParent 及其子類
<? extends ArgsParent>

// 限定引數只能為 ArgsParent 及其父類
<? extends ArgsParent>
複製程式碼
public class AllGenerics<T> {

    public void run1(T t) {
        System.out.println(t);
    }

    public <S> void run2(S s) {
        System.out.println(s);
    }
    
    // 限定引數型別只能為 ArgsParent及其子類
    public <S extends ArgsParent> void run3(S s) {
        System.out.println(s);
    }
    
    // 語法錯誤,
    // public <S super ArgsParent> void run3(S s) {
    // System.out.println(s);
    //}
    
    // 限定引數型別只能為 ArgsParent及其子類
    public void run4(List<? extends ArgsParent>  s) {
        System.out.println(s);
    }

    // 限定引數只能為 ArgsParent及其父類
    public void run5(List<? super ArgsParent>  s) {
        System.out.println(s);
    }
}
複製程式碼

泛型擦除

泛型只在編譯期有效,編譯之後會對泛型宣告進行替換,對於能確定型別的使用確定型別,不確定的使用 Object 代替。

  • 不能確定泛型型別的時候
// 原始碼
public class AllGenerics<T> {
     public void run1(T t) {
        System.out.println(t);
    }
}

// 編譯之後的程式碼可以這樣理解

public class AllGenerics<Object> {
     public void run1(Object t) {
        System.out.println(t);
    }
}

複製程式碼
  • 對使用泛型限定符
public class AllGenerics<T> {
     public <S extends ArgsParent> void run2(S s) {
        System.out.println(s);
    }
}

// 編譯之後的程式碼可以這樣理解

public class AllGenerics<ArgsParent> {
     public void run1(ArgsParent t) {
        System.out.println(t);
    }
}
複製程式碼

程式碼驗證

  • 泛型只在編譯時期有效
@Data
public class ClientTestSuperT {
    @Test
    public void test1() throws Exception{
        List<String>t=new ArrayList<>();
        Method add = t.getClass().getMethod("add",Object.class);
        
        // 新增 Integer 型別數字
        add.invoke(t,1);
        System.out.println(t);
    }
}
複製程式碼
  • 泛型擦除使用可以確定的型別替換
package com.fly.study.java.generics;


import org.junit.Test;

import java.lang.reflect.Method;


public class AllGenerics<T> {

    public <S extends ArgsParent> void run2(S s) {
        System.out.println(s);
    }

    public void run1(T t) {
        System.out.println(t);
    }

    @Test
    public void test1() throws Exception {
        AllGenerics<String> allGenerics =new AllGenerics<>();
        Method method= allGenerics.getClass().getMethod("run1",Object.class);
// java.lang.NoSuchMethodException:
// com.fly.study.java.generics.AllGenerics.run1(java.lang.String)
//      Method method= allGenerics.getClass().getMethod("run1",String.class);
        method.invoke(allGenerics,"111");
    }

    @Test
    public void run2() throws Exception {
        AllGenerics<String> allGenerics =new AllGenerics<>();
        Method method= allGenerics.getClass().getMethod("run2",ArgsParent.class);
        ArgsParent argsParent = new ArgsParent();
        argsParent.setName("測試");
// java.lang.NoSuchMethodException: 
// com.fly.study.java.generics.AllGenerics.run1(java.lang.String)
//  Method method= allGenerics.getClass().getMethod("run2",Object.class);
        method.invoke(allGenerics,argsParent);
    }

}
複製程式碼
  • 驗證思路
AllGenerics<T> 編譯之後 AllGenerics<Object>
    
    // 編譯之後為 public void run1(Object t)
    public void run1(T t) {
        System.out.println(t);
    }
    
    // 編譯之後 public void run2(ArgsParent s)
    public <S extends ArgsParent> void run2(S s) {
        System.out.println(s);
    }
複製程式碼
  • 利用反射獲取Method run1,引數 Object 的方法能獲取,而獲取 run1,引數 String 的找不到

@Test
public void test1() throws Exception {
    AllGenerics<String> allGenerics =new AllGenerics<>();
    Method method= allGenerics.getClass().getMethod("run1",Object.class);
    method.invoke(allGenerics,"111");
}
複製程式碼

上述程式碼可以正常執行,驗證編譯之後程式碼確實替換了

@Test
public void test1() throws Exception {
    AllGenerics<String> allGenerics =new AllGenerics<>();
    Method method= allGenerics.getClass().getMethod("run1",String.class);
    method.invoke(allGenerics,"111");
}
複製程式碼

上述程式碼執行報錯 java.lang.NoSuchMethodException:com.fly.study.java.generics.AllGenerics.run1(java.lang.String),說明編譯之後的程式碼中沒有這個方法

  • 對於使用限定型別的泛型,替換為限定的型別
// 方法編譯之後為:public  void run2(ArgsParent s)
public <S extends ArgsParent> void run2(S s) {
    System.out.println(s);
}
複製程式碼
@Test
public void run2() throws Exception {
    AllGenerics<String> allGenerics =new AllGenerics<>();
    Method method= allGenerics.getClass().getMethod("run2",ArgsParent.class);
    ArgsParent argsParent = new ArgsParent();
    argsParent.setName("測試");
    method.invoke(allGenerics,argsParent);
}
複製程式碼

以上獲取到了方法,說明我的猜測正確

@Test
public void run2() throws Exception {
    AllGenerics<String> allGenerics =new AllGenerics<>();
    ArgsParent argsParent = new ArgsParent();
    argsParent.setName("測試");
    Method method= allGenerics.getClass().getMethod("run2",argsParent);
}
複製程式碼

程式碼報錯,找不到對應的方法

推薦閱讀

java泛形理解

泛型:工作原理及其重要性