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);
}
複製程式碼
程式碼報錯,找不到對應的方法