java泛型

一、泛型类

在类名后面加上类型T,如下:

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

 

二、泛型接口

interface Generator<T> { T next(); }

 

实现:

interface Generator<T> { T next(); } ///:~

// 现在我们编写一个类,实现Generator<Shape>接口,能够随机生成不同类型的Coffee对象
// 实现了Iterable接口,所以可以再循环语句中使用
class ShapeGenerator implements Generator<Shape>, Iterable<Shape> {
  private Class[] types = { Circle.class, Square.class,
      Triangle.class};
  private static Random rand = new Random(47);
  public ShapeGenerator() {}
  // For iteration:
  private int size = 0;
  public ShapeGenerator(int sz) { size = sz; } 
  public Shape next() {
    try {
      return (Shape)
          types[rand.nextInt(types.length)].newInstance();
    // Report programmer errors at run time:
    } catch(Exception e) {
      throw new RuntimeException(e);
    }
  }
  class ShapeIterator implements Iterator<Shape> {
    int count = size;
    public boolean hasNext() { return count > 0; }
    public Shape next() {
      count--;
      return ShapeGenerator.this.next();
    }
    public void remove() { // Not implemented
      throw new UnsupportedOperationException();
    }
  };

  // 迭代方法
  @Override
  public Iterator<Shape> iterator() {
    return new ShapeIterator();
  }

  public static void test() {
    ShapeGenerator gen = new ShapeGenerator();
    for(int i = 0; i < 5; i++)
      System.out.println(gen.next());
    for(Shape c : new ShapeGenerator(5))
      System.out.println(c);
  }
}

 

三、泛型方法

3.1 杠杆利用类型参数推断

首先是一个静态方法:

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

  // 然后可以这样创建一个Map:
  public static void test(String[] args){
    Map<String, List<Cat>> catMap = New.map();
  }
}

 

可以发现,右边的不用再按照以前的这种写法了:

Map<String, List> catMap = new HashMap<String, List>();

左边声明部分的类型为右边提供了一种推断,使得编译器可以直接创造出来具体的类了。不过,这种场景没有声明,直接使用New.map()是编译不通过的,因为没有像这里左边的可以推断的依据了, 如下面的,加入f()是一个方法,需要传入一个Map,如下的写法是编译不通过的:

f(New.map());

如果确实还是想按照上面那样使用,则可以考虑使用显示类型说明了,在操作符与方法名直接插入尖括号显示的指明类型,代码如下:

F(New.<Person, List>map());

不过这种方式很少使用。也就是说,在编写非赋值语句时,才要这样的说明,而使用不了杠杆利用类型推断。

我们为了方便,可以使用同样的方式创建其他的容器了,可惜JDK本身没有提供这样的类:

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

 

3.2、可变参数与泛型方法

可变参数也是可以使用泛型声明类型的:

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 test(String[] args){
    List<String> ls = makeList("Jay", "Mike");
  }
}

 

3.3、用于Generator的泛型方法

通过使用泛型方法,封装更加抽象的方法,比如下面的fill(),然后在使用的时候才传入需要使用的的具体对象:

class GenericGenerator{

  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 class Chapter15_4_3 {
  public static void main(String[] args){
    Collection<Shape> shapes = GenericGenerator.fill(new ArrayList<Shape>(), new ShapeGenerator(), 2);
    for(Shape a : shapes){
      System.out.println(a);
    }
  }
}

 

3.4、一个通用的Generator

通过使用泛型类,我们更创建一个更加通用的生成器Generator。

class BasicGenerator<T> implements Generator<T> {

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

  @Override
  public T next() {
    try {
      return type.newInstance();
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }
  public static <T> Generator<T> create(Class<T> type){
    return new BasicGenerator<T>(type);
  }
}

 

由于使用了newInstance()方法,所以这里生产的类必须要提供一个默认的无参构造函数。

下面试验一下,创建一个对象,为了标示是新创建的对象,在类里面保存一个static的计数器,每创建一个对象就加1:

class CountObject {

  private static long counter = 0;
  private final long id = counter++;
  public long id(){
    return id;
  }
  public String toString(){
    return "countObject" + id;
  }
  public static void test(String[] args){
    Generator<CountObject> gen = BasicGenerator.create(CountObject.class);
    for(int i=0; i<5; i++){
      System.out.println(gen.next());
    }
  }
}
/*
test 输入结果如下:
countObject0
countObject1
countObject2
countObject3
countObject4
*/

 

3.5、简化元组的使用

我们可以发现之前创建的元组,在使用的时候都传入了一长串具体的类型,通过杠杆利用类型推断参数,我们其实可以直接省略掉那一长串具体的类型了,添加一个static方法,可以使该方法成为更通用的类库的方法了:

class TupleTest2 {

  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 class Chapter15_4_5 {
  public static void main(String[] args){
    // 根据左边的类型自动判断右边的类型,无需手动创建时指明类型了
    ThreeTuple<Cat, Integer, String> tt = TupleTest2.tuple(new Cat(), 1, "Jason");
    System.out.println(tt);
  }
}

 

3.6、一个Set实用工具

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
}

class WatercolorSets {
    public static void main(String[] args) {
  Set<Watercolors> set1 =
    EnumSet.range(Watercolors.BRILLIANT_RED, Watercolors.VIRIDIAN_HUE);
  Set<Watercolors> set2 =
    EnumSet.range(Watercolors.CERULEAN_BLUE_HUE, Watercolors.BURNT_UMBER);
  System.out.println("set1: " + set1);
  System.out.println("set2: " + set2);
  System.out.println("union(set1, set2): " + union(set1, set2));
  Set<Watercolors> subset = intersection(set1, set2);
  System.out.println("intersection(set1, set2): " + subset);
  System.out.println("difference(set1, subset): " +
    difference(set1, subset));    
  System.out.println("difference(set2, subset): " +
    difference(set2, subset));
  System.out.println("complement(set1, set2): " +
    complement(set1, set2));
    } 
} /* Output: (Sample)
  set1: [BRILLIANT_RED, CRIMSON, MAGENTA, ROSE_MADDER, VIOLET, CERULEAN_BLUE_HUE, PHTHALO_BLUE, ULTRAMARINE, COBALT_BLUE_HUE, PERMANENT_GREEN, VIRIDIAN_HUE]
  set2: [CERULEAN_BLUE_HUE, PHTHALO_BLUE, ULTRAMARINE, COBALT_BLUE_HUE, PERMANENT_GREEN, VIRIDIAN_HUE, SAP_GREEN, YELLOW_OCHRE, BURNT_SIENNA, RAW_UMBER, BURNT_UMBER]
  union(set1, set2): [SAP_GREEN, ROSE_MADDER, YELLOW_OCHRE, PERMANENT_GREEN, BURNT_UMBER, COBALT_BLUE_HUE, VIOLET, BRILLIANT_RED, RAW_UMBER, ULTRAMARINE, BURNT_SIENNA, CRIMSON, CERULEAN_BLUE_HUE, PHTHALO_BLUE, MAGENTA, VIRIDIAN_HUE]
  intersection(set1, set2): [ULTRAMARINE, PERMANENT_GREEN, COBALT_BLUE_HUE, PHTHALO_BLUE, CERULEAN_BLUE_HUE, VIRIDIAN_HUE]
  difference(set1, subset): [ROSE_MADDER, CRIMSON, VIOLET, MAGENTA, BRILLIANT_RED]
  difference(set2, subset): [RAW_UMBER, SAP_GREEN, YELLOW_OCHRE, BURNT_SIENNA, BURNT_UMBER]
  complement(set1, set2): [SAP_GREEN, ROSE_MADDER, YELLOW_OCHRE, BURNT_UMBER, VIOLET, BRILLIANT_RED, RAW_UMBER, BURNT_SIENNA, CRIMSON, MAGENTA]
  *///:~

 

4、匿名内部类

泛型方法还可以应用于内部类和匿名内部类,下面是使用匿名内部类实现Generator接口的例子:

class Customer {
    private static long counter = 1;
    private final long id = counter++;
    // private 构造器,强制你使用Generator类的generator方法生成对象
    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(); }
        };
    }
}   

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

class BankTeller {
    public static void serve(Teller t, Customer c) {
        System.out.println(t + " serves " + c);
    }
    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);
    } 
} /* Output:
  Teller 3 serves Customer 1
  Teller 2 serves Customer 2
  Teller 3 serves Customer 3
  Teller 1 serves Customer 4
  Teller 1 serves Customer 5
  Teller 3 serves Customer 6
  Teller 1 serves Customer 7
  Teller 2 serves Customer 8
  Teller 3 serves Customer 9
  Teller 3 serves Customer 10
  Teller 2 serves Customer 11
  Teller 4 serves Customer 12
  Teller 2 serves Customer 13
  Teller 1 serves Customer 14
  Teller 1 serves Customer 15
  *///:~

 

5、构建复杂模型

泛型的一个重要好处是能够简单而安全地创建复杂的模型,下面一个实例展示使用泛型类型来构建复杂模型是多么简单的事情:

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

class Shelf extends ArrayList<Product> {
    public Shelf(int nProducts) {
        Generators.fill(this, Product.generator, nProducts);
    }
}   

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));
    }
} /* Output:
  258: Test, price: $400.99
  861: Test, price: $160.99
  868: Test, price: $417.99
  207: Test, price: $268.99
  551: Test, price: $114.99
  278: Test, price: $804.99
  520: Test, price: $554.99
  140: Test, price: $530.99
  ...
  *///:~

public class Chapter15_6 {
    public static void main(String[] args){
        TupleList.main(args);
    }
}

 

 

6、擦除的神秘之处

看个奇怪的问题,考虑下面输出的结果:

Class c1 = new ArrayList<String>().getClass();
Class c2 = new ArrayList<Integer>().getClass();
System.out.println(c1 == c2)

输出的结果竟然是true。

下面我们用Class.getTypeParameters()方法返回TypeVariable对象数组看个究竟:

System.out.println(Arrays.toString(c1.getTypeParameters()));
System.out.println(Arrays.toString(c2.getTypeParameters()))

我们发现输出结果为:

[E]
[E]

这里只是参数的占位符,所以,在泛型代码内部,无法获得任何有关泛型参数类型的信息。你可以知道诸如类型参数标示符和泛型类型边界这类信息,但却无法知道用来创建某个特定实例的实际的类型参数。Java中的泛型是使用擦除来实现的,所以在使用泛型的时候,任何具体的类型信息都被擦除了,只知道当前使用的是一个对象。所以上面才会出现相等的情况。

Java是使用擦除实现泛型的,在没有指定边界的情况下,是不能在泛型类里面直接调用泛型对象的方法的,如下面的例子:

public class Manipulator<T> {

  private T obj;
  public Manipulator(T x){
    obj = x;
  }
  public void doSomething(){
    obj.f();  // 编译错误
  }
}

通过没有边界的obj调用f(),编译出错了,下面指定边界,让其通过编译:

public class Manipulator<T extends HasF> {

  private T obj;
  public Manipulator(T x){
    obj = x;
  }
  public void doSomething(){
    obj.f();  // 编译通过
  }
}
class HasF{
  public void f(){
    System.out.println("HasF.f();");
  }
}

上面的例子,把泛型类型参数擦除到了HasF,就好像在类的声明中用HasF替换了T一样。

 

6.1、擦除的问题

擦除使得现有的非泛型客户端代码能够在不改变的情况下继续使用,直至客户端准备好用泛型重写这些代码。

但是擦除的代价也是显著的,泛型不能用于显式的引用运行时类型的操作中,如转型,instanceof和new操作符,因为所有关于参数的类型信息都丢失了。无论何时当你在编写泛型代码时,必须时刻提醒自己,你只是看起来好像拥有有关参数的类型信息而已,实际上,它只是一个Object。

当要使用@SuppressWarnings("unchecked") 关闭警告时,最好尽量地“聚焦”,这样就不会因为过于宽泛地关闭警告,而导致意外的屏蔽掉真正的问题。

下面的Derived3的错误意味着编译器期望得到一个原生基类,当你希望将参数类型不要仅仅当做Object处理时,需要付出额外努力来管理边界。

class GenericBase<T> {
  private T element;
  public void set(T arg) { arg = element; }
  public T get() { return element; }
}

class Derived1<T> extends GenericBase<T> {}

class Derived2 extends GenericBase {} // No warning

// class Derived3 extends GenericBase<?> {}
// Strange error:
//   unexpected type found : ?
//   required: class or interface without bounds	

class ErasureAndInheritance {
  @SuppressWarnings("unchecked")
  public static void main(String[] args) {
    Derived2 d2 = new Derived2();
    Object obj = d2.get();
    d2.set(obj); // Warning here!
  }
} ///:~

 

6.2、边界处的动作

class GenericHolder<T> {
  private T obj;
  public void set(T obj) { this.obj = obj; }
  public T get() { return obj; }
  public static void main(String[] args) {
    GenericHolder<String> holder =
        new GenericHolder<String>();
    holder.set("Item");
    String s = holder.get();
  }
}

上面的代码的set()方法会在编译期接受检查,而get()的时候直接取出了String类型,其实此处还是会进行转型的,只不过是由编译器自动插入的相当于插入了这样的代码:(String)holder.get(),详细的转型处理可以编译成字节码查看。

 

7、擦除的补偿

正因为类型信息被擦除了,所以和类型相关的代码都无法工作了,如下的:

class Erased<T> {
  private static 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
  }
}

上面的instanceof方法也没法使用了额,为了在泛型类中能够判断类型,可以引入类型标签:

采用Class的方式,可以判断类型,但是不能只能用instanceof。

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<String> ctc = new ClassTypeCapture<String>(String.class);
    System.out.println(ctc.f("art")); // true
    System.out.println(ctc.f(1));  // false
  }
}

 

7.1、创建类型实例

我们怎么在一个泛型类中创建泛型的对象呢,上面直接创建的方法也是编译不通过的?我们可以使用泛型工厂的方式。可以保存一个类型标签,使用Class.newInstance()的方式,创建泛型的对象, 但是这种情况,传入的类型标签对应的类必须要有构造函数,所以不推荐这样干,下面说说显示的工厂这种方法(限制其类型,使得只能接受实现了这个工厂的类):

首先来创建一个工厂接口:

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

接下来创建一个对象,里面包含了一个需要使用工厂创建的泛型对象:

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

接下来创建显示的工厂:

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

这样子我们就可以创建泛型类中的泛型对象了,通过传入上面的显示工厂:

public class Chapter15_8_1 {
    public static void main(String[] args){
        new Foo<Integer>(new IntegerFactory());
        new Foo<Widget>(new Widget.WFactory());

        // TODO 模板方法设计模式
    }
}

 

上述这种模式很好,需要记牢。

 

7.2、泛型数组

从上面Erased的例子中可以看出,不能直接创建泛型数组,一般使用ArrayList替代

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

class Generic<T> {}

但是可以按照编译器喜欢的方式来定义一个引用,却永远都不能创建这个确切类型的数组,也就是数,你可以声明一个数组,但是不能对其初始化与赋值。

class ArrayOfGenericReference {
    static Generic<Integer>[] gia;
}

不能创建这个确切类型的数组

class ArrayOfGeneric {
    static final int SIZE = 100;
    static Generic<Integer>[] gia;
    @SuppressWarnings("unchecked")
    public static void main(String[] args) {
      gia = (Generic<Integer>[])new Object[SIZE];  // 编译通过,运行报ClassCastException错误,因为数组将跟踪它们的实际类型,而这个类型是在数组被创建时确定的。
      // Runtime type is the raw (erased) type:
      gia = new Generic<Integer>[SIZE];  // 不能这样创建,Cannot create a generic array of Generic<Integer>
      gia = (Generic<Integer>[])new Generic[SIZE];  // 成功创建泛型数组的唯一方法就是创建一个被擦除类型的新数组,然后对其转型。
      System.out.println(gia.getClass().getSimpleName());  // Generic[]
      gia[0] = new Generic<Integer>();
      gia[1] = new Object();  // 错误:cannot convert from Object to Generic<Integer>
      gia[2] = new Generic<Double>();  // 错误:cannot convert from Generic<Double> to Generic<Integer>
    }
}

下面是一个泛型数组包装器

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(); // 返回T[],运行报ClassCastException,还是因为实际的运行时类型是Object[]
    // This is OK:
    Object[] oa = gai.rep();
  }
}

因为有了擦除,数组的运行时类型就只能是Object[],如果我们立即将其转型为T[],在编译期该数组的实际类型就会丢失,而编译器可能会错过某些潜在的错误检查。正因为这样,最好是在集合内部使用Object[],当使用数组元素时,添加一个对T的类型转换

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(); // 这里仍将报转型错误,因此,没有任何方式可以推翻底层的数组类型,它只能是Object[]
    } catch(Exception e) { System.out.println(e); }
  }
} /* Output: (Sample)
0 1 2 3 4 5 6 7 8 9
java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer;
*///:~

可以传递一个类型标记,使得rep()方法可以工作:

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

你可能感兴趣的:(java泛型)