Java笔记 – 泛型 泛型方法 泛型接口 擦除 边界 通配符(1)
本文由发表于4年前 | J2EE | 暂无评论 |  被围观 4,791 views+
1、与C++的比较2、简单泛型2.1、一个一元组类库2.2、一个堆栈类2.3、RandomList3、泛型接口4、泛型方法4.1、杠杆利用类型参数推断4.2、可变参数与泛型方法4.3、用于Generator的泛型方法4.4、一个通用的Generator4.5、简化元组的使用4.6、一个Set实用工具5、匿名内部类6、构建复杂模型7、擦除的神秘之处7.1、C++的方式7.2、迁移兼容性7.3、擦除的问题7.4、边界处的动作8、擦除的补偿8.1、创建类型实例8.2、泛型数组9、边界
1、与C++的比较

Java中的泛型参考了C++的模板,Java的界限是Java泛型的局限。

2、简单泛型

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

首先看一个只能持有单个对象的类,这个类可以明确指定其持有的对象的类型

class Holder1 {
    private Circle a;
    public Holder1(Circle a) { this.a = a; }
    Circle get() { return a; }
}

上面的类的可重用性不怎么样,无法持有其他类型的任何对象,下面通过持有Object类型的对象实现

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 Circle());
      Circle a = (Circle)h2.get();
      h2.set("Not an Automobile");
      String s = (String)h2.get();
      h2.set(1); // Autoboxes to Integer
      Integer x = (Integer)h2.get();
    }
}

通常而言,我们只会用容器来存储一种类型的队形,泛型的主要目的之一就是用来指定容器要持有什么类型的对象,由编译器来保证类型的正确性:

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对象时,必须指明想持有什么类型的对象,然后只能存入该类型(或其子类型,因为多台与泛型不冲突)的对象了
        // 取出对象的时候,会自动转型为正确的类型
        Holder3<Circle> h3 =
                new Holder3<Circle>(new Circle());
        Circle a = h3.get(); // No cast needed
        // h3.set("Not an Automobile"); // Error
        // h3.set(1); // Error
    }
}

也就是告诉编译器想使用什么类型,然后编译器帮你处理一切细节。

2.1、一个一元组类库

为了在一次方法调用返回多个对象,可以使用元组的概念:将一组对象直接打包存储于其中的一个单一对象,这个类容器允许读取其中元素,但是不允许向其中存放新的对象(也称为数据传送对象,或信使)。

元组可以具有任意长度,元组中的对象可以使任意不同类型的,下面的程序是一个二维元组,能够持有两个对象:

class TwoTuple<A,B> {
    public final A first;  // 声明为final,同样确保了public的安全性,不可改写,如果想要使用具有不同元素的元组,就强制要求另外创建一个新的TwoTuple对象
    public final B second;
    // 元组隐含的保持了其中元素的次序
    public TwoTuple(A a, B b) { first = a; second = b; }
    public String toString() {
        return "(" + first + ", " + second + ")";
    }
}

/**
 * 使用继承机制实现长度更长的元组
 */
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 +")";
    }
}

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

为了使用元组,只需定义长度合适的元组,作为方法的返回值就可以了

class TupleTest {
    static TwoTuple<String,Integer> f() {
        // Autoboxing converts the int to Integer:
        return new TwoTuple<String,Integer>("hi", 47);  // 这里的new语句有点啰嗦,后面有方法简化
    }
    static ThreeTuple<Circle,String,Integer> g() {
        return new ThreeTuple<Circle, String, Integer>(
                new Circle(), "hi", 47);
    }

    static FourTuple<Circle,Square,String,Integer> h() {
      return
        new FourTuple<Circle,Square,String,Integer>(
          new Circle(), new Square(), "hi", 47);
    }
    static FiveTuple<Circle,Square,String,Integer,Double> k() {
      return new
        FiveTuple<Circle,Square,String,Integer,Double>(
          new Circle(), new Square(), "hi", 47, 11.1);
    }

    public static void test() {
        TwoTuple<String,Integer> ttsi = f();
        FourTuple<Circle,Square,String,Integer> ts = h();
        System.out.println(ttsi);
        // ttsi.first = "there"; // Compile error: final
    }
}

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 Chapter15_2_1 {
    public static void main(String args){
        TupleTest.test();
    }
}
2.2、一个堆栈类
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>(); // 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 test() {
        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);
    }
} /* Output:
  stun!
  on
  Phasers
  *///:~
2.3、RandomList

持有特定类型对象的列表,每次调用其上的select()方法,可以随机地取一个元素:

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() + " ");
    }
} 
/* Output:
brown over fox quick quick dog brown The brown lazy brown
*///:~
3、泛型接口

泛型接口也可以应用于接口,例如生成器

是工厂模式的一种应用,不过使用生成器创建新的对象的时候,不需要任何的参数,而工厂方法一般需要参数。

下面我就来创建一个生成器来展示泛型在接口中的使用场景

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

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

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 test(String[] args) {
        Fibonacci gen = new Fibonacci();
        for(int i = 0; i < 18; i++)
            System.out.print(gen.next() + " ");
    }
} 
/* Output:
  1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584
*///:~

下面编写一个实现了Iterable的Fibonacci生成器,通过继承来创建适配器类:

class IterableFibonacci extends Fibonacci implements Iterable<Integer> {
  private int n;
  public IterableFibonacci(int count) { n = count; }

  @Override
  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();
      }
    };
  } 
  public static void test(String[] args) {
    for(int i : new IterableFibonacci(18))
      System.out.print(i + " ");
  }
} /* Output:
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584
*///:~
4、泛型方法
4.1、杠杆利用类型参数推断

首先是一个静态方法:

class New1 {
    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();
    }
}
4.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");
    }
}
4.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);
        }
    }
}
4.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
*/
4.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);
    }
}
4.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]
  *///:~

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

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 test(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);
    }
}
5、匿名内部类

泛型方法还可以应用于内部类和匿名内部类,下面是使用匿名内部类实现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
  *///:~
6、构建复杂模型

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

class TupleList<A,B,C,D>
    extends ArrayList<FourTuple<A,B,C,D>> {
    public static void main(String[] args) {
        TupleList<Circle, Square, String, Integer> tl =
                new TupleList<Circle, Square, String, Integer>();
        tl.add(TupleTest.h());
        tl.add(TupleTest.h());
        for(FourTuple<Circle,Square,String,Integer> i: tl)
            System.out.println(i);
    }
} /* Output: (75% match)
(Circle, com.itzhai.javanote.chapter_15_Generics.Square@1befab0, hi, 47)
(Circle, com.itzhai.javanote.chapter_15_Generics.Square@13c5982, hi, 47)
*///:~

下面一个实例展示使用泛型类型来构建复杂模型是多么简单的事情

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);
    }
}
7、擦除的神秘之处

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

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中的泛型是使用擦除来实现的,所以在使用泛型的时候,任何具体的类型信息都被擦除了,只知道当前使用的是一个对象。所以上面才会出现相等的情况。

7.1、C++的方式

查看下面的一段C++的泛型代码:

#include <iostream>
using namespace std;

template<class T> class Manipulator {
  T obj;
public:
  Manipulator(T x) { obj = x; }
  void manipulate() { obj.f(); }
};

class HasF {
public:
  void f() { cout << "HasF::f()" << endl; }
};

int main() {
  HasF hf;
  Manipulator<HasF> manipulator(hf);
  manipulator.manipulate();
} /* Output:
HasF::f()

C++编写的泛型,当模板被实例化时,模板代码知道其模板参数的类型,C++编译器将进行检查,如果泛型对象调用了一个当前实例化对象不存在的方法,则报一个编译期错误。例如上面的manipulate里面调用了obj.f(),因为实例化的HasF存在这个方法,所以不会报错。

而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一样。

7.2、迁移兼容性

泛型是JDK1.5才出现的,所以为了兼容,采用了擦除的方式实现。泛型类型只有在静态类型检查期间才出现,在此之后,程序中所有泛型类型都被擦除,替换为他们的非泛型上界。例如List将被擦除为List,而普通的类型变量在未指定边界的情况下将被擦除为Object。

7.3、擦除的问题

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

但是擦除的代价也是显著的,泛型不能用于显式的引用运行时类型的操作中,如转型,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!
    }
} ///:~
7.4、边界处的动作
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(),详细的转型处理可以编译成字节码查看。

8、擦除的补偿

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

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 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
    }
}
8.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 模板方法设计模式
    }
}
8.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();
    }
} ///:~
9、边界

使用无界泛型调用的方法只能是Object可以调用的方法,如果能够将参数类型限定为某个类型子集,就可以用这些类型子集来调用方法了。

使用extends关键字给泛型声明添加边界:

interface Animal{
    public void speek();
}
interface Fish{
    public void bubble();
}
class GoldenFish implements Animal, Fish{
    @Override
    public void bubble() {
        System.out.println("O。.");
    }
    @Override
    public void speek() {
        System.out.println("wow~");
    }
}
class HoldItem<T>{
    T item;
    HoldItem(T item){ this.item = item; }
    T getItem() { return item; }
}
class Item1<T extends Animal & Fish> extends HoldItem<T>{
    Item1(T item){ super(item); }
    public void doSomething(){
        item.speek();
        item.bubble();
    }
}

public class Chapter15_9 {
    public static void main(String[] args){
        GoldenFish fish = new GoldenFish();
        // 创建泛型类,super关键字对应的类继承结构
        Item1<GoldenFish> item1 = new Item1<GoldenFish>(fish);
        item1.doSomething();
    }
}
除了文章中有特别说明,均为IT宅原创文章,转载请以链接形式注明出处。
本文链接:http://www.itzhai.com/java-bi-ji-fan-xing-fan-xing-fang-fa-fan-xing-jie-kou-ca-chu-bian-jie-tong-pei-fu.html
关键字: , ,
arthinking Java技术交流群:280755654,入门群:428693174 more
分享到:
 
2014 3/16
如果您有更好的原创技术博文或者观点,欢迎投稿:admin@itzhai.com,或者关注订阅左侧浮动面板的微信号订阅IT宅itread)发送消息。
文章评论
    没有评论
给我留言

有人回复时邮件通知我
J2EE的相关文章
随机文章 本月热门 热评
1 IT宅书籍推荐:《富爸爸,穷爸爸 》 2012/1/14
2 数据结构笔记 – 排序算法 希尔排序算法 2011/9/21
3 Java Web笔记 – Servlet多线程同步问题及其解决方法 2011/11/10
4 Java使用默认浏览器打开超链接 2011/5/10
5 使用masm for windows编译并跟踪调试程序 2011/4/14
6 Linux中sed和awk的使用及其相关实例解析 2011/6/11
友情推荐 更多
破博客 文官洗碗安天下,武将打怪定乾坤。多么美好的年代,思之令人泪落。
Mr.5's Life 白天是一名程序员,晚上就是个有抱负的探索者
行知-追寻技术之美 关注大数据,分布式系统
我爱编程 编程成长轨迹
Cynthia's Blog 学习笔记 知识总结 思考感悟
 
猜您喜欢
欢迎关注我的公众号 IT宅
关于IT宅 文章归档

IT宅中的文章除了标题注明转载或有特别说明的文章,均为IT宅的技术知识总结,学习笔记或随笔。如果喜欢,请使用文章下面提供的分享组件。转载请注明出处并加入文章的原链接。 感谢大家的支持。

联系我们:admin@itzhai.com

Theme by arthinking. Copyright © 2011-2015 IT宅.com 保留所有权利.