《JAVA編程思想》學習筆記---第十五章:泛型

2,簡單泛型

一個元組類庫

return只能返回一個對象,要是想返回多個對象,解決方法就是讓返回的這個對象持有多個想返回的對象,但是每次返回都需要不同類型的對象,那就需要創建很多類,使用泛型可以很好地避免此類問題。同時,我們在編譯時就可以確保類型安全。
這種概念就成爲元組。這個容器對象允許讀取對象,但是允許向其中添加對象。

package com.generic;

public class TwoTuple<A,B> {

    public final A first;
    public final B second;
    public TwoTuple(A a,B b){
        first = a;
        second = b;
    }

    public String toString(){
        return "("+first+ "  "+second+")";
    }

}

這裏變量設置爲Final,可以保證其不被修改。

可以使用繼承機制實現長度更長的元組:

package com.generic;

public class ThreeTuple<A,B,C> extends TwoTuple<A, B> {

    public final C third;
    public ThreeTuple(A a,B b,C c){
        super(a,b);
        third = c;
    }
    public String toString(){
        return "("+first+ "  "+second+"    "+third+")";
    }
}

實現更長的元祖以此類推。
使用元組:

package com.generic;

class Amp{}

public class TupleTest {

    static TwoTuple<String,Integer> f(){
        return new TwoTuple<String,Integer>("hi",47);
    }

    static ThreeTuple<Amp,String,Integer> g(){
        return new ThreeTuple(new Amp(),"three",48);
    }

    public static void main(String[] args) {

        System.out.println(f());
        System.out.println(g());
    }

}

一個堆棧類

不用Linkedlist的Stack:

package com.generic;

public class LinkedStack<T> {

    private static class Node<U>{
        U item;
        Node<U> next;
        Node(){
            item = null;
            next = null;
        }
        Node(U item, Node<U> next){
            this.item = item;
            this.next = next;
        }
        boolean end(){
            return item == null&&next == null;
        }
    }
    private Node<T> top = new Node<T>();

    public void push(T item){
        top = new Node<T>(item,top);
    }

    public T pop(){
        T result = top.item;
        if(!top.end()){
            top = top.next;
        }
        return result;
    }

    public static void main(String[] args) {
        LinkedStack<String> lss = new LinkedStack<>();
        for(String s:"Phasers on stun!".split(" ")){
            lss.push(s);
        }
        String s;
        while((s = lss.pop()) != null){
            System.out.println(s);
        }
    }

}
/*
輸出:
stun!
on
Phasers
*/

很有意思的例子。
個人理解相當於一個鏈表的樣子。

RandomList

一個使用泛型實現隨機訪問的例子:

package com.generic;

import java.util.ArrayList;
import java.util.Random;

public class RandomList<T> {

    private ArrayList<T> storage = new ArrayList<T>();
    private Random rand = new Random(47);

    public void add(T item){
        storage.add(item);
    }

    public T select(){
        return storage.get(rand.nextInt(storage.size()));
    }

    public static void main(String[] args) {

        RandomList<String> rs = new RandomList<>();
        for(String s:"the quick brown fox jumped over".split(" ")){
            rs.add(s);
        }
        for(int i = 0; i < 5; i++){
            System.out.print(rs.select()+" ");
        }
    }

}
/*
輸出:
brown over quick over quick 
*/

3,泛型接口

package com.generic;

public interface generator<T> {

    T next() throws InstantiationException, IllegalAccessException;
}

定義一些Coffee類:

package com.generic;

public class Coffee {

    private static long counter = 9;
    private final long id = counter++;
    public String toString(){
        return getClass().getSimpleName();
    }
}

public class Latte extends Coffee{}

....

編寫類,實現這個接口,隨機生成不同類型的Coffee對象:

package com.generic;

import java.util.*;

public class CoffeeGenerator implements generator<Coffee>,Iterable<Coffee>{

    private Class[] types = {Latte.class,Mocha.class,Cappuccino.class,Americano.class,Breve.class};
    private static Random rand = new Random(47);
    public CoffeeGenerator(){}
    private int size = 0;
    public CoffeeGenerator(int sz){
        size = sz;
    }
    @Override
    public Coffee next() throws InstantiationException, IllegalAccessException {
        return (Coffee)types[rand.nextInt(types.length)].newInstance();
    }

    class CoffeeIterator implements Iterator<Coffee>{

        int count = size;
        @Override
        public boolean hasNext() {
            return count > 0;
        }

        @Override
        public Coffee next() {
            count--;
            Coffee coffee = null;
            try {
                coffee = CoffeeGenerator.this.next();
            } catch (InstantiationException | IllegalAccessException e) {
                e.printStackTrace();
            }
            return coffee;
        }

    }

    @Override
    public Iterator<Coffee> iterator() {
        return new CoffeeIterator();
    }

    public static void main(String[] args) throws InstantiationException, IllegalAccessException{
        CoffeeGenerator gen = new CoffeeGenerator();
        for(int i = 0; i < 5; i++){
            System.out.print(gen.next()+"   ");
        }
        System.out.println();
        for(Coffee c: new CoffeeGenerator(5)){
            System.out.print(c+"   ");
        }
    }
}
/*
輸出:
Americano   Latte   Americano   Mocha   Mocha   
Breve   Americano   Latte   Cappuccino   Cappuccino   
*/

接口泛型的另一個實例:

package com.generic;

import java.util.Iterator;

public class Fibonacci implements generator<Integer>,Iterable<Integer>{

    private int count = 0;
    private int n = 0;
    public Fibonacci(){}
    public Fibonacci(int n){
        this.n = n;
    }
    @Override
    public Integer next() throws InstantiationException, IllegalAccessException {
        return fib(count++);
    }
    private int fib(int n){
        if(n < 2){
            return 1;
        }
        return fib(n-2)+fib(n-1);
    }

    @Override
    public Iterator<Integer> iterator() {
        return new Iterator<Integer>(){

            @Override
            public boolean hasNext() {
                return n > 0;
            }

            @Override
            public Integer next() {
                n--;
                int i = 0;
                try {
                    i =  Fibonacci.this.next();
                } catch (InstantiationException | IllegalAccessException e) {
                    e.printStackTrace();
                }
                return i;
            }

        };
    }

    public static void main(String[] args) throws InstantiationException, IllegalAccessException{
        Fibonacci gen = new Fibonacci();
        for(int i = 0;i < 18;i++){
            System.out.print(gen.next()+"  ");
        }
        System.out.println();
        for(int i: new Fibonacci(18)){
            System.out.print(i+"  ");
        }
    }


}
/*
輸出:
1  1  2  3  5  8  13  21  34  55  89  144  233  377  610  987  1597  2584  
1  1  2  3  5  8  13  21  34  55  89  144  233  377  610  987  1597  2584 
*/

4,泛型方法

package com.generic;

public class GenericMethods {

    public static <T> void f(T x){
        System.out.println(x.getClass().getName());
    }
    public static void main(String[] args) {
        GenericMethods gm = new GenericMethods();
        gm.f("");
        gm.f(1.0);
        gm.f(1.0f);
        gm.f('c');
        gm.f(gm);
        f("");
    }

}

泛型方法可以不依賴與泛型類,如果使用泛型方法可以取代泛型類,那就儘量使用泛型方法!
這種利用參數判斷泛型類型。下面使用返回值判斷泛型類型。

類型參數推斷

package com.generic;

import java.util.*;

public class New {

    public static <K,V> Map<K,V> map(){
        return new HashMap<K,V>();
    }

    public static <T> void list(){

    }

    public static <T> LinkedList<T> lList(){
        return new LinkedList<T>();
    }

    public static <T> Set<T> set(){
        return new HashSet<T>();
    }

    public static <T> Queue<T> queue(){
        return new LinkedList<T>();
    }

    public static void main(String[] args) {
        Map<String,List<String>> sls = New.map();
        New.list();
        LinkedList<String> lls = New.lList();
        Set<String> ss = New.set();
        Queue<String> qs = New.queue();
    }

}

但是這種方法只在賦值操作時有效,但在其他地方並不適用,看如下示例:

package com.generic;

import java.util.*;

public class LimitsOfInference {

    static void f(Map<Coffee,Coffee> coffee){
        System.out.println(coffee.getClass());
        coffee.put(new Coffee(), new Latte());
    }
    public static void main(String[] args) {
        f(New.map());
    }

}

這段代碼書上說不可以,但是實際是可以的!
可以顯示的指明類型:

package com.generic;

import java.util.Map;

public class ExplicitTypeSpecification {

    static void f(Map<Coffee,Coffee> coffee){
        System.out.println(coffee.getClass());
        coffee.put(new Coffee(), new Latte());
    }

    public static void main(String[] args) {
        f(New.<Coffee,Coffee>map());
    }

}

這種必須在方法前指明調用者,在方法前指明泛型類型,這種方法不常用。

可變參數與泛型方法

package com.generic;

import java.util.*;

public class GenericVarargs {

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

    public static void main(String[] args) {
        List<String> ls = makeList("A");
        System.out.println(ls);
        ls = makeList("A","B","C");
        System.out.println(ls);
    }

}

用於Generator的泛型方法

就是一個使用泛型的例子:

package com.generic;

import java.util.*;

public class Generators {

    public static <T> Collection<T> 
    fill(Collection<T> coll,generator<T> gen,int n) throws InstantiationException, IllegalAccessException{
        for(int i = 0; i < n;i++){
            coll.add(gen.next());
        }
        return coll;
    }
    public static void main(String[] args) throws InstantiationException, IllegalAccessException {
        Collection<Coffee> coffee = fill(new ArrayList<Coffee>(),new CoffeeGenerator(),4);
        for(Coffee c:coffee){
            System.out.println(c);
        }

    }
}

一個通用的Generator

package com.generic;

public class BasicGeneratorDemo {

    public static void main(String[] args) throws InstantiationException, IllegalAccessException {
        generator<CountedObject> gen = BasicGenerator.create(CountedObject.class);
        for(int i = 0; i < 5; i++){
            System.out.println(gen.next());
        }
    }

}
package com.generic;

public class CountedObject {

    private static long counter = 0;
    private final long id = counter++;
    public long id(){
        return id;
    }
    public String toString(){
        return "CountedObject"+id;
    }
}
package com.generic;

public class BasicGeneratorDemo {

    public static void main(String[] args) throws InstantiationException, IllegalAccessException {
        generator<CountedObject> gen = BasicGenerator.create(CountedObject.class);
        for(int i = 0; i < 5; i++){
            System.out.println(gen.next());
        }
    }

}

挺簡單的,不解釋了。

簡化元組的使用

package com.generic;

public class Tuple {

    public static <A,B> TwoTuple<A,B> tuple(A a, B b){
        return new TwoTuple<A,B>(a,b);
    }

    public static <A,B,C> ThreeTuple<A,B,C> tuple(A a, B b,C c){
        return new ThreeTuple<A,B,C>(a,b,c);
    }
}
package com.generic;

import static com.generic.Tuple.*;

public class TupleTest2 {

    static TwoTuple<String,Integer> f(){
        return tuple("hi",47);
    }

    static TwoTuple f2(){
        return tuple("hi",47);
    }
    static ThreeTuple<Amphibian,String,Integer> h(){
        return tuple(new Amphibian(),"hi",47);
    }
    public static void main(String[] args) {
        System.out.println(f());
        System.out.println(f2());
        System.out.println(h());
    }

}

一個Set實用工具

有一個例子,略!

5,匿名內部類

泛型的一個好處就是可以簡單而安全的創建複雜的模型,下面實例很容易的創建了List元組:

package com.generic;

import java.util.*;

public class TupleList<A,B,C> extends ArrayList<ThreeTuple<A,B,C>> {
    public static void main(String[] args){
        TupleList<Amphibian,String,Integer> t1 = new TupleList<>();
        t1.add(TupleTest.g());
        t1.add(TupleTest.g());
        for(ThreeTuple<Amphibian,String,Integer> i : t1){
            System.out.println(i);
        }
    }
}

另一個複雜模型,真夠複雜的,有點搞不懂:

package com.generic;

import java.util.*;

class Product{
    private final int id;
    private String description;
    private double price;
    public Product(int IDumber,String descr,double price){
        id = IDumber;
        description = descr;
        this.price = price;
        System.out.println(toString());
    }
    public String toString(){
        return id+":"+description+".price:$"+price;
    }
    public void priceChange(double change){
        price += change;
    }
    public static generator<Product> generator = new generator<Product>(){
        private Random rand = new Random(47);
        public Product next(){
            return new Product(rand.nextInt(1000),"Test",
                    Math.round(rand.nextDouble()*1000.0)+0.99);
        }
    };
}

class Shelf extends ArrayList<Product>{
    public Shelf(int nProducts){
        try {
            Generators.fill(this, Product.generator, nProducts);
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

class Aisle extends ArrayList<Shelf>{
    public Aisle(int nShelves,int nProducts){
        for(int i = 0; i < nShelves;i++){
            add(new Shelf(nProducts));
        }
    }
}

class CheckoutStand{}
class Office{}

public class Store extends ArrayList<Aisle>{

    private ArrayList<CheckoutStand> checkouts = new ArrayList<CheckoutStand>();
    private Office office = new Office();
    public Store(int nAisles, int nShelves,int nProducts){
        for(int i = 0; i < nAisles;i++){
            add(new Aisle(nShelves,nProducts));
        }
    }
    public String toString(){
        StringBuilder result = new StringBuilder();
        for(Aisle a : this){
            for(Shelf s: a){
                for(Product p : s){
                    result.append(p);
                    result.append("\n");
                }
            }
        }
        return result.toString();
    }
    public static void main(String[] args){
        System.out.println(new Store(14,5,10));
    }
}

邏輯有點複雜,智商不夠用。

7,擦除的神祕之處

儘管可以聲明ArrayList.class但是不能聲明ArrayList<Integer>

package com.generic;

import java.util.*;

public class ErasedTypeEquivalence {

    public static void main(String[] args) {
        Class c1 = new ArrayList<String>().getClass();
        Class c2 = new ArrayList<Integer>().getClass();
        System.out.println(c1 == c2);
    }

}
/*
輸出:
true
*/

利用反射得到泛型信息:

package com.generic;

import java.util.*;

class Frob{}
class Fnorkle{}
class Quark<Q>{}
class Particle<POSITION,MOMENTUM>{}
public class LostInformation {

    public static void main(String[] args) {
        List<Frob> list = new ArrayList<>();
        Map<Frob,Fnorkle> map = new HashMap<>();
        Quark<Fnorkle> quark = new Quark<>();
        Particle<Long,Double> p = new Particle<>();
        System.out.println(Arrays.toString(list.getClass().getTypeParameters()));
        System.out.println(Arrays.toString(map.getClass().getTypeParameters()));
        System.out.println(Arrays.toString(quark.getClass().getTypeParameters()));
        System.out.println(Arrays.toString(p.getClass().getTypeParameters()));
    }

}
/*
輸出:
[E]
[K, V]
[Q]
[POSITION, MOMENTUM]
*/

輸出的只是用作參數的佔位符的表示符。
因此,殘酷的現實是:
在泛型代碼內部,無法獲得任何有關泛型參數類型的信息
java泛型是使用擦除來實現的,這意味着使用泛型時,任何具體的類型信息都被擦除了!

C++的方式

看不懂,不看

遷移兼容性

好多字,大體的意思就是泛型不是在java出現時就有的特性,而是後來添加的。爲了不使之前的類庫代碼報廢,只好使用了向前兼容的方式,所有使用了擦除的方式。

擦除的問題

擦除的代價是顯著的,泛型不能用於顯示的引用運行時類型的操作之中,例如轉型,instanceof,和new表達式。因爲所有關於參數的類型的信息都丟失了,所以無論何時,在編寫泛型代碼時,必須時刻提醒自己,這只是看起來擁有有關參數的類型信息而已,因此,如果看到如下代碼:

class Foo<T>{
    T var;
}

創建實例時:

Foo<Cat> f = new Foo<Cat>();

Foo類的代碼應該知道工作在Cat類之上,而泛型語法也強烈暗示,在整個類的所有地方,類型T都被Cat替換。但是事實並非如此,當在編寫這個類的代碼時,必須提醒自己:“這只是一個Object”;

所以,擦除和遷移兼容性以爲這使用泛型並不是強制的,不使用泛型只會出現警告而不是報錯!

package com.generic;

class GenericBase<T>{
    private T element;
    public void set(T arg){
        element = arg;
        System.out.println("set");
    }
    public T get(){
        return element;
    }
}

class Derived1<T> extends GenericBase<T>{}
class Derived2 extends GenericBase{}//不警告

public class ErasureAndInheritance {

    public static void main(String[] args) {
        Derived2 d2 = new Derived2();
        Object obj = d2.get();
        d2.set(obj);//警告
    }

}

邊界處的動作

正式有了擦除,泛型可以表示沒有任何意義的實物:

package com.generic;

import java.lang.reflect.Array;
import java.util.Arrays;

public class ArrayMaker<T> {

    private Class<T> kind;

    public ArrayMaker(Class<T> kind){
        this.kind = kind;
    }

    T[] create(int size){
        //T[] t = new T[]{kind.newInstance(),kind.newInstance()};報錯
        return (T[])Array.newInstance(kind, size);
    }

    public static void main(String[] args) {
        ArrayMaker<String> stringMaker = new ArrayMaker<>(String.class);
        String[] stringArray = stringMaker.create(9);
        System.out.println(Arrays.toString(stringArray));
    }

}
/*
輸出:
[null, null, null, null, null, null, null, null, null]
*/

注意,在泛型中,推薦使用Array.newInstance()。
創建一個容器而不是一個數組:

package com.generic;

import java.util.*;

public class FilledListMaker<T> {

    List<T> create(T t, int n){
        List<T> result = new ArrayList<T>();
        for(int i = 0; i <n;i++){
            result.add(t);
        }
        return result;
    }

    public static void main(String[] args) {
        FilledListMaker<String> stringMaker = new FilledListMaker<>();
        List<String> list = stringMaker.create("Hello", 4);
        System.out.println(list);
    }

}

擦除的補償

擦除丟失了在泛型代碼中執行某些操作的能力。任何在運行時需要知道確切類型信息的操作都無法總做!

package com.generic;

public class Erased<T> {

    private final int SIZE = 100;
    public  void f(Object arg){
        /*if(arg instanceof T){

        }
        T var = new T();
        T[] array = new T[SIZE];*///報錯
        T[] array  = (T[])new Object[SIZE];//警告
    }
}

繞過這些問題來編程,可以使用Class:

package com.generic;

class Building{}
class House extends Building{}

public class ClassTypeCapture<T> {

    Class<T> kind;

    public ClassTypeCapture(Class<T> kind){
        this.kind = kind;
    }

    public boolean f(Object arg){
        return kind.isInstance(arg);
    }

    public static void main(String[] args) {
        ClassTypeCapture<Building> ctt = new ClassTypeCapture<>(Building.class);
        System.out.println(ctt.f(new Building()));
        System.out.println(ctt.f(new House()));
        ClassTypeCapture<House> ctt2 = new ClassTypeCapture<>(House.class);
        System.out.println(ctt2.f(new Building()));
        System.out.println(ctt2.f(new House()));
    }

}

這樣,可以確保類型標籤可以匹配泛型參數。

個人理解:
泛型 T 只可以用來標識另一種東西,如容器類型,返回值類型,參數類型等,但不可以像類一樣生成對象!即不能用在運行時。

new T[]這樣的操作無法實現部分原因是因爲擦除,而另一部分原因是因爲編譯器不能檢查T具有默認構造器這裏寫代碼片,下面代碼將解決這一問題:

package com.generic;

class ClassAsFactory<T>{
    public T x;
    public ClassAsFactory(Class<T> kind){
        try {
            x = kind.newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

class Employee{
    public void print(){
        System.out.println("Employee");
    }
}

public class InstantiateGenericType {

    public static void main(String[] args) {
        ClassAsFactory<Employee> fe = new ClassAsFactory<>(Employee.class);
        fe.x.print();
        ClassAsFactory<Integer> in = new ClassAsFactory<>(Integer.class);

    }

}

可以看到生成ClassAsFactory<Integer>實例時會報錯,因爲Integer沒有默認的構造器。同時,這個錯誤不是在編譯期捕獲的。

使用顯示的工廠方法避免運行時異常,個人認爲這種方法不太靈活。

package com.generic;

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

class Foo2<T>{
    private T x;
    public <F extends Factory<T>> Foo2(F factory){
        x = factory.create();
    }
}

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

class Widght{
    public static class FactoryDemo implements Factory<Widght>{
        public Widght create(){
            return new Widght();
        }
    }
}

public class FactoryConstraint {

    public static void main(String[] args) {
        new Foo2<Integer>(new IntegerFactory());
        new Foo2<Widght>(new Widght.FactoryDemo());
    }

}

使用模板方法模式也能解決這個問題:

package com.generic;

abstract class GenericWithCreate<T>{
    final T ele;
    GenericWithCreate(){
        ele = create();
    }
    abstract T create();
}

class X{}
class Creator extends GenericWithCreate<X>{
    X create(){
        return new X();
    }
    void f(){
        System.out.println(ele.getClass().getSimpleName());
    }
}

public class CreatorGeneric {

    public static void main(String[] args) {
        Creator creator = new Creator();
        creator.f();
    }

}

泛型數組

如前面所示,不能創建泛型數組一般的解決檔案是使用ArrayList:

package com.generic;

import java.util.*;

public class ArrayOfGenericReference<T> {

    private List<T> array = new ArrayList<T>();
    public void add(T item){
        array.add(item);
    }
    public T get(int index){
        return array.get(index);
    }
}
package com.generic;

import java.lang.reflect.Array;

class Generic<T>{}
public class ArrayOfGeneric {

    static final int SIZE = 5;
    static Generic<Integer>[] gia;
    public static void main(String[] args) {
        gia = (Generic<Integer>[])new Generic[SIZE];
        System.out.println(gia.getClass().getSimpleName());
        gia[0] = new Generic<Integer>();
        //gia[1] = new Generic<Double>();報錯
        for(int i = 0; i  < SIZE; i++){
            System.out.println(gia[i]);
        }
    }

}

挺正常的代碼。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章