首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

Java 泛型進階:類型邊界、通配符、PECS 原則與泛型方法實戰

     編輯:Java 綜合教學

Java 泛型進階:類型邊界、通配符、PECS 原則與泛型方法實戰

一、限制泛型可用類型:有界類型參數

1.1 語法與機制

<T extends BoundType>  // 上界:T 必須是 BoundType 或其子類
<T super BoundType>    // 下界:僅用於通配符,T 必須是 BoundType 或其父類
  • extends 關鍵字同時用於繼承類別與實作介面。
  • 未指定邊界時預設 <T extends Object>。
  • 可指定多重邊界:<T extends Comparable<T> & Serializable>(類別最多一個,介面多個)。

1.2 程式碼範例:繼承抽象類別

// 限制 T 必須為 Animal 或其子類
class GenericClass<T extends Animal> {
    private T obj;
    public void setObj(T obj) { this.obj = obj; }
    public T getObj() { return obj; }
}

abstract class Animal {
    public abstract void eat();
}
class Dog extends Animal {
    public void eat() { System.out.println("啃骨頭"); }
}
class Cat extends Animal {
    public void eat() { System.out.println("吃魚肉"); }
}

// 使用
GenericClass<Dog> dogBox = new GenericClass<>();
dogBox.setObj(new Dog());
dogBox.getObj().eat();  // 編譯期保證有 eat() 方法

// GenericClass<String> errorBox = new GenericClass<>(); // 編譯錯誤:String 非 Animal 子類

1.3 程式碼範例:實作介面

interface Animal2 { void eat2(); }
class Dog2 implements Animal2 { public void eat2() { System.out.println("啃骨頭2"); } }
class Cat2 implements Animal2 { public void eat2() { System.out.println("吃魚肉2"); } }

class GenericClass2<T extends Animal2> { ... } // 同樣運作

二、類型通配符:解決泛型不變性問題

2.1 泛型不變性痛點

Generic<Boolean> g1 = new Generic<>();
Generic<Integer> g2 = new Generic<>();
// g1 = g2; // 編譯錯誤:Generic<Boolean> 非 Generic<Integer> 子類
// Generic<Object> g3 = g1; // 編譯錯誤:泛型不變性

2.2 三種通配符形式

<?><? extends T><? super T>
語法名稱可讀取型別可寫入型別適用場景
無限定通配符Objectnull僅需 Object 方法(toString、equals)
上界通配符T 及其子類nullProducer(讀取/消費資料)
下界通配符ObjectT 及其子類Consumer(寫入/生產資料)

2.3 PECS 原則(Producer Extends, Consumer Super)

// Producer:從結構讀取資料 → 用 extends
public void printAnimals(List<? extends Animal> animals) {
    for (Animal a : animals) {  // 讀取時視為 Animal
        a.eat();
    }
    // animals.add(new Dog()); // 錯誤:不可寫入具體類型
}

// Consumer:向結構寫入資料 → 用 super
public void addDogs(List<? super Dog> animals) {
    animals.add(new Dog());     // 寫入 Dog 及其子類安全
    animals.add(new Puppy());   // Puppy 繼承 Dog
    // Animal a = animals.get(0); // 錯誤:讀取只能視為 Object
}

2.4 完整範例程式碼

public class WildcardDemo {
    public static void main(String[] args) {
        GenericClass<Dog> dogBox = new GenericClass<>();
        dogBox.setObj(new Dog());
        GenericClass<Cat> catBox = new GenericClass<>();
        catBox.setObj(new Cat());
        GenericClass<String> strBox = new GenericClass<>();

        // 1. 無限定通配符:只能讀取 Object 方法
        GenericClass<?> wildcard = dogBox;
        Object obj = wildcard.getObj();  // 僅能當 Object
        // wildcard.setObj(new Dog()); // 錯誤:不可寫入

        // 2. 上界通配符:可讀取 Animal 方法
        GenericClass<? extends Animal> upper = dogBox;
        Animal a = upper.getObj();       // 安全讀取為 Animal
        a.eat();                         // 呼叫 Animal 方法
        // upper.setObj(new Dog());     // 錯誤

        // 3. 下界通配符:可寫入 Dog 及子類
        GenericClass<? super Dog> lower = new GenericClass<Animal>();
        lower.setObj(new Dog());         // 寫入 Dog
        lower.setObj(new Puppy());       // 寫入 Puppy (Dog 子類)
        // lower.setObj(new Cat());     // 錯誤:Cat 非 Dog 子類
        Object o = lower.getObj();       // 讀取只能是 Object
    }
}

interface Animal { void eat(); }
class Dog implements Animal { public void eat() { System.out.println("啃骨頭"); } }
class Puppy extends Dog { public void eat() { System.out.println("喝奶"); } }
class Cat implements Animal { public void eat() { System.out.println("吃魚肉"); } }

class GenericClass<T> {
    private T obj;
    public void setObj(T obj) { this.obj = obj; }
    public T getObj() { return obj; }
}

三、泛型方法:方法級別的類型參數化

3.1 語法定義

[修飾符] <T> 回傳型別 方法名(參數列表) { ... }
  • 類型參數 <T> 置於修飾符與回傳型別之間。
  • 可用於回傳型別、參數型別、區域變數型別。
  • 靜態方法必須使用泛型方法,無法使用類別的型別參數。
  • 可重載:不同邊界的泛型方法視為不同簽名。

3.2 何時使用泛型方法而非泛型類?

  • 型別約束僅作用於單一方法的多個參數之間(如比較兩個同型別物件)。
  • 工具類靜態方法(Collections.sort、Arrays.asList)。
  • 不希望整個類別參數化,僅特定操作需要型別安全。

3.3 程式碼範例

public class GenericMethodDemo {
    public static void main(String[] args) {
        GenericUtil util = new GenericUtil();
        
        // 1. 一般泛型方法:任意型別列印
        util.println("Hello");
        util.println(123);
        util.println(true);
        util.println(3.14);
        util.println(new Dog());
        
        // 2. 有界泛型方法:僅接受 Animal 子類
        util.printlnAnimal(new Dog());  // 呼叫 eat()
        util.printlnAnimal(new Cat());
        // util.printlnAnimal("String"); // 編譯錯誤
        
        // 3. 靜態泛型方法
        GenericUtil.staticPrint("靜態泛型方法");
    }
}

class GenericUtil {
    // 一般泛型方法
    public <T> void println(T content) {
        System.out.println(content);
    }
    
    // 有界泛型方法(重載)
    public <T extends Animal> void println(T animal) {
        animal.eat();  // 安全呼叫 Animal 方法
    }
    
    // 靜態泛型方法
    public static <T> void staticPrint(T content) {
        System.out.println("Static: " + content);
    }
}

interface Animal { void eat(); }
class Dog implements Animal { public void eat() { System.out.println("啃骨頭"); } }
class Cat implements Animal { public void eat() { System.out.println("吃魚肉"); } }

四、進階主題:型別擦除與可變參數陷阱

  1. 型別擦除:泛型資訊僅存在編譯期,執行期被擦除為邊界類型(或 Object)。
  2. 不可建立泛型陣列:new T[10]、new List<String>[10] 皆非法,改用 List<List<String>>。
  3. 可變參數堆污染:<T> void foo(T... args) 會建立 Object[],若 T 為具體型別可能導致 ClassCastException,需加 @SafeVarargs 註解(Java 7+)。
  4. 橋接方法:編譯器為多態產生橋接方法,可能導致效能微損與堆疊追蹤混淆。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved