Java笔记 – 泛型 泛型方法 泛型接口 擦除 边界 通配符(1)

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 {
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 h3 =
new Holder3(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 {
private static class Node {
U item;
Node next;
Node() { item = null; next = null; }
Node(U item, Node next) {
this.item = item;
this.next = next;
}
boolean end() { return item == null && next == null; }
}
private Node top = new Node(); // End sentinel
public void push(T item) {
top = new Node(item, top);
}
public T pop() {
T result = top.item;
if(!top.end())
top = top.next;
return result;
}
public static void test() {
LinkedStack lss = new LinkedStack();
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 {
private ArrayList storage = new ArrayList();
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 rs = new RandomList();
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 next(); } ///:~

// 现在我们编写一个类,实现Generator接口,能够随机生成不同类型的Coffee对象
// 实现了Iterable接口,所以可以再循环语句中使用
class ShapeGenerator implements Generator, Iterable {
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 {
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 {
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 {
private int n;
public IterableFibonacci(int count) { n = count; }

@Override
public Iterator iterator() {
return new Iterator() {
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 List list() {
return new ArrayList();
}
public static LinkedList lList() {
return new LinkedList();
}
public static Set set() {
return new HashSet();
}
public static Queue queue() {
return new LinkedList();
}
// Examples:
public static void test(String[] args) {
Map<String, List> sls = New.map();
List ls = New.list();
LinkedList lls = New.lList();
Set ss = New.set();
Queue 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 shapes = GenericGenerator.fill(new ArrayList(), new ShapeGenerator(), 2);
for(Shape a : shapes){
System.out.println(a);
}
}
}

4.4、一个通用的Generator

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

class BasicGenerator implements Generator {

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 set1 =
EnumSet.range(Watercolors.BRILLIANT_RED, Watercolors.VIRIDIAN_HUE);
Set 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 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 methodSet(Class<?> type) {
Set result = new TreeSet();
for(Method m : type.getMethods())
result.add(m.getName());
return result;
}
static void interfaces(Class<?> type) {
System.out.print(“Interfaces in “ +
type.getSimpleName() + “: “);
List result = new ArrayList();
for(Class<?> c : type.getInterfaces())
result.add(c.getSimpleName());
System.out.println(result);
}
static Set 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 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 generator() {
return new Generator() {
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 generator =
new Generator() {
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 line = new LinkedList();
Generators.fill(line, Customer.generator(), 15);
List tellers = new ArrayList();
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 generator =
new Generator() {
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 {
public Shelf(int nProducts) {
Generators.fill(this, Product.generator, nProducts);
}
}

class Aisle extends ArrayList {
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 {
private ArrayList checkouts =
new ArrayList();
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().getClass();
Class c2 = new ArrayList().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
using namespace std;

template 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 manipulator(hf);
manipulator.manipulate();
} /* Output:
HasF::f()

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

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

public class Manipulator {

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

}

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

public class Manipulator {

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 {
private T element;
public void set(T arg) { arg = element; }
public T get() { return element; }
}

class Derived1 extends GenericBase {}

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 {
private T obj;
public void set(T obj) { this.obj = obj; }
public T get() { return obj; }
public static void main(String[] args) {
GenericHolder holder =
new GenericHolder();
holder.set(“Item”);
String s = holder.get();
}
}

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

8、擦除的补偿

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

class Erased {
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 {

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

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

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

接下来创建显示的工厂:

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

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

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

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

}

8.2、泛型数组

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

class ListOfGenerics {
private List array = new ArrayList();
public void add(T item) { array.add(item); }
public T get(int index) { return array.get(index); }
}

class Generic {}

但是可以按照编译器喜欢的方式来定义一个引用,却永远都不能创建这个确切类型的数组。

class ArrayOfGenericReference {
static Generic[] gia;
}

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

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

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

class GenericArray {
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 gai =
new GenericArray(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 {
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 gai =
new GenericArray2(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 {
private T[] array;
@SuppressWarnings(“unchecked”)
public GenericArrayWithTypeToken(Class 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 gai =
new GenericArrayWithTypeToken(
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 item;
HoldItem(T item){ this.item = item; }
T getItem() { return item; }
}
class Item1 extends HoldItem{
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 item1 = new Item1(fish);
item1.doSomething();
}
}

arthinking wechat
欢迎关注itzhai公众号