泛型
學習目標:
- 掌握泛型的基本
原理及應用 - 掌握泛型
通配符的使用 - 指定泛型操作中的
上限及下限 - 在
介面上應用泛型 - 掌握
泛型方法及泛型陣列的使用
這里針對的是JDK1.5本身的泛型特性,JDK1.5之后在類集和反射機制中已經大量使用泛型,需要結合類集框架及反射機制,
什么是泛型:
就是指在物件創建時不指定類中屬性的具體型別,而由外部在宣告和實體化物件時指定具體的型別,
1.為什么要使用泛型
引入案例:
要求設計一個可以表示坐標點的類,坐標由X和Y組成,坐標的表示方法有以下三種:
- 整數表示:x = 10,y = 20
- 小數表示:x = 10.5,y = 20.6
- 字串表示:x = "東經180度", y = "北緯210度"
案例分析:
- 首先要創建一個表示坐標點的類:Point
- Point類中有兩個屬性,分別表示x坐標和y坐標
- x和y坐標的資料型別有3種:(int、float、String)
- 需要使用一種型別同時接收3種型別資料,只能使用object,使用Object類接收任何資料型別,都會自動向上轉型,
- int --> 自動裝箱成Integer --> 向上轉型使用Object接收
- float --> 自動裝箱成Float --> 向上轉型使用Object接收
- String --> 向上轉型使用Object接收
代碼設計:
Point類
class Point{ private Object x; private Object y; public void setX(Object x){ this.x = x; } public void setY(Object y){ this.y = y; } public Object getX(){ return this.x; } public Object getY(){ return this.y; } }
使用整數表示坐標:
public class GenericsDemo{ public statci void main(String[] args){ Point p = new Point(); p.setX(10); //利用自動裝箱操作:int --> Integer -->Object p.setY(20); //利用自動裝箱操作:int --> Integer -->Object int x = (Integer)p.getX(); //取出資料先變為Integer,之后自動拆箱 int y = (Integer)p.getY(); //取出資料先變為Integer,之后自動拆箱 System.out.println("整數表示,X坐標為:" + x); System.out.println("整數表示,Y坐標為:" + y); } }
使用小數表示坐標
public class GenericsDemo { public static void main(String[] args) { Point p = new Point(); p.setX(10.5f); //利用自動裝箱操作:float --> Float -->Object p.setY(20.6f); //利用自動裝箱操作:float --> Float -->Object float x = (Float) p.getX();//取出資料先變為Float,之后自動拆箱 float y = (Float) p.getY();//取出資料先變為Float,之后自動拆箱 System.out.println("小數表示,X坐標為:" + x); System.out.println("小數表示,Y坐標為:" + y); } }
使用字串表示坐標
public class GenericsDemo { public static void main(String[] args) { Point p = new Point(); p.setX("東經180度"); //String --> Object p.setY("北緯210度"); //String --> Object String x = (String) p.getX(); //取出資料 String y = (String) p.getY(); //取出資料 System.out.println("字串表示,X坐標為:" + x); System.out.println("字串表示,Y坐標為:" + y); } }
以上三個程式已經證明Point類符合要求,但是是存在問題的,
可以把X設定成數字,Y設定為字串,程式在編譯的時候不會出現錯誤,但是運行的時候會報錯,
public class GenericsDemo { public static void main(String[] args) { Point p = new Point(); p.setX(10); p.setY("北緯210度"); int x = (Integer) p.getX(); int y = (Integer) p.getY(); System.out.println("整數表示,X坐標為:" + x); System.out.println("整數表示,Y坐標為:" + y); } }程式出現類轉換例外,因為String類無法向Integer類轉換,出現這個問題的原因就是Point類中使用了Object型別接收屬性,造成了型別安全問題,要絕解決這個問題就需要使用泛型,
2.泛型的應用
1.基本應用
泛型可以解決資料型別的安全問題,原理:是在類宣告時通過一個標識標識類中某個屬性的型別或者某個方法的回傳值及引數型別,這樣類在宣告或者實體化時只要指定好需要的具體型別即可,
泛型類定義:
[訪問權限] class 類名稱<泛型型別標識1,泛型型別標識2,...泛型型別標識n>{
? [訪問權限] 泛型型別標識 變數名稱;
? [訪問權限] 泛型型別標識 方法名稱(){};
? [訪問權限] 回傳值型別宣告 方法名稱(泛型型別標識 變數名稱){};
}
泛型物件定義:
類名稱<具體型別> 物件名稱 = new 類名稱<具體型別>();
宣告泛型:
class Point<T>{ //此處可以是任意的識別符號號 private T var; //此變數的型別由外部決定 public T getVar(){ //回傳值的型別由外部指定 return var; } public void setVar(T var){ //設定的型別由外部指定 this.var = var; } } public class Demo01 { public static void main(String[] args) { Point<Integer> integerPoint = new Point<Integer>(); //里面的var型別為Integer型別 integerPoint.setVar(30); //設定數字,自動裝箱 System.out.println(integerPoint.getVar()*2); //計算結果按數字取出, } }
關于整型設定的問題:
只能使用包裝類;在泛型的指定中是無法使用基本資料型別的,必須設定成一個類,這樣在設定一個數字時就必須使用包裝類,
注意:
如果傳進來的值和泛型所指定的型別不一致,則在編譯的時候會報錯,
加入泛型后使得程式的操作更加安全,避免了型別轉換例外(ClassCastException)的發生,
2.泛型中的構造方法
定義: [訪問權限] 構造方法([泛型型別 引數名稱]){}
與普通的構造方法并無不同,只是引數型別使用泛型表示,
案例:
class Point<T>{ //此處可以是任意的識別符號號 private T var; //此變數的型別由外部決定 public Point(T var){ //構造方法 this.var = var; } public T getVar(){ //回傳值的型別由外部指定 return var; } public void setVar(T var){ //設定的型別由外部指定 this.var = var; } } public class Demo01 { public static void main(String[] args) { Point<String> p = null; p = new Point<String>("Java"); System.out.println(p.getVar()); } }
3.指定多個泛型型別
如果一個類中有多個屬性需要使用不同的泛型宣告,則可以在宣告類時指定多個泛型型別,
案例:
設定多個泛型型別
class Notepad<K,V>{ private K key; private V value; public K getKey() { return key; } public void setKey(K key) { this.key = key; } public V getValue() { return value; } public void setValue(V value) { this.value = https://www.cnblogs.com/kwstudy/archive/2023/01/07/value; } } public class Demo01 { public static void main(String[] args) { Notepadnotepad = null; notepad = new Notepad (); notepad.setKey("工資"); notepad.setValue(30000); System.out.println("資金來源:" + notepad.getKey()); System.out.println("金額:" + notepad.getValue()); } }
3.泛型的安全警告
泛型在應用中最好在宣告和實體化類物件時,指定好其內部的資料型別,如Info<String>,如果不指定型別,就會出現不安全操作的警告資訊,
案例:
不指定泛型型別
class Info<T>{ private T var; public T getVar() { return var; } public void setVar(T var) { this.var = var; } } public class Demo01 { public static void main(String[] args) { Info info = new Info(); //警告沒有指定泛型型別 info.setVar("Java"); System.out.println("內容:" + info.getVar()); } }以上程式會在編譯的時候出現警告,但是并不影響程式的運行,
如果沒有指定泛型型別,則所有的型別統一使用Object進行接收
以上程式的var屬性實際就變成了Object型別,也就是相當于在定義時將泛型擦除了,
以上程式相當于下面這個程式:
public class Demo01 { public static void main(String[] args) { Info<Object> info = new Info<Object>(); //指定Object為泛型型別 info.setVar("Java"); System.out.println("內容:" + info.getVar()); } }

4.通配符
在泛型操作中可以通過通配符接收任意指定泛型型別的物件(不需要設定一個固定的型別),
1.匹配任意型別的通配符
在泛型類的操作中,進行參考傳遞時泛型型別必須匹配才可以傳遞,否則是無法傳遞的,
案例:
使用泛型宣告后的物件參考傳遞問題
class Info<T>{ private T var; public T getVar() { return var; } public void setVar(T var) { this.var = var; } } public class Demo01 { public static void main(String[] args) { Info<String> stringInfo = new Info<String>(); //指定String為泛型型別 stringInfo.setVar("Java"); //為屬性賦值 fun(stringInfo); //錯誤無法傳遞 } public static void fun(Info<Object> temp){ //此處可以接收Object泛型型別的Info物件 System.out.println("內容:" + temp); } }程式在編譯時會報錯,
注意:盡管String是Object類的子類,但是在進行參考傳遞時也同樣無法進行操作,
可以將方法fun()中定義的Info(Object)修改為Info,即不指定泛型,
如下:
class Info<T>{ private T var; public T getVar() { return var; } public void setVar(T var) { this.var = var; } } public class Demo01 { public static void main(String[] args) { Info<String> stringInfo = new Info<String>(); //指定String為泛型型別 stringInfo.setVar("Java"); //為屬性賦值 fun(stringInfo); } public static void fun(Info temp){ //此處可以接收Info物件 System.out.println("內容:" + temp); } }但是這樣做時,Info中并沒有指定任何的泛型型別,是不妥當的,為了解決這個問題可以使用Java中的通配符"?"
通配符:
"?"表示可以接收此型別的任意泛型物件
使用通配符"?"
class Info<T>{ private T var; public T getVar() { return var; } public void setVar(T var) { this.var = var; } } public class Demo01 { public static void main(String[] args) { Info<String> stringInfo = new Info<String>(); //指定String為泛型型別 stringInfo.setVar("Java"); //為屬性賦值 fun(stringInfo); } public static void fun(Info<?> temp){ //此處可以接收Object泛型型別的Info物件 System.out.println("內容:" + temp); } }
注意:
-
fun()方法中使用Info<?>的代碼形式,表示可以使用任意的泛型型別物件,
-
如果使用"?"接收泛型物件,則不能設定被泛型指定的內容,
-
public class Demo01 { public static void main(String[] args) { Info<?> stringInfo = new Info<String>(); //使用"?"接收泛型 stringInfo.setVar("Java"); //錯誤,無法設定 } } -
可以設定為null值
public class Demo01 { public static void main(String[] args) { Info<?> stringInfo = new Info<String>(); //使用"?"接收泛型 stringInfo.setVar(null); //null,可以設定 } }
-
2.受限泛型
參考傳遞中,在泛型操作中也可以設定一個泛型物件的范圍上限和范圍下限,
- 范圍上限:使用
extends關鍵字宣告,表示泛型的型別可能是所指定的型別或者此型別的子類, - 范圍下限:使用
super關鍵之宣告,表示泛型的型別可能是所指定的型別,或者此型別的父類,或是Object類,
格式:
- 設定上限:
- 宣告物件:
類名稱<? extends 類> 物件名稱 - 定義類:
[訪問權限] 類名稱<泛型標識 extends 類>{}
- 宣告物件:
- 設定下限:
- 宣告物件:
類名稱<? super 類> 物件名稱 - 定義類:
[訪問權限] 類名稱<泛型標識 extends 類>{}
- 宣告物件:
1.泛型的上限
案例:
假設一個方法中能夠接收的泛型物件只能是數字(Byte、Short、Long、Integer、Float、Double)型別,此時在定義方法引數接收物件時,就必須指定泛型的上限,所有的數字包裝類都是Number型別的子類,
設定方法只能接收泛型為Number或Number型別的子類
class Info<T>{ private T var; public T getVar() { return var; } public void setVar(T var) { this.var = var; } public String toString(){ return this.var.toString(); } } public class Demo01 { public static void main(String[] args) { Info<Integer> i1 = new Info<Integer>(); //宣告Integer的泛型物件 Info<Float> i2 = new Info<Float>(); //宣告Float的泛型物件 i1.setVar(30); i2.setVar(30.1f); fun(i1); fun(i2); } // 接收Info物件,范圍上限設定為Number,所以只能接收數字型別 public static void fun(Info<? extends Number> temp){ System.out.print(temp + "、"); } }輸出:
30、30.1、
注意:以上程式中的fun()方法只能接收數字型別的Info類的泛型物件,如果傳遞的是一個String類的泛型物件,就會編譯錯誤,
class Info<T>{ private T var; public T getVar() { return var; } public void setVar(T var) { this.var = var; } public String toString(){ return this.var.toString(); } } public class Demo01 { public static void main(String[] args) { Info<String> i1 = new Info<String>(); //宣告String的泛型物件 i1.setVar("Hello"); fun(i1); } // 接收Info物件,范圍上限設定為Number,所以只能接收數字型別 public static void fun(Info<? extends Number> temp){ System.out.print(temp + "、"); } }
也可以直接在類的宣告處指定泛型的上限范圍???
class Info<T extends Number>{ //此處泛型只能是數字型別 private T var; public T getVar() { return var; } public void setVar(T var) { this.var = var; } public String toString(){ return this.var.toString(); } }以上代碼Info類中的泛型范圍是所有的數字,如果此時宣告的泛型物件是Number的子類,不會出現問題;如果不是Number的子類就會出現問題,
正確宣告:
public class Demo01 { public static void main(String[] args) { Info<Integer> i1 = new Info<Integer>(); //宣告Integer的泛型物件 System.out.println("內容: " + i1); } }錯誤的宣告:
public class Demo01 { public static void main(String[] args) { Info<String> i1 = new Info<String>(); //宣告String的泛型物件 System.out.println("內容: " + i1); } }
2.泛型的下限
應用場景:
當使用的泛型只能在本類及其父型別別上應用時,就必須使用泛型的范圍下限進行配置,
案例:
class Info<T>{ //此處泛型只能是數字型別 private T var; public T getVar() { return var; } public void setVar(T var) { this.var = var; } public String toString(){ return this.var.toString(); } } public class Demo01 { public static void main(String[] args) { Info<Object> i1 = new Info<Object>(); //宣告Object的泛型物件 Info<String> i2 = new Info<String>(); //宣告String的泛型物件 i1.setVar(new Object()); i2.setVar("Java"); fun(i1); fun(i2); } // 只能接收String或Object型別的泛型 public static void fun(Info<? super String> temp){ System.out.println("內容:" + temp); } }在fun()方法中,Info進行了下限的配置,只能接收泛型是String和Object型別的參考,
5.泛型與子類繼承的限制
注意:
一個類的子類可以通過物件的多型性為其父類實體化,
但是
在泛型操作中:子類的泛型型別是無法使用父類的泛型型別接收的,
例如: Info<String>不能使用 Info<Object>接收,
下面的程式編譯時會報錯:
public class Demo01{ public static void main(String[] args){ Info<String> i1 = new Info<String>(); //泛型型別為String Info<Object> i2 = null; //泛型型別為Object i2 = i1; //兩個Info物件進行轉換,Info<String> --> Info<Object> } }問題:這里為什么不能使用向上轉型,
回答:如果將子類泛型變為父類泛型,則表示擴大了子類的內容,
6.泛型介面
可以在介面上宣告泛型,宣告格式在介面名稱后面加<T>即可,格式:
泛型介面: [訪問權限] interface 介面名稱<泛型標識>{}
泛型介面的實作方式:
- 直接在子類后宣告泛型
- 直接在子類實作的介面中明確的給出泛型型別
案例:
直接在子類的定義上宣告泛型interface Info<T>{ //在介面上定義泛型 public T getVar(); } class InfoImpl<T> implements Info<T>{ //定義泛型介面的子類 private T var; //定義屬性 public InfoImpl(T var){ //通過構造方法設定屬性內容 this.var = var; } @Override public T getVar() { return var; } public void setVar(T var) { this.var = var; } }以上程式泛型介面的子類宣告了與介面中同樣的泛型標識
使用泛型介面的子類
public class Demo01 { public static void main(String[] args) { Info<String> info = null; //定義介面物件 info = new InfoImpl<String>("Java"); //通過子類實體化Info物件 System.out.println("內容:" + info.getVar()); } }以上程式使用了泛型,但是依然可以使用物件的多型性通過一個子類為介面實體化,
案例:
直接在子類實作的介面中明確的給出泛型型別interface Info<T>{ //在介面上定義泛型 public T getVar(); } class InfoImpl implements Info<String>{ //定義泛型介面的子類,指定型別為String private String var; //定義屬性 public InfoImpl(String var){ //通過構造方法設定屬性內容 this.var = var; } @Override public String getVar() { return var; } public void setVar(String var) { this.var = var; } }以上程式,泛型介面的子類在實作介面時,直接在實作的介面處指定了具體的泛型型別String,
使用泛型介面的子類
public class Demo01 { public static void main(String[] args) { Info<String> info = null; //定義介面物件 info = new InfoImpl("Java"); //通過子類實體化Info物件,不用指定泛型 System.out.println("內容:" + info.getVar()); } }
7.泛型方法
可以在類中定義泛型化的方法,
注意:泛型方法所在的類可以是泛型類,也可以不是泛型類,
1.定義泛型方法
注意:泛型方法中可以定義泛型引數,引數的型別就是傳入的資料型別,
格式: [訪問權限] <泛型標識> 泛型標識 方法名稱([泛型標識 引數名稱])
案例:定義一個泛型方法,
class Demo{ public <T> T fun(T t){ //可以接收任意型別的資料 return t; } } public class Demo01 { public static void main(String[] args) { Demo demo = new Demo(); String str = demo.fun("Java"); //傳遞字串 int i = demo.fun(30); //傳遞數字,自動裝箱 System.out.println(str); System.out.println(i); } }<T>的意思是:方法中傳入或回傳的泛型型別由呼叫方法時所傳入的引數型別決定,
2.通過泛型方法回傳泛型類實體
案例:通過方法回傳泛型類實體
class Info<T extends Number>{ private T var; public T getVar() { return var; } public void setVar(T var) { this.var = var; } public String toString(){ return this.var.toString(); } } public class Demo01 { public static void main(String[] args) { Info<Integer> i = fun(30); System.out.println(i.getVar()); } public static <T extends Number> Info<T> fun(T param){ Info<T> temp = new Info<T>(); //根據傳入的資料型別實體化Info物件 temp.setVar(param); return temp; //回傳實體化物件 } }注意: <T extends Number>的意思是,方法中傳入或回傳的泛型型別由呼叫方法時所傳入的引數型別決定,
3.使用泛型統一傳入的引數型別
傳入一個方法的泛型物件的泛型型別必須一致
案例:統一傳入物件的泛型型別
class Info<T>{ private T var; public T getVar() { return var; } public void setVar(T var) { this.var = var; } public String toString(){ return this.var.toString(); } } public class Demo01 { public static void main(String[] args) { Info<String> info1 = new Info<String>(); //設定String為泛型型別 Info<String> info2 = new Info<String>(); info1.setVar("HELLO"); info2.setVar("kang"); add(info1, info2); } public static <T> void add(Info<T> info1,Info<T> info2){ System.out.println(info1.getVar() + " " + info2.getVar()); } }注意:add()方法中的兩個Info物件的泛型型別必須一致,
8.泛型陣列
使用泛型方法時也可以傳遞或回傳一個泛型陣列,
案例:接收和回傳泛型陣列,
public class Demo01 { public static void main(String[] args) { Integer[] i = fun1(1,2,3,4,5,6); //回傳泛型陣列 fun2(i); //輸出陣列內容 } public static <T> T[] fun1(T...arg){ //接收可變引數,回傳泛型陣列 return arg; //回傳泛型陣列 } public static <T> void fun2(T[] param){ //接收泛型陣列 System.out.println("接收泛型陣列:"); for (T t : param) { System.out.print(t + "、"); } System.out.println(); } }
9.泛型的嵌套設定
在一個類的泛型中指定另一個類的泛型
案例: 定義兩個泛型類
class Info<T,V>{ //指定兩個泛型型別 private T var; private V value; public Info(T var, V value) { this.var = var; this.value = https://www.cnblogs.com/kwstudy/archive/2023/01/07/value; } public T getVar() { return var; } public void setVar(T var) { this.var = var; } public V getValue() { return value; } public void setValue(V value) { this.value = value; } } class Demo{ private S info; public Demo(S info){ this.setInfo(info); } public S getInfo(){ return info; } public void setInfo(S info){ this.info = info; } }Info類需要指定兩個泛型型別,Demo類需要指定一個泛型型別,可以將Info設定成Demo的泛型型別,
設定嵌套泛型
public class Demo01 { public static void main(String[] args) { Demo< > demo = null; //將Info設定成Demo的泛型型別 Info<String, Integer> info = null; //指定Info類的兩個泛型型別 info = new Info<String,Integer>("工資",30000); demo = new Demo<Info<String,Integer>>(info); //在Demo類中設定Info類物件 System.out.println("內容一:" + demo.getInfo().getVar()); System.out.println("內容二:" + demo.getInfo().getValue()); } }輸出:
內容一:工資
內容二:30000
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/541431.html
標籤:其他
下一篇:淺談PHP設計模式的狀態模式
