學習總結-Thinking In Java Chapter 15 generics

學習總結

本篇是對學習【Java編程思想 第 15 章 泛型】的學習總結。

一般的類和方法,只能使用具體的類型,要麼是基本類型,要麼是自定義的類。如果要編寫可以應用於多種類型的代碼,這種刻板的限制的束縛就會很大。
在面向對象編程語言中,多態算是一種泛化機制。例如,你可以將方法的參數設爲一個基類,那麼該方法就可以接受從這個基類導出的任意類型作爲參數。

自Java SE5以來,提出一個重大的變化:泛型的概念。泛型實現了參數化類型的概念。

泛型初識

泛型類

就是在類名後加上泛型參數,語法是<>裏放上我們不確定的類型,通常用大寫字母表示,常用的有A T E R,,,

public class Holder<T> {
    private T a;
    public Holder(T a) {
        this.a = a;
    }
    public T getA() {
        return a;
    }
    public void setA(T a) {
        this.a = a;
    }

}

泛型接口

泛型可以應用於接口。例如生成器(generator),這是一種專門負責創建對象的類。實際上,這是工廠模式的一種應用。

// 泛型接口
public interface Generator<T> {
    T next();
}
//咖啡工廠
public class CoffeeGenerator implements Generator<Coffee> {
    private Random rand = new Random(47);

    //咖啡的子類
    private Class[] types = {
            Amerciano.class,
            Breve.class,
            Cappuccino.class,
            Latte.class,
            Mocha.class};

    private int size = 0 ;

    public CoffeeGenerator() {
    }

    public CoffeeGenerator(int size) {
        this.size = size;
    }

    @Override
    public Coffee next() {
        try {
            return (Coffee) types[rand.nextInt(types.length)].newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        } 
        return null;
    }

}

當然我們也可以用適配器模式來創建泛型接口。這裏就不貼代碼了。

泛型方法

要定義泛型方法,只需要將泛型參數置於返回值之前即可。另外,是否擁有泛型方法,與其是否是泛型類沒有關係。

// 泛型方法
public <T> void f(T t) {
    System.out.println(t.getClass().getName());
}

元組

<>裏不止放一個或者兩個“大寫字母”,可以放很多的。這被稱爲元組,可以存放不同類型的對象。

public class ThreeTuple<A, B, C> extends TwoTuple<A, B> {
    public C c;
    public ThreeTuple(A a, B b, C c) {
        super(a, b);
        this.c = c;
    }

    @Override
    public String toString() {
        return "ThreeTuple{" +
                "a=" + first +
                ", c=" + c +
                ", second=" + second +
                '}';
    }
}

可變參數與泛型方法

泛型方法與可變參數列表能夠很好地共存。

public static <T> List<T> makeList(T...args) {
    List<T> list = new ArrayList<>();
    for ( T t: args ) {
        list.add(t);
    }
    return list;
}

內部類與泛型

泛型還可應用於內部類和匿名內部類。

class Customer {
    private static long counter = 0;
    private long id = counter++;
    private Customer(){}

    @Override
    public String toString() {
        return "Customer{ id=" + id + "}";
    }
    public static Generator<Customer> generator() {
        //lambda表達式
        return () -> new Customer();
    }
}

擦除

在泛型代碼內部,無法獲得任何有關泛型參數類型的信息。
ArrayList<String>ArrayList<Integer>被看作是相同的類型。

Class.getTypeParameters()將返回一個TypeVariable對象數組,表示有泛型聲明中所聲明的參數類型。

所以說,ArrayList<String>ArrayList<Integer>在運行時實際上是相同的類型。這兩種類型都被擦除爲它們的原生類型,即ArrayList

class Forb {}
class Fnork {}
class Quark<Q extends Forb> {}
class Particle<POSITION, MOMENTUM> {}
源代碼 擦除後的Type對象
Forb []
List [E]
List<Forb> [E]
Map<Forb, Fnork> [K,V]
Quark<? extends Forb> [Q]
Particle<Long, Double> [POSITION, MOMENTUM]

泛型類型只有在靜態類型檢查期間纔出現,在此之後,程序中的所有泛型類型都將被擦除,替換爲它們的非泛型上界。

List<T> ==> List
Person==>Object//普通的類型變量未指定泛型邊界時

泛型擦除意味着在運行時失去了參數的類型信息。
有趣的是,非泛型代碼和泛型代碼的字節碼文件是一樣的。

由於擦除丟失了參數的類型信息,任何在運行時需要知道確切類型信息的操作都將無法工作。

if( o instanceof T) {}
T var = new T();
T[] array = new T[size];
T[] arr = (T[])new Object[size];

爲此,引入了類型標籤,就可以轉而使用動態的isInstance

public boolean f(Object obj) {
    return c.isInstance(obj);
}

創建泛型實例和泛型數組

泛型實例

Java中的解決方案是傳遞一個工廠對象,並使用它來創建新的實例。最便利的工廠對象就是Class對象

try {
    //使用該語法的類必須有一個無參的構造函數
     return c.newInstance();
 } catch (InstantiationException e) {
     e.printStackTrace();
 } catch (IllegalAccessException e) {
     e.printStackTrace();
 }

對上述這個不好的地方的解決辦法就是使用顯示的工廠模式

interface Factory<T>{
    T create();
}

class Foo<T> {
    private T t;
    //F代表不確定的類型,<F extends Factory<T>>表示Factory<T>或者是其未知的子類型
    public <F extends Factory<T>> Foo(F factory) {
        t = factory.create();
    }
}

class IntegerFactory implements Factory<Integer> {
    @Override
    public Integer create() {
        return new Integer(0);
    }
}


Foo<Integer> foo = new Foo<>(new IntegerFactory());

泛型數組

  1. 使用ArrayList
  2. 創建擦除後的數組,然後對其轉型
    ?????

  3. 攜帶類型標記創建數組(推薦)

public <T> GenericArrayWithTypeToken(Class<T> type, int size) {
    array = (T[]) Array.newInstance(type, size);
}

邊界

邊界使得你可以用於泛型的參數上設置限制條件。

class ColoredDimension<T extends Dimension & HasColor> { }

泛型重用了extends關鍵字,這裏它區別於繼承,表示某個類以及該類的子類。
類在前,接口在後。

通配符,extends,super

List<? extends Fruit> fruits = new ArrayList<>();
//編譯錯誤
//fruits.add(new Fruit());
//fruits.add(new Apple());
//fruits.add(new Orange());
//fruits.add(new Object());
fruits.add(null);

再看一個例子

public class Holder<T> {
    private T t;

    public Holder(){}

    public Holder(T t) {
        this.t = t;
    }

    public T getT() {
        return t;
    }

    public void setT(T t) {
        this.t = t;
    }


    public static void main(String[] args)  {
        Holder<Apple> apples = new Holder<>(new Apple());
        Apple a = apples.getT();
        apples.setT(a);
        //不能裝換
        //Holder<Fruit> fruits = apples;

        //? extends Fruit意味着它可以是任意從Fruit繼承的類,編譯器無法驗證
        //故添加的操作都將失敗
        Holder<? extends Fruit> fruits = apples;
        Fruit fruit = fruits.getT();
        Apple aa = (Apple) fruits.getT();
        try {
            //會報錯
            Orange orange = (Orange) fruits.getT();
        } catch (Exception e) {
            e.printStackTrace();
        }
        // 編譯錯誤
//        fruits.setT(new Fruit());
//        fruits.setT(new Apple());
//        fruits.setT(new Orange());
//        fruits.setT(new Object());
    }
}
//能讀不能寫

List

List<? super Fruit> flist = new ArrayList<Apple>();  

List

無界通配符

無界通配符<?>看起來意味着“任何事物”,因此使用無界通配符好像等價使用原生類型。

問題

任何基本類型都不能作爲類型參數

一個類不能實現同一泛型接口的多種變體

由於擦除,重載方法將產生相同的類型簽名

自限定的類型

古怪的循環泛型

class GenericType<T>{ }

public class CuriouslyRecurringGeneric
    extends GenericType<CuriouslyRecurringGeneric> {
}

我創建了一個新類,它繼承自一個泛型類型,這個泛型類型接受一個我的類的名字作爲參數

自限定

自限定將採用額外的步驟,強制泛型當做其自己的邊界參數來使用。
自限定限制只能強制作用於繼承關係。

class SelfBounded<T extends SelfBounded<T>>{
    T element;

    SelfBounded<T> set(T arg) {
        element = arg;
        return this;
    }

    T get() {
        return element;
    }

}
class A extends SelfBounded<A> {}
class B extends SelfBounded<A> {}
class C extends SelfBounded<C> {
    C setAndGet(C arg) {
        set(arg);
        return get();
    }
}
class D {}
//class E extends SelfBounded<D> {}

class F extends SelfBounded {}

public class SelfBounding {
    public static void main(String[] args) {
        A a = new A();
        a.set(new A());

        a = a.set(new A()).get();
        a = a.get();
        C c = new C();
        c = c.setAndGet(new C());
    }
}

SelfBounded<T extends SelfBounded<T>>要求只接受從該泛型繼承的類型作爲泛型參數。

參數協變

自限定類型的價值在於它們可以產生協辦類型參數——方法參數類型會隨子類而變化。

class Base{}
class Derived extends Base{}

interface GenericsGetter<T extends GenericsGetter<T>> {
    T get() ;
}
interface Getter extends GenericsGetter<Getter> {}

public class GenericAndReturnTypes {
    void test(Getter g) {
        Getter result = g.get();
        GenericsGetter gg = g.get();
    }
}

我們看到繼承自自限定類型的類,返回類型是導出類的類型。同樣的,方法中參數也是接受導出類的類型。

interface GenericsGetter<T extends GenericsGetter<T>> {
    T get() ;
}
interface Getter extends GenericsGetter<Getter> {}

public class GenericAndReturnTypes {
    void test(Getter g) {
        Getter result = g.get();
        GenericsGetter gg = g.get();
    }
}

動態類型安全

checkedCollection
checkedList
checkedMap
checkedSet
checkedSortedMap
checkedSortedSet()

這些方法每一個都會將你希望動態檢查的容器當做第一個參數接受,並希望你強制要求的類型作爲第二個參數接受。

public class CheckedList {
    static void oldStyleMethod(List pro) {
        pro.add(new Cat());
    }

    public static void main(String[] args) {
        List<Dog> dogs1 = new ArrayList<>();
        //這句不合法的語句居然逃脫了編譯器的檢查
        oldStyleMethod(dogs1);

        List<Dog> dogs2 = Collections.checkedList(new ArrayList<>(), Dog.class);

        try{
            oldStyleMethod(dogs2);
        } catch (ClassCastException cce) {
            cce.printStackTrace();
        }

        List<Pet> pets = Collections.checkedList(new ArrayList<>(), Pet.class);
        pets.add(new Dog());
        pets.add(new Dog());
    }
}

混型

最基本的概念就是混合多個類的能力。(根本不理解)

潛在類型機制

相信看代碼就懂了。

C++版本

class Dog{
    public:
    void speak(){}
    void sit(){}
    void reproduce(){}
};

class Robot{
 public:
    void speak(){}
    void sit(){}
    void reproduce(){}
}

template<class T> void perform(T anything) {
    anything.speak();
    anything.sit();
}

int main() {
    Dog d = new Dog();
    Robot r = new Robot();
    perform(d);
    perform(r);
    return 0;
}

Python版本

# coding:UTF-8

class Dog:
    def speak(self):
        print "Arf"
    def sit(self):
        print "Sitting"
    def reproduce(self):
        pass

class Robot:
    def speak(self):
        print "Click"
    def sit(self):
        print "Clank"
    def reproduce(self):
        pass        

def perform(anything):
    anything.speak();
    anything.sit();

dog = Dog();
robot = Robot();

perform(dog);
perform(robot);

可惜的是Java的語法不允許這樣寫。你可能想到用多態或者簡單的泛型來寫,可以到達相同的效果,是的。

Java替代方案-多態

interface Performs {
    void speak();
    void sit();
}

static void perform(Performs p) {
    p.speak();
    p.sit();
}

Java替代方案-泛型

static <P extends Performs>void perform(P p) {
    p.speak();
    p.sit();
}

Java替代方案-反射

public static void perform(Object speaker) {
        Class<?> cl = speaker.getClass();
        try {
            //指定公共成員方法
            Method method1 = cl.getMethod("speak");
            method1.invoke(speaker);
        } catch (Exception e) {
            e.printStackTrace();
        }

        try {
            Method method2 = cl.getMethod("sit");
            method2.invoke(speaker);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
發佈了91 篇原創文章 · 獲贊 12 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章