一、泛型
什么是泛型
Java 泛型(generics)是 JDK 5
中引入的一個新特性, 泛型提供了編譯時類型安全檢測機制,該機制允許程序員在編譯時檢測到非法的類型。
泛型的本質是參數化類型
,也就是說所操作的數據類型被指定為一個參數。
為什么用泛型
提高的代碼的復用性,減少了數據的類型轉換(泛型提供了類型檢查),同時保證了類型安全。
減少類型轉換?如,使用Comparable比較時每次都需要類型強轉
泛型參數類型
規則
- 使用簡練的名字作為類型形參的名字,最好為單個的大寫字母,比如 T ;
- 如果一個泛型類有泛型方法,對于它們的類型形參來說,應避免使用相同的名字;
- 泛型的類型實參只能是類類型,不能是基本數據類型。
常見的參數類型起名
- K 鍵,比如映射的鍵 key的類型
- V 值,比如Map的值 value類型
- E 元素,比如Set<E> Element表示元素,元素的類型
- T 泛型,Type的意思
注意:泛型的類型名字是可以隨便寫的,上面的K,V,E,T只是我們常用的用法,有一定含義,我們對應的把T換成HAHAHA,也算是可以的。
如何了解泛型
我們可以大概從下面幾點來開展文章。
- 泛型方法
- 泛型類
- 泛型接口
- 泛型的通配符
二、泛型方法
泛型方法定義格式
修飾符 <泛型參數列表> 返回值類型 方法名 (參數列表) {
……
}
.
.
我們先來看一個簡單的泛型方法
private static <T> void inputContent(T t){
System.out.println("打印傳入的數據:"+t);
}
簡單規則
- 泛型返回返回值(void也需要)之前需要有
泛型類型參數的聲明
,由尖括號包括,比如<T>
(也可以理解為帶有返回值前帶有泛型類型的都是參數方法 ) - 形參參數可以不是泛型參數
code1 泛型方法用法參考
public class TestClass {
public static void main(String[] args) {
inputContent(666);
inputContent("哈哈哈");
new Num("張三").say();
Num num = doClass(new Num("李四"));
num.say();
}
private static <T> void inputContent(T t){
System.out.println("打印傳入的數據:"+t);
}
private static <T> T doClass(T t){
return t;
}
}
class Num{
private String name;
public Num(String str){
this.name = str;
}
public void say(){
System.out.println(name +" 調用了say方法");
}
}
.
.
輸出
打印傳入的數據:666
打印傳入的數據:哈哈哈
張三 調用了say方法
李四 調用了say方法
.
.
code2 泛型方法,多參數類型以類型名
public class TestClass {
public static void main(String[] args) {
Map map = getAMap("張三", 18);
Set<String> set = map.keySet();
for(String key : set){
System.out.println("key "+key);
System.out.println("value "+map.get(key));
}
}
private static <HAHA extends String,XIXI> Map<String,XIXI> getAMap(HAHA haha,XIXI xixi){
Map map = new HashMap<HAHA,XIXI>();
map.put(haha, xixi);
return map;
}
}
輸出
key 張三
value 18
從這個例子中,我們看到,泛型方法返回值前的<>里面的參數可以有多個,而且,參數類型名稱我們基本可以隨便起,不局限于一個字母,比如起名為HAHA,需要注意的是,
- 我們最好起有默認含義的,比如T,K,E,V
- <>里面的泛型參數列表已經限定了參數參數的類型,方法后面的形參的泛型參數類型只能在前面聲明的類型中選擇。
至于extends是什么怎么用,后面會涉及。
三、泛型類
格式
class 類名<泛型類型1,泛型類型2……>{
……
}
簡單規則
- 在類名后面帶上<>,在<>里面聲明泛型參數列表
code3 泛型方法示例
public class TestClass {
public static void main(String[] args) {
Box<Integer> integerBox = new Box<Integer>();
Box<String> stringBox = new Box<String>();
integerBox.add(new Integer(10));
stringBox.add(new String("張三"));
System.out.println("整型值為 :"+integerBox.get());
System.out.println("字符串為 :"+ stringBox.get());
}
}
class Box<T> {
private T t;
public void add(T t) {
this.t = t;
}
public T get() {
return t;
}
}
.
.
輸出
整型值為 :10
字符串為 :張三
如上 Box類就是泛型類
.
.
.
泛型接口
泛型接口,就是在接口后面跟著一對<>,在<>里面放上泛型參數列表。
在接口里面的抽象方法,我們就可以結合接口定義上的泛型參數列表做一些事情。
當然,方法可以使用泛型參數類型必須是接口的聲明的泛型參數列表以內的。
code4 泛型接口的使用
public class TestClass {
public static void main(String[] args) {
SayClass class1 = new SayClass();
System.out.println(class1.doSomeThing());
}
}
class SayClass implements Generator<String>{
@Override
public String doSomeThing() {
String str = "被指定的類型為String";
return str;
}
}
interface Generator<T> {
// 接口的里的抽象方法繁殖可以不是T
// 但是一般這里我們指定為T才有一定意義,不然不就白泛型了
public T doSomeThing();
}
輸出
被指定的類型為String
.
.
.
四、泛型的限定符/通配符
四.1、泛型限定符的種類
- <? extends Type> 子類限定/上界通配符
- <? super Type> 父類限定 / 下界通配符
- <?> 無限定
Person<?> 和 Person<? extends Object> 等同.
為了演示,我們弄幾個類,一個父類Water,Water下有SofdDrink(汽水)和Juice(果汁),Juice下有分為OrangeJuice和AppleJuice類。
四.2、子類限定 extends
<? extends Type>
子類限定,有上界,代表傳入實參必須是指定類型的子類
- 1、只讀,不可寫
- 2、對于<? extends T>,那么限定傳入類型只能 T類或者T的子類
- 3、<T extends Runnable & Serializable>如果給T限定多個類型,則需要使用符號&,比如
<T extends Runnable & Serializable>
code5 泛型的子類限定示例
import java.util.ArrayList;
import java.util.List;
public class TestClass{
public static void main(String[] args) {
Water water = new Water("純凈水");
SodaDrink sodaDrink = new SodaDrink("可樂");
Juice juice = new Juice("果汁");
OrangeJuice orangeJuice = new OrangeJuice("果粒橙");
AppleJuice appleJuice =new AppleJuice("牛頓牌果汁");
ArrayList<OrangeJuice> tempList = new ArrayList<OrangeJuice>();
tempList.add(orangeJuice);
// ======
// 證明1、<? extends T>,那么限定傳入類型只能 T類或者T的子類
ArrayList<? extends Juice> arrayList1 = new ArrayList<Juice>();
//ArrayList<? extends Juice> arrayList2 = new ArrayList<Water>(); // 編譯報錯 Water不是Juice也不是Juice的子類
ArrayList<? extends Juice> arrayList3 = new ArrayList<OrangeJuice>();
// 證明2、ArrayList<? extends Juice> arrayList1,是只讀的,不可寫入
// 我們無法插入任何數據到arrayList1中,因為我們無法確定插入的到底是哪一種類型,是Juice? 還是OrangeJuice? 還是AppleJuice?
// 泛型extends是不可寫的,非要寫也只能是寫入null
//arrayList1.add(orangeJuice); //
// 泛型extends是可讀的
ArrayList<? extends Juice> arrayList4 = tempList;
Juice juice23 =arrayList4.get(0);
juice23.sayName();
}
}
// 商店
class Shop<T>{
private T t;
public Shop(T t){
this.t= t;
}
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
}
class Water {
private String name;
public Water(String name) {
this.name = name;
}
public void sayName() {
System.out.println("name===: "+name);
}
public String getName(){
return name;
}
}
class SodaDrink extends Water{
public SodaDrink(String name) {
super(name);
}
public void sayName() {
System.out.println("汽水/蘇打水===: "+getName());
}
}
class Juice extends Water{
public Juice(String name) {
super(name);
}
public void sayName() {
System.out.println("果汁===: "+getName());
}
}
class OrangeJuice extends Juice{
public OrangeJuice(String name) {
super(name);
}
public void sayName() {
System.out.println("橙汁===: "+getName());
}
}
class AppleJuice extends Juice{
public AppleJuice(String name) {
super(name);
}
public void sayName() {
System.out.println("蘋果汁===: "+getName());
}
}
.
打印
橙汁===: 果粒橙
如上,我們知道,對于作了子類限定的泛型
1、實參只能傳入限定類型的子類或者其子類
2、是只讀的,不能調用set,只能set(null)
? extends T 限定了類型為T和T的子類型
ArrayList<? extends Juice> arrayList1 = new ArrayList<Juice>();
ArrayList<? extends Juice> arrayList2 = new ArrayList<Water>(); // 編譯報錯 Water不是Juice也不是Juice的子類
ArrayList<? extends Juice> arrayList3 = new ArrayList<OrangeJuice>();
對于上面的代碼,arrayList1和arrayList3是合法。arrayList2會編譯報錯,因為我們限定了傳入的是Juice或者Juice的子類,但是arrayList2傳入的是Juice的父類Water。
泛型extends的讀和寫
讀
可讀并且只能讀取到限定的類型(無法讀取到子類型)
對于ArrayList<? extends Juice> arrayList1
- 可以從 arrayList1 中讀取到 Juice 對象, 因為 arrayList1 中包含的元素是 Juice 類型或 Juice 的子類型.
- 無法從 arrayList1 中讀取到 OrangeJuice 類型, 因為 arrayList1 中可能保存的是 AppleJuice 類型.
.
.
寫
不可寫,無法寫入除了null外任何元素,包括Juice類型 或者 Juice類型的子類
對于ArrayList<? extends Juice> arrayList1
- 不能添加 Juice 到 arrayList1 中, 因為 numberArray 有可能是List<AppleJuice> 類型
- 不能添加 OrangeJuice 到 numberArray 中, 因為 numberArray 有可能是 List<AppleJuice> 類型
- 不能添加 AppleJuice 到 numberArray 中, 因為 numberArray 有可能是 List<OrangeJuice> 類型
四.2、父類限定 super
- 可寫,不可讀。
- 對于<? super T>,那么限定傳入類型只能 T類或者T的父類
code6 泛型的父類限定示例
public class TestClass{
public static void main(String[] args) {
Water water = new Water("純凈水");
SodaDrink sodaDrink = new SodaDrink("可樂");
Juice juice = new Juice("果汁");
OrangeJuice orangeJuice = new OrangeJuice("果粒橙");
AppleJuice appleJuice =new AppleJuice("牛頓牌果汁");
// ======
// 證明1、<? super T>,那么限定傳入類型只能 T類或者T的父類
ArrayList<? super Juice> arrayList1 = new ArrayList<Juice>();
ArrayList<? super Juice> arrayList2 = new ArrayList<Water>();
//ArrayList<? super Juice> arrayList3 = new ArrayList<OrangeJuice>();// 編譯報錯 OrangeJuice不是Juice也不是Juice的子類
// 證明2、ArrayList<? extends Juice> arrayList1,是可寫,不可讀。
// 關于寫,我們只能寫入 Juice或者Juice的子類,而不能寫入Juice的父類
arrayList1.add(juice);
arrayList1.add(orangeJuice);
//arrayList1.add(water); // 編譯報錯
arrayList2.add(juice);
//arrayList2.add(water); // 編譯報錯
//關于讀,我們無法讀取到類型為Juice的數據,或者Juice的值類型,因為存入的可能是Juice,也可能是Juice的子類
// 唯一可以確定的是,讀取的出來的肯定是個Object,但是如果你非常強轉也是可以的
Object obj = arrayList1.get(0); // 只能確定讀出出來的是Object
Juice jui = (Juice) arrayList1.get(1);
jui.sayName();
}
}
// 商店
class Shop<T>{
private T t;
public Shop(T t){
this.t= t;
}
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
}
class Water {
private String name;
public Water(String name) {
this.name = name;
}
public void sayName() {
System.out.println("name===: "+name);
}
public String getName(){
return name;
}
}
class SodaDrink extends Water{
public SodaDrink(String name) {
super(name);
}
public void sayName() {
System.out.println("汽水/蘇打水===: "+getName());
}
}
class Juice extends Water{
public Juice(String name) {
super(name);
}
public void sayName() {
System.out.println("果汁===: "+getName());
}
}
class OrangeJuice extends Juice{
public OrangeJuice(String name) {
super(name);
}
public void sayName() {
System.out.println("橙汁===: "+getName());
}
}
class AppleJuice extends Juice{
public AppleJuice(String name) {
super(name);
}
public void sayName() {
System.out.println("蘋果汁===: "+getName());
}
}
打印輸出
橙汁===: 果粒橙
? super T 限定了類型為T和T的父類型
ArrayList<? super Juice> arrayList1 = new ArrayList<Juice>();
ArrayList<? super Juice> arrayList2 = new ArrayList<Water>();
//ArrayList<? super Juice> arrayList3 = new ArrayList<OrangeJuice>();// 編譯報錯 OrangeJuice不是Juice也不是Juice的子類
這里很清楚的說明,我們限定了傳入類型只能是限定類型的或者其父類。
泛型super的讀和寫
讀
對于 ArrayList<? super Juice> arrayList1
- 無法確定讀出來的是Juice或者OrangeJuice或者AppleJuice。
- 可以確定的是,讀出來肯定是一個Object(如果你非要強轉也行)。
.
.
寫
- 寫入的必須是T類型的或者T的子類型
.
.
.
四.3、泛型限定extends和super的使用原則 PECS
PECE 原則: Producer Extends, Consumer Super
因為extends,可讀不可寫;super可寫不可讀
所以:
1、如果我們的操作基本上是只讀的,那么用extends
2、如果我們的操作基本上是只寫的,那么用super
** 小結:extends和super **
- 阿敏說:extends
比如 ArrayList<? extends Juice> arrayList1,肯定不可以寫入,我們都不知道你放進來的是蘋果汁還是橙汁,然后別人到會來取得時候,已經限定了要一杯果汁,那我機器人這么笨怎么知道給一杯什么,所以你不要給我放進來了,因為我也無法給出去啊,多浪費啊,少年你自己喝了吧。
你這么笨機器人,你可以取,但是不不能存,沒有存哪有取,要你何用?
少年你這么說就不對了,你可以不要一個個添加嘛,你先把所有的符合我要求的數據批量準備好,比如 ArrayList<Juice> temp1 或者 ArrayList<Juice> temp2 ,數據你自己填充好,然后直接給我,這樣我給別人的時候也好給啊,我這么聰明的機器人一定不會弄錯啦。
ArrayList<Juice> tempList1 = new ArrayList<Juice>();
tempList1.add(new Juice("果汁1"));
tempList1.add(new Juice("果汁2"));
ArrayList<? extends Juice> juiceList1 = tempList1;
juiceList1.get(0).sayName();
juiceList1.get(1).sayName();
// =======
ArrayList<OrangeJuice> tempList2 = new ArrayList<OrangeJuice>();
tempList2.add(new OrangeJuice("果粒橙1"));
tempList2.add(new OrangeJuice("果粒橙2"));
ArrayList<? extends Juice> juiceList2 = tempList2;
juiceList2.get(0).sayName();
juiceList2.get(1).sayName();
輸出
果汁===: 果汁1
果汁===: 果汁2
橙汁===: 果粒橙1
橙汁===: 果粒橙2
.
- 阿敏說:super存取疑惑
.
比如 ArrayList<? super Juice> arrayList2,可以寫入,為什么,你放進來的可以是果汁,可以是蘋果汁,可以是橙汁,都沒問題。但是取是萬萬不能的,里面存放辣么多不同的飲料,或者可能不同的飲料,你說要果汁,我是機器人那么笨,我拿什么給你。
參考:
JAVA泛型?通配符限定