Java编程思想学习笔记(15)

Java编程思想学习笔记(15)

泛型

一般的类和方法,只能使用具体的类型:要么是基本类型,要么是自定义的类。如果要编写可以应用千多种类型的代码,这种刻板的限制对代码的束缚就会很大。

为了解决这个问题,JDK 5中引入了一个新特性—泛型(generics),泛型提供了编译时类型安全监测机制,该机制允许程序员在编译时监测非法的类型。使用泛型机制编写的程序代码要比那些杂乱地使用Object变量,然后再进行强制类型转换的代码具有更好的安全性和可读性。泛型对于集合类尤其有用,例如,ArrayList就是一个无处不在的集合类。

泛型的本质是参数化类型,也就是所操作的数据类型被指定为一个参数。

简单泛型

有许多原因促成了泛型的出现,而最引人注目的一个原因,就是为了创造容器类。

例子:

public class Automobile {
}


public class Holder1 {

//    只能持有单个对象的类
    private Automobile a;
    public Holder1(Automobile a) { this.a = a; }
    Automobile get() { return a; }



}


这个类的可重用性就不怎么样了,它无法持有其他类型的任何对象

在JavaSES之前,我们可以让这个类直接持有Object类型的对象:

public class Holder2 {

    private Object a;
    
    public Holder2(Object a) {
        this.a = a; 
    }
    
    public void set(Object a) { this.a = a; }
    public Object get() { return a; }
    
    
    
    public static void main(String[] args) {
        Holder2 h2 = new Holder2(new Automobile());
        Automobile a = (Automobile)h2.get();
        h2.set("Not an Automobile");
        String s = (String)h2.get();
        h2.set(1); // Autoboxes to Integer
        Integer x = (Integer)h2.get();
    }
}


现在,Holder2可以存储任何类型的对象,在这个例子中,只用了一个Holder2对象,却先 后三次存储了三种不同类型的对象。

有些情况下,我们确实希望容器能够同时持有多种类型的对象。但是,通常而言,我们只
会使用容器来存储一种类型的对象。泛型的主要目的之一就是用来指定容器要持有什么类型的对象,而且由编译器来保证类型的正确性。

与其使用Object, 我们更喜欢暂时不指定类型, 而是稍后再决定具体使用什么类型。要达到这个目的,需要使用类型参数, 用尖括号括住,放在类名后面。 然后在使用这个类的时候,再用实际的类型替换此类型参数。 在下面的例子中,T是类型参数:

public class Holder3<T>{

    private T a;
    public Holder3(T a) { this.a = a; }
    public void set(T a) { this.a = a; }
    public T get() { return a; }
    public static void main(String[] args) {
        Holder3<Automobile> h3 =
                new Holder3<Automobile>(new Automobile());
        Automobile a = h3.get(); // No cast needed
// h3.set("Not an Automobile"); // Error
// h3.set(1); // Error
    }
}



这就是Java泛型的核心概念:告诉编译器想使用什么类型, 然后编译器帮你处理一切细节。

元组类库

return语句只允许返回单个对象,如果我们想返回多个对象,解决办法就是创建一个对象,用它来持有想要返回的多个对象。

这个概念称为元组(tuple), 它是将一组对象直接打包存储于其中的一个单一对象 这个容器对象允许读取其中元素, 但是不允许向其中存放新的对象。(这个概念也称为数据传送对象, 或信使。)

通常, 元组可以具有任意长度,同时, 元组中的对象可以是任意不同的类型。 不过, 我们希望能够为每一个对象指明其类型,并且从容器中读取出来时,能够得到正确的类型。 要处理不同长度的问题,我们需要创建多个不同的元组。 下面的程序是一个2维元组,它能够持有两个对象:


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 + ")";
    }
}


上面类的字段虽然没有声明private,但是final修饰后依旧保持了安全性。

可以利用继承机制实现长度更长的元组。 从下面的例子中可以看到, 增加类型参数是件很简单的事情:


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 + ")";
    }
}

public class FourTuple<A,B,C,D> extends ThreeTuple<A,B,C>{
    public final D fourth;
    public FourTuple(A a, B b, C c, D d) {
        super(a, b, c);
        fourth = d;
    }
    public String toString() {
        return "(" + first + ", " + second + ", " +
                third + ", " + fourth + ")";
    }



}


public class FiveTuple<A,B,C,D,E>
        extends FourTuple<A,B,C,D> {
    public final E fifth;
    public FiveTuple(A a, B b, C c, D d, E e) {
        super(a, b, c, d);
        fifth = e;
    }
    public String toString() {
        return "(" + first + ", " + second + ", " +
                third + ", " + fourth + ", " + fifth + ")";
    }

}



为了使用元组,只需要定义一个长度合适的元组。


public class Amphibian {
}

public class Vehicle {
}


public class TupleTest {

    static TwoTuple<String,Integer> f() {
// Autoboxing converts the int to Integer:
        return new TwoTuple<String,Integer>("hi", 47);
    }
    static ThreeTuple<Amphibian,String,Integer> g() {
        return new ThreeTuple<Amphibian, String, Integer>(
                new Amphibian(), "hi", 47);
    }
    public static FourTuple<Vehicle,Amphibian,String,Integer> h() {
        return
                new FourTuple<Vehicle,Amphibian,String,Integer>(
                        new Vehicle(), new Amphibian(), "hi", 47);
    }
    static FiveTuple<Vehicle,Amphibian,String,Integer,Double> k() {
        return new
                FiveTuple<Vehicle,Amphibian,String,Integer,Double>(new Vehicle(), new Amphibian(), "hi", 47, 11.1);
    }
    public static void main(String[] args) {
        TwoTuple<String,Integer> ttsi = f();
        System.out.println(ttsi);
// ttsi.first = "there"; // Compile error: final
        System.out.println(g());
        System.out.println(h());
        System.out.println(k());
    }
}

通过ttsi.first= "there"语句的错误,我们可以看出,final声明确实能够保护public元素,在 对象被构造出来之后,声明为final的元素便不能被再赋予其他值了。

堆栈类

不用Linked.List,来实现自己的内部链式存储机制。


public class LinkedStack<T> {

    //    内部类Node也是一个泛型,拥有自己的类型参数
    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>(); // End sentinel
    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<String>();
        for(String s : "Phasers on stun!".split(" "))
            lss.push(s);
        String s;
        while((s = lss.pop()) != null)
            System.out.println(s);
    }
}


RandomList

假设我们锯要一个持有特定类型对象的列表,每次调用其上的 selectO方法时,它可以随机地选取一个元素。如果我们希望以此构建一个可以应用于各种类型 的对象的工具,就帣要使用泛型:

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<String>();
        for(String s: ("The quick brown fox jumped over " +
                "the lazy brown dog").split(" "))
            rs.add(s);
        for(int i = 0; i < 11; i++)
            System.out.print(rs.select() + " ");
    }
}


泛型接口

泛型也可以应用千接口。例如生成器(generator),这是一种专门负责创建对象的类。

实际上,这是工厂方法设计模式的一种应用。不过,当使用生成器创建新的对象时,它不需要任何参数,而工厂方法一般锯要参数。也就是说,生成器无需额外的信息就知道如何创建新对象。

一个生成器只定义一个方法,该方法用以产生新的对象。在这里,就是nextO方法:


public interface Generator<T> {
    
    
//    方法next()的返回类型是参数化的T。接口使用泛型与类使用泛型没什么区别。
    T next();
}

Coffee层次类:

public class Coffee {

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


}


public class Cappuccino extends Coffee{
}

public class Breve extends Coffee{
}


public class Americano extends Coffee{
}

public class Latte extends Coffee{
}


public class Mocha extends Coffee{
}



现在,我们可以编写一个类,实现Generator接口,它能够随机生成不同类型的 Coffee对象:

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);

//    参数化的Generator接口确保nextO的返回值是参数的类型。CoffeeGenerator同时还实现了
//    Iterable接口,所以它可以在循环语句中使用。不过,它还要一个“末端哨兵”来判断何时停止,
//    这正是第二个构造器的功能。

    public CoffeeGenerator() {}


    // For iteration:
    private int size = 0;


    public CoffeeGenerator(int sz) { size = sz; }


    public Coffee next() {
        try {
            return (Coffee)
                    types[rand.nextInt(types.length)].newInstance();
// Report programmer errors at run time:
        } catch(Exception e) {
            throw new RuntimeException(e);
        }
    }



    class CoffeeIterator implements Iterator<Coffee> {
        int count = size;
        public boolean hasNext() { return count > 0; }
        public Coffee next() {
            count--;
            return CoffeeGenerator.this.next();
        }
        public void remove() { // Not implemented
            throw new UnsupportedOperationException();
        }
    };



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


    public static void main(String[] args) {
        CoffeeGenerator gen = new CoffeeGenerator();
        for(int i = 0; i < 5; i++)
            System.out.println(gen.next());
        for(Coffee c : new CoffeeGenerator(5))
            System.out.println(c);
    }


}



下面的类是Generator接口的另一个实现,它负贲生成Fibonacci数列:

public class Fibonacci implements Generator<Integer> {

    private int count = 0;
    public Integer next() { return fib(count++); }
    private int fib(int n) {
        if(n < 2) return 1;
        return fib(n-2) + fib(n-1);
    }
    public static void main(String[] args) {
        Fibonacci gen = new Fibonacci();
        for(int i = 0; i < 18; i++)
            System.out.print(gen.next() + " ");
    }


}


如果还想更进一步,编写一个实现了Iterable的Fibonacci生成器。

我们的一个选择是重写这个类,令其实现Iterable接口。不过,你并不是总能拥有源代码的控制权,并且,除非必须这么做,否则,我们也不愿意重写一个类。而且我们还有另一种选择,就是创建一个适配器(adapter) 来实现所需的接口

public class IterableFibonacci extends Fibonacci implements Iterable<Integer> {

    private int n;

    public IterableFibonacci(int count) {
        n = count;
    }

    public Iterator<Integer> iterator() {
        return new Iterator<Integer>() {
            public boolean hasNext() {
                return n > 0;
            }

            public Integer next() {
                n--;
                return IterableFibonacci.this.next();
            }

            public void remove() { // Not implemented
                throw new UnsupportedOperationException();
            }
        };
    }
}

泛型方法

到目前为止,我们看到的泛型,都是应用在整个类上。但同样可以在类中包含参数化方法, 而这个方法所在的类可以是泛型类,也可以不是泛型类。

也就是说,是否拥有泛型方法,与其所在的类是否是泛型没有关系。

泛型方法使得该方法能够独立于类而产生变化。以下是一个基本的指导原则:无论何时,只要你能做到,你就应该尽址使用泛型方法。也就是说,如果使用泛型方法可以取代将整个类 泛型化,那么就应该只使用泛型方法,因为它可以使事情更清楚明白。

另外,对于一个static的方法而言,无法访间泛型类的类型参数,所以,如果static方法需要使用泛型能力,就必须使其成为泛型方法。


public class GenericMethods {

    
    public <T> void f(T x) {
        System.out.println(x.getClass().getName());
    }

    
//    注意,当使用泛型类时,必须在创建对象的时候指定类型参数的值,而使用泛型方法的时候,
//    通常不必指明参数类型,因为编译器会为我们找出具体的类型。
//    这称为类型参数推断(type argument inference)。因此,我们可以像调用普通方法一样调用f(),
//    而且就好像是ro被无限次地重载过。它甚至可以接受GenericMethods作为其类型参数。

    public static void main(String[] args) {
        GenericMethods gm = new GenericMethods();
        gm.f("");
        gm.f(1);
        gm.f(1.0);
        gm.f(1.0F);
        gm.f('c');
        gm.f(gm);
    }


}


类型参数推断

使用泛型有时候需要向程序中加人更多的代码,如果要创建一个持有List的Map, 就要像下面这样:

Map<Person, List<? extends Pet>> petPeople =
new HashMap<Person, List<? extends Pet>>();

然而, 在泛型方法中, 类型参数推断可以为我们简化一部分工作。 例如, 我们可以编写一个工具类, 它包含各种各样的Static方法, 专门用来创建各种常用的容器对象:


public class New {


    public static <K,V> Map<K,V> map() {
        return new HashMap<K,V>();
    }
    
    
    public static <T> List<T> list() {
        return new ArrayList<T>();
    }
    
    
    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>();
    }
    
    
    // Examples:
    public static void main(String[] args) {
        Map<String, List<String>> sls = New.map();
        List<String> ls = New.list();
        LinkedList<String> lls = New.lList();
        Set<String> ss = New.set();
        Queue<String> qs = New.queue();
    }



}

类型推断只对赋值操作有效,其他时候并不起作用。如果你将一个泛型方法调用的结果
(例如Ncw.mapO)作为参数,传递给另一个方法,这时编译器并不会执行类型推断。在这种情
况下,编译器认为:调用泛型方法后,其返回值被赋给一个Object类型的变扯。下面的例子证明了这一点:

public class LimitsOfInference {

    static void
    f(Map<Person, List<? extends Pet>> petPeople) {}


    public static void main(String[] args) {
// f(New.map()); // Does not compile
    }


}

显示的类型说明

在泛型方法中,可以显式地指明类型,不过这种语法很少使用。要显式地指明类型,必须在点操作符与方法名之间插入尖括号,然后把类型置千尖括号内。

如果是在定义该方法的类的内部,必须在点操作符之前使用this关键字,如果是使用static的方法,必须在点操作符之前加 上类名。使用这种语法,可以解决LimitsOflnference.java中的问题

public class ExplicitTypeSpecification {


    static void f(Map<Person, List<Pet>> petPeople) {}
    
    
    public static void main(String[] args) {
        f(New.<Person, List<Pet>>map());
    }



}


当然,这种语法抵消了New类为我们带来的好处(即省去了大证的类型说明),不过,只有在编写非赋值语旬时,我们才需要这样的额外说明。

可变参数与泛型方法

泛型方法与可变参数列表能很好共存

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);
        ls = makeList("ABCDEFFHIJKLMNOPQRSTUVWXYZ".split(""));
        System.out.println(ls);
    }


}



用千Generator的泛型方法

利用生成器, 我们可以很方便地填充一个Collection, 而泛型化这种操作是具有实际意义的:

public class Generators {

    public static <T> Collection<T>
    fill(Collection<T> coll, Generator<T> gen, int n) {
        for(int i = 0; i < n; i++)
            coll.add(gen.next());
        return coll;
    }


    public static void main(String[] args) {
        Collection<Coffee> coffee = fill(
                new ArrayList<Coffee>(), new CoffeeGenerator(), 4);
        for(Coffee c : coffee)
            System.out.println(c);
        System.out.println("_______________________________________________");
        Collection<Integer> fnumbers = fill(
                new ArrayList<Integer>(), new Fibonacci(), 12);
        for(int i : fnumbers)
            System.out.print(i + ", ");
    }


}



一个通用的Generator

下面的程序可以为任何类构造一个Generator, 只要该类具有默认的构造器。 为了减少类型
声明, 它提供了一个泛型方法, 用以生成BasicGenerator:

public class BasicGenerator<T> implements Generator<T> {

    private Class<T> type;


    public BasicGenerator(Class<T> type){ this.type = type; }


    public T next() {
        try {
// Assumes type is a public class:
        return type.newInstance();
    } catch(Exception e) {
        throw new RuntimeException(e);
    }
    }



    // Produce a Default generator given a type token:

    public static <T> Generator<T> create(Class<T> type) {
        return new BasicGenerator<T>(type);
    }
}

一个具有默认构造器的简单的类:

public class CountedObject {

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


}


使用BasicGenerator,你可以很容易地为CountedObject创建一个Generator

public class BasicGeneratorDemo {

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


}


简化元组的使用

有了类型参数推断, 再加上static方法, 我们可以重新编写之前看到的元组工具, 使其成为更通用的工具类库。 在这个类中, 我们通过重载static方法创建元组:

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);
    }
    
    
    public static <A,B,C,D> FourTuple<A,B,C,D>
    tuple(A a, B b, C c, D d) {
        return new FourTuple<A,B,C,D>(a, b, c, d);
    }
    
    
    public static <A,B,C,D,E>
    FiveTuple<A,B,C,D,E> tuple(A a, B b, C c, D d, E e) {
        return new FiveTuple<A,B,C,D,E>(a, b, c, d, e);
    }


}



下面是修改后的TupleTest.java, 用来测试Tuple.java:

public class TupleTest2 {


    static TwoTuple<String,Integer> f() {
        return tuple("hi", 47);
    }
    
    
    static TwoTuple f2() { return tuple("hi", 47); }
    
    
    static ThreeTuple<Amphibian,String,Integer> g() {
        return tuple(new Amphibian(), "hi", 47);
    }
    
    
    static FourTuple<Vehicle,Amphibian,String,Integer> h() {
        return tuple(new Vehicle(), new Amphibian(), "hi", 47);
    }
    
    
    static FiveTuple<Vehicle,Amphibian,String,Integer,Double> k() {
        return tuple(new Vehicle(), new Amphibian(),
                "hi", 47, 11.1);
    }
    
    
    public static void main(String[] args) {
        TwoTuple<String,Integer> ttsi = f();
        System.out.println(ttsi);
        System.out.println(f2());
        System.out.println(g());
        System.out.println(h());
        System.out.println(k());
    }
}


一个Set实用工具

作为泛型方法的另一个示例,我们看看如何用Set来表达数学中的关系式。通过使用泛型方法,可以很方便地做到这一点,而且可以应用千多种类型:

public class Sets {

    
//    在前三个方法中,都将第一个参数Set复制了一份,将Set中的所有引用都存入一个新的
//    HashSet对象中,因此,我们并未直接修改参数中的Set。返回的值是一个全新的Set对象。
//这四个方法表达了如下的数学集合操作:union()返回一个Set,它将两个参数合并在一起, 
// intersection()返回的Set只包含两个参数共有的部分,difference()
// 方法从superset中移除subset包含的元素1complement()返回的Set包含除了交集之外的所有元素。


    public static <T> Set<T> union(Set<T> a, Set<T> b) {
        Set<T> result = new HashSet<T>(a);
        result.addAll(b);
        return result;
    }
    public static <T>
    Set<T> intersection(Set<T> a, Set<T> b) {
        Set<T> result = new HashSet<T>(a);
        result.retainAll(b);
        return result;
    }
    // Subtract subset from superset:
    public static <T> Set<T>
    difference(Set<T> superset, Set<T> subset) {
        Set<T> result = new HashSet<T>(superset);
        result.removeAll(subset);
        return result;
    }
    // Reflexive--everything not in the intersection:
    public static <T> Set<T> complement(Set<T> a, Set<T> b) {
        return difference(union(a, b), intersection(a, b));
    }



}


下面提供了一个enum,它包含各种水彩画的颜色。我们将用它来演示以上这些方法的功能和效果。


public enum Watercolors {


    ZINC, LEMON_YELLOW, MEDIUM_YELLOW, DEEP_YELLOW, ORANGE,
    BRILLIANT_RED, CRIMSON, MAGENTA, ROSE_MADDER, VIOLET,
    CERULEAN_BLUE_HUE, PHTHALO_BLUE, ULTRAMARINE,
    COBALT_BLUE_HUE, PERMANENT_GREEN, VIRIDIAN_HUE,
    SAP_GREEN, YELLOW_OCHRE, BURNT_SIENNA, RAW_UMBER,
    BURNT_UMBER, PAYNES_GRAY, IVORY_BLACK


}

下面的示例以static的方式引人Watercolors。
这个示例使用了EnumSet,这是JavaSE5中的新工具,用来从enum直接创建Set。
在这里,我们向static方法EnumSet.range()传入某个范围的第一个元素与最后一个元素,然后它将返回一个Set,其中包含该范围内的所有元素:

public class WatercolorSets {

    public static void main(String[] args) {
        Set<Watercolors> set1 =
                EnumSet.range(BRILLIANT_RED, VIRIDIAN_HUE);
        
        
        Set<Watercolors> set2 =
                EnumSet.range(CERULEAN_BLUE_HUE, BURNT_UMBER);
        
        
        print("set1: " + set1);
        print("set2: " + set2);
        print("union(set1, set2): " + union(set1, set2));
        Set<Watercolors> subset = intersection(set1, set2);
        print("intersection(set1, set2): " + subset);
        print("difference(set1, subset): " +
                difference(set1, subset));
        print("difference(set2, subset): " +
                difference(set2, subset));
        print("complement(set1, set2): " +
                complement(set1, set2));
    }


}



下面的示例使用Sets.difference()打印出java.util包中各种Collection类与Map类之间的方法差异:


public class ContainerMethodDifferences {

    static Set<String> methodSet(Class<?> type) {
        Set<String> result = new TreeSet<String>();
        for(Method m : type.getMethods())
            result.add(m.getName());
        return result;
    }
    
    
    static void interfaces(Class<?> type) {
        System.out.print("Interfaces in " +
                type.getSimpleName() + ": ");
        List<String> result = new ArrayList<String>();
        for(Class<?> c : type.getInterfaces())
            result.add(c.getSimpleName());
        System.out.println(result);
    }
    
    
    static Set<String> object = methodSet(Object.class);
    
    
    static { object.add("clone"); }
    
    static void
    difference(Class<?> superset, Class<?> subset) {
        System.out.print(superset.getSimpleName() +
                " extends " + subset.getSimpleName() + ", adds: ");
        Set<String> comp = Sets.difference(
                methodSet(superset), methodSet(subset));
        comp.removeAll(object); // Don’t show ‘Object’ methods
        System.out.println(comp);
        interfaces(superset);
    }
    
    
    public static void main(String[] args) {
        System.out.println("Collection: " +
                methodSet(Collection.class));
        interfaces(Collection.class);
        difference(Set.class, Collection.class);
        difference(HashSet.class, Set.class);
        difference(LinkedHashSet.class, HashSet.class);
        difference(TreeSet.class, Set.class);
        difference(List.class, Collection.class);
        difference(ArrayList.class, List.class);
        difference(LinkedList.class, List.class);
        difference(Queue.class, Collection.class);
        difference(PriorityQueue.class, Queue.class);
        System.out.println("Map: " + methodSet(Map.class));
        difference(HashMap.class, Map.class);
        difference(LinkedHashMap.class, HashMap.class);
        difference(SortedMap.class, Map.class);
        difference(TreeMap.class, Map.class);
    }
}


匿名内部类

泛型还可以应用千内部类以及匿名内部类。下面的示例使用匿名内部类实现了Generator 接口

public class Customer {

    private static long counter = 1;

    private final long id = counter++;

    private Customer() {}

    public String toString() { return "Customer " + id; }

    // A method to produce Generator objects:
    public static Generator<Customer> generator() {
        return new Generator<Customer>() {
            public Customer next() { return new Customer(); }
        };
    }


}

public class Teller {

    private static long counter = 1;
    private final long id = counter++;
    private Teller() {}
    public String toString() { return "Teller " + id; }
    // A single Generator object:
    public static Generator<Teller> generator =
            new Generator<Teller>() {
                public Teller next() { return new Teller(); }
            };


}


public class BankTeller {

    public static void serve(Teller t, Customer c) {
        System.out.println(t + " serves " + c);
    }

//    Customer和Teller类都只有private的构造器,这可以强制你必须使用Generator对象。
//    Customer有一个generator()方法,每次执行它都会生成一个新的Generator<Customer>对象。
//    我们其实不蒂要多个Generator对象,Teller就只创建了一个public的generator对象。
//    在main()方法中可以看到,这两种创建Generator的方式都在fill()中用到了。
//由于Customer中的generator()方法,以及Teller中的Generator对象都声明成了static的,
// 所以它们无法作为接口的一部分,因此无法用接口这种特定的惯用法来泛化这二者。尽管如此,
// 它们在血O方法中都工作得很好。
    public static void main(String[] args) {
        Random rand = new Random(47);
        Queue<Customer> line = new LinkedList<Customer>();
        Generators.fill(line, Customer.generator(), 15);
        List<Teller> tellers = new ArrayList<Teller>();
        Generators.fill(tellers, Teller.generator, 4);

        for(Customer c : line)
            serve(tellers.get(rand.nextInt(tellers.size())), c);
    }
}


构建复杂模型

泛型的一个重要好处是能够简单而安全地创建复杂的模型。例如,我们可以很容易地创建List元组:


public class TupleList<A,B,C,D> extends ArrayList<FourTuple<A,B,C,D>> {

    public static void main(String[] args) {
        TupleList<Vehicle, Amphibian, String, Integer> tl =
                new TupleList<Vehicle, Amphibian, String, Integer>();
        tl.add(TupleTest.h());
        tl.add(TupleTest.h());
        for(FourTuple<Vehicle,Amphibian,String,Integer> i: tl)
            System.out.println(i);
    }


}

下面是另一个示例,它展示了使用泛型类型来构建复杂模型是多么的简单。即使每个类都是作为一个构建块创建的,但是其整个还是包含许多部分。在本例中,构建的模型是一个零售店,它包含走廊、货架和商品:

public class Aisle extends ArrayList<Shelf> {

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

public class CheckoutStand {
}

public class Office {
}

public class Shelf extends ArrayList<Product> {

    public Shelf(int nProducts) {
        Generators.fill(this, Product.generator, nProducts);
    }


}

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));
    }
}

public class Product {

    private final int id;
    private String description;
    private double price;
    public Product(int IDnumber, String descr, double price){
        id = IDnumber;
        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);
                }
            };


}



擦除的神秘之处

当你开始更深入地钻研泛型时, 会发现有大量的东西初看起来是没有意义的。 例如, 尽管 可以声明ArrayList.class, 但是不能声明ArrayList< lnteger >.class。 请考虑下面的情况:


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);
    }
    
//    ArrayList<.Strin贮和ArrayList<lnteger>很容易被认为是不同的类型。 不同的类型在行为方面肯定不同, 例如, 如果尝试若将一个Integer放入ArrayList<.String>, 所得到的行为(将失败)
//与把一个Integer放入ArrayList<lnteger> (将成功)所得到的行为完全不同。 但是上面的程序会
//认为它们是相同的类型。
    
    


}

下面是的示例是对这个谜题的一个补充:

public class Frob {
}

public class Fnorkle {
}

public class Particle<POSITION,MOMENTUM> {
}

public class Quark<Q> {
}

public class LostInformation {


    public static void main(String[] args) {

        List<Frob> list = new ArrayList<Frob>();

        Map<Frob,Fnorkle> map = new HashMap<Frob,Fnorkle>();

        Quark<Fnorkle> quark = new Quark<Fnorkle>();

        Particle<Long,Double> p = new Particle<Long,Double>();


        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()));
    }


}


根据JDK文档的描述, Class.getTypeParameters()将 “返回一个TypeVariable对象数组,表示有泛型声明所声明的类型参数…···"这好像是在暗示你可能发现参数类型的信息, 但是,正如你从输出中所看到的, 你能够发现的只是用作参数占位符的标识符, 这并非有用的信息。

因此, 残酷的现实是:在泛型代码内部, 无法荻得任何有关泛型参数类型的信息。

因此, 你可以知道诸如类型参数标识符和泛型类型边界这类的信息—一你却无法知道用来创建某个特定实例的实际的类型参数。

Java泛型是使用擦除来实现的, 这意味若当你在使用泛型时, 任何具体的类型信息都被擦除了, 你唯一知道的就是你在使用一个对象。 因此List< String >和List< lnteger >在运行时事实上 是相同的类型。 这两种形式都被擦除成它们的 “原生” 类型, 即List

擦除的问题

掠除的代价是显著的。泛型不能用千显式地引用运行时类型的操作之中,例如转型
、 instanceo瑁作和new表达式。因为所有关千参数的类型信息都丢失了,无论何时,当你在编写 泛型代码时,必须时刻提醒自己,你只是看起来好像拥有有关参数的类型信息而已。因此,如果你编写了下面这样的代码段:

public class Foo<T> {

    T var;

}

那么,看起来当你在创建Foo的实例时:

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

class Foo中的代码应该知道现在工作千Cat之上,而泛型语法也在强烈暗示:在整个类中的各个地方,类型T都在被替换。但是事实井非如此,无论何时,当你在编写这个类的代时,必须知道它只是一个object

正是因为有了擦除,我发现泛型最令人困惑的方面源自这样一个事实,即可以表示没有任何意义的辜物。例如:

public class ArrayMaker<T> {

    private Class<T> kind;

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

    @SuppressWarnings("unchecked")
    T[] create(int size) {
        return (T[]) Array.newInstance(kind, size);
    }


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

//    即使kind被存储为Class<f>, 擦除也意味若它实际将被存储为Class,
//    没有任何参数。 因此,当你在使用它时,例如在创建数组时,
//    Array.newlnstanceO实际上并未拥有kind所蕴含的类型倌
}


如果我们要创建一个容器而不是数组, 情况就有些不同了:

public class ListMaker<T> {
    List<T> create() { return new ArrayList<T>(); }
    public static void main(String[] args) {
        ListMaker<String> stringMaker= new ListMaker<String>();
        List<String> stringList = stringMaker.create();
    }


}



编译器不会给出任何警告,尽管我们(从控除中)知道在create()内部的newArrayList 一中的被移除了 在运行时,这个类的内部没有任何, 因此这看起来亳无意义。 但是如果你遵从这种思路,并将这个表达式改为new Array List(), 编译器就会给出警告。

如果返回list之前, 将某些对象放入其中, 就像下面这样,情况又会如何呢?


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<String>();
        List<String> list = stringMaker.create("Hello", 4);
        System.out.println(list);
    }

//即使编译器无法知道有关create()中的T的任何信息,但是它仍旧可以在编译期确保你放置到result中的
// 对象具有T类型,使其适合ArrayList<T>。因此,即使擦除在方法或类内部移除了 有关实际类型的信息,
// 编译器仍旧可以确保在方法或类中使用的类型的内部一致性。
}

擦除的补偿

擦除丢失了在泛型代码中执行某些操作的能力。任何在运行时需要知道确切类型信息的操作都将无法工作:

public class Erased<T> {
    private final int SIZE = 100;
//    public static void f(Object arg) {
//        if(arg instanceof T) {} // Error
//        T var = new T(); // Error
//        T[] array = new T[SIZE]; // Error
//        T[] array = (T)new Object[SIZE]; // Unchecked warning
//    }
//
}


偶尔可以绕过这些问题来编程,但是有时必须通过引人类型标签来对擦除进行补偿。这意味着你蒂要显式地传递你的类型的Class对象,以便你可以在类型表达式中使用它。

public class Building {
}

public 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> ctt1 =
                new ClassTypeCapture<Building>(Building.class);
        System.out.println(ctt1.f(new Building()));
        System.out.println(ctt1.f(new House()));
        ClassTypeCapture<House> ctt2 =
                new ClassTypeCapture<House>(House.class);
        System.out.println(ctt2.f(new Building()));
        System.out.println(ctt2.f(new House()));
    }


}




创建类型实例

在Erased.java中对创建一个newTO的尝试将无法实现,部分原因是因为擦除,而另一部分

原因是因为编译器不能验证T具有默认(无参)构造器。但是在C++中, 这种操作很自然观,井且很安全(它是在编译期受到检查的)

Java中的解决方案是传递一个工厂对象,井使用它来创建新的实例。 最便利的工广对象就是 Class对象, 因此如果使用类型标签, 那么你就可以使用newInstance()来创建这个类型的新对象:

public class ClassAsFactory<T> {

    T x;
    public ClassAsFactory(Class<T> kind) {
        try {
            x = kind.newInstance();
        } catch(Exception e) {
            throw new RuntimeException(e);
        }
    }
}
public class Employee {
}


public class InstantiateGenericType {

    public static void main(String[] args) {
        ClassAsFactory<Employee> fe =
                new ClassAsFactory<Employee>(Employee.class);
        print("ClassAsFactory<Employee> succeeded");
        try {
            ClassAsFactory<Integer> fi =
                    new ClassAsFactory<Integer>(Integer.class);
        } catch(Exception e) {
            print("ClassAsFactory<Integer> failed");
        }
    }


}

这可以编译,但是会因ClassAsFactory< lnteger >而失败,因为Integer没有任何默认的构造器。


public interface FactoryI<T> {

    T create();

}


public class Foo2<T> {

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

public class IntegerFactory implements FactoryI<Integer>{

    public Integer create() {
        return new Integer(0);
    }


}

public class Widget {

    public static class Factory implements FactoryI<Widget> {
        public Widget create() {
            return new Widget();
        }
    }


}

public class FactoryConstraint {

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




泛型数组

在Erased.java中所见,不能创建泛型数组。一般的解决方案是在任何想要创建泛型 数组的地方都使用ArrayList:

public class ListOfGenerics<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); }


}


有时,你仍旧希望创建泛型类型的数组(例如,ArrayList内部使用的是数组)。有趣的是, 可以按照编译器喜欢的方式来定义一个引用,例如:

public class ArrayOfGenericReference {

    static Generic<Integer>[] gia;

}

编译器将接受这个程序,而不会产生任何警告。但是,永远都不能创建这个确切类型的数 组(包括类型参数),.因此这有一点令人困惑。既然所有数组无论它们持有的类型如何,都具有相同的结构(每个数组槽位的尺寸和数组的布局),那么看起来你应该能够创建一个Objec数组,并将其转型为所希望的数组类型。事实上这可以编译,但是不能运行,它将产生ClassCase­Exception:

public class ArrayOfGeneric {

    static final int SIZE = 100;

    static Generic<Integer>[] gia;


    @SuppressWarnings("unchecked")
    public static void main(String[] args) {


// Compiles; produces ClassCastException:
//! gia = (Generic<Integer>[])new Object[SIZE];
// Runtime type is the raw (erased) type:


        gia = (Generic<Integer>[])new Generic[SIZE];
        System.out.println(gia.getClass().getSimpleName());
        gia[0] = new Generic<Integer>();


//! gia[1] = new Object(); // Compile-time error
// Discovers type mismatch at compile time:
//! gia[2] = new Generic<Double>();
    }


}


问题在千数组将跟踪它们的实际类型, 而这个类型是在数组被创建时确定的, 因此,
即使gia已经被转型为Generic< lnteger >[], 但是这个信息只存在千编译期(并且如果没有@Suppress Warnings注解, 你将得到有关这个转型的警告)。 在运行时, 它仍旧是Objec岱组, 而这将引发间题。 成功创建泛型数组的唯方式就是创建一个被擦除类型的新数组, 然后对其转型。

public class GenericArray<T> {

    private T[] array;
    @SuppressWarnings("unchecked")
    public GenericArray(int sz) {
        array = (T[])new Object[sz];
    }
    public void put(int index, T item) {
        array[index] = item;
    }
    public T get(int index) { return array[index]; }
    // Method that exposes the underlying representation:
    public T[] rep() { return array; }
    public static void main(String[] args) {
        GenericArray<Integer> gai =
                new GenericArray<Integer>(10);
// This causes a ClassCastException:
//! Integer[] ia = gai.rep();
// This is OK:
        Object[] oa = gai.rep();
    }


}


前面相同, 我们并不能声明T[] array = new T[sz], 因此我们创建了一个对象数组, 然后将其转型。rep()方法将返回T[], 它在main()中将用于gai, 因此应该是Integer[], 但是如果调用它, 并尝试着将结果作为Integer[]引用来捕获, 就会得到ClassCastException, 这还是因为实际的运行时类型是Object□。如果在注释掉@SuppressWarnings注解之后再编译GenericArray.java, 编译器就会产生 警告:

因为有了掠除,数组的运行时类型就只能是Object□。如果我们立即将其转型为T[ ],那么在编译期该数组的实际类型就将丢失,而编译器可能会错过某些潜在的错误检查。正因为这样,最好是在集合内部使用Object[],然后当你使用数组元素时,添加一个对T的转型。让我们看看这是如何作用于GenericArray.java示例的:


public class GenericArray2<T> {

    private Object[] array;
    public GenericArray2(int sz) {
        array = new Object[sz];
    }
    public void put(int index, T item) {
        array[index] = item;
    }
    @SuppressWarnings("unchecked")
    public T get(int index) { return (T)array[index]; }
    @SuppressWarnings("unchecked")
    public T[] rep() {
        return (T[])array; // Warning: unchecked cast
    }
    public static void main(String[] args) {
        GenericArray2<Integer> gai =
                new GenericArray2<Integer>(10);
        for(int i = 0; i < 10; i ++)
            gai.put(i, i);
        for(int i = 0; i < 10; i ++)
            System.out.print(gai.get(i) + " ");
        System.out.println();
        try {
            Integer[] ia = gai.rep();
        } catch(Exception e) { System.out.println(e); }
    }


}

初看起来,这好像没多大变化,只是转型挪了地方。如果没有@SuppressWarnings注解,
你仍旧会得到unchecked警告。但是,现在的内部表示是Object[]而不是T[J。当get()被调用时, 它将对象转型为T,这实际上是正确的类型,因此这是安全的。然而,如果你调用rep(),它还是尝试若将Object[ ]转型为T[ ],这仍旧是不正确的,将在编译期产生警告,在运行时产生异常。因此,没有任何方式可以推翻底层的数组类型,它只能是Object□。在内部将array当作Object[] 而不是T[]处理的优势是:我们不太可能忘记这个数组的运行时类型,从而意外地引人缺陷(尽管大多数也可能是所有这类缺陷都可以在运行时快速地探测到)。

对于新代码,应该传递一个类型标记。在这种情况下,GenericArray看起来会像下面这样:

public class GenericArrayWithTypeToken<T> {

    private T[] array;


    @SuppressWarnings("unchecked")
    public GenericArrayWithTypeToken(Class<T> type, int sz) {
        array = (T[]) Array.newInstance(type, sz);
    }

    public void put(int index, T item) {
        array[index] = item;
    }

    public T get(int index) { return array[index]; }


    // Expose the underlying representation:
    public T[] rep() { return array; }


    public static void main(String[] args) {
        GenericArrayWithTypeToken<Integer> gai =
                new GenericArrayWithTypeToken<Integer>(
                        Integer.class, 10);
// This now works:
        Integer[] ia = gai.rep();
    }

}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值