1、与C++的比较
Java中的泛型参考了C++的模板,Java的界限是Java泛型的局限。
2、简单泛型
促成泛型出现最引人注目的一个原因就是为了创造容器类。
首先看一个只能持有单个对象的类,这个类可以明确指定其持有的对象的类型
1 2 3 4 5 class Holder1 { private Circle a; public Holder1 (Circle a) { this .a = a; } Circle get () { return a; } }
上面的类的可重用性不怎么样,无法持有其他类型的任何对象,下面通过持有Object类型的对象实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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 ); Integer x = (Integer)h2.get(); } }
通常而言,我们只会用容器来存储一种类型的队形,泛型的主要目的之一就是用来指定容器要持有什么类型的对象,由编译器来保证类型的正确性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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<Circle> h3 = new Holder3<Circle>(new Circle()); Circle a = h3.get(); } }
也就是告诉编译器想使用什么类型,然后编译器帮你处理一切细节。
2.1、一个一元组类库
为了在一次方法调用返回多个对象,可以使用元组的概念:将一组对象直接打包存储于其中的一个单一对象,这个类容器允许读取其中元素,但是不允许向其中存放新的对象(也称为数据传送对象,或信使)。
元组可以具有任意长度,元组中的对象可以使任意不同类型的,下面的程序是一个二维元组,能够持有两个对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 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 + ")" ; } } 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 + ")" ; } }
为了使用元组,只需定义长度合适的元组,作为方法的返回值就可以了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 class TupleTest { static TwoTuple<String,Integer> f () { return new TwoTuple<String,Integer>("hi" , 47 ); } 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); } } 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、一个堆栈类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class LinkedStack <T > { private static class Node <U > { U item; Node<U> next; Node() { item = null ; next = null ; } Node(U item, Node<U> next) { this .item = item; this .next = next; } boolean end () { return item == null && next == null ; } } private Node<T> top = new Node<T>(); public void push (T item) { top = new Node<T>(item, top); } public T pop () { T result = top.item; if (!top.end()) top = top.next; return result; } public static void 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); } }
2.3、RandomList
持有特定类型对象的列表,每次调用其上的select()方法,可以随机地取一个元素:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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() + " " ); } }
3、泛型接口
泛型接口也可以应用于接口,例如生成器
是工厂模式的一种应用,不过使用生成器创建新的对象的时候,不需要任何的参数,而工厂方法一般需要参数。
下面我就来创建一个生成器来展示泛型在接口中的使用场景
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 interface Generator <T > { T next () ; } 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 () {} private int size = 0 ; public ShapeGenerator (int sz) { size = sz; } public Shape next () { try { return (Shape) types[rand.nextInt(types.length)].newInstance(); } 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 () { 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数列:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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() + " " ); } }
下面编写一个实现了Iterable的Fibonacci生成器,通过继承来创建适配器类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 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 () { throw new UnsupportedOperationException(); } }; } public static void test (String[] args) { for (int i : new IterableFibonacci(18 )) System.out.print(i + " " ); } }
4、泛型方法
4.1、杠杆利用类型参数推断
首先是一个静态方法:
1 2 3 4 5 6 7 8 9 10 class New1 { public static <K, V> Map<K, V> map () { return new HashMap<K, V>(); } public static void test (String[] args) { Map<String, List<Cat>> catMap = New.map(); } }
可以发现,右边的不用再按照以前的这种写法了:
1 Map<String, List> catMap = new HashMap<String, List>();
左边声明部分的类型为右边提供了一种推断,使得编译器可以直接创造出来具体的类了。不过,这种场景没有声明,直接使用New.map()是编译不通过的,因为没有像这里左边的可以推断的依据了, 如下面的,加入f()是一个方法,需要传入一个Map,如下的写法是编译不通过的:
如果确实还是想按照上面那样使用,则可以考虑使用显示类型说明了,在操作符与方法名直接插入尖括号显示的指明类型,代码如下:
1 F(New.<Person, List>map());
不过这种方式很少使用。也就是说,在编写非赋值语句时,才要这样的说明,而使用不了杠杆利用类型推断。
我们为了方便,可以使用同样的方式创建其他的容器了,可惜JDK本身没有提供这样的类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 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>(); } 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、可变参数与泛型方法
可变参数也是可以使用泛型声明类型的:
1 2 3 4 5 6 7 8 9 10 11 12 13 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(),然后在使用的时候才传入需要使用的的具体对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 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()); } } }
4.5、简化元组的使用
我们可以发现之前创建的元组,在使用的时候都传入了一长串具体的类型,通过杠杆利用类型推断参数,我们其实可以直接省略掉那一长串具体的类型了,添加一个static方法,可以使该方法成为更通用的类库的方法了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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实用工具
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 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)); } }
下面的示例使用Sets.difference() 打印出 java.util包中各种Collection类与Map类之间的方法差异:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 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); 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接口的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 class Customer { private static long counter = 1 ; private final long id = counter++; private Customer () {} public String toString () { return "Customer " + id; } 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; } 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); } }
6、构建复杂模型
泛型的一个重要好处是能够简单而安全地创建复杂的模型,例如很容易的创建List元组:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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); } }
下面一个实例展示使用泛型类型来构建复杂模型是多么简单的事情
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 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 )); } } public class Chapter15_6 { public static void main (String[] args) { TupleList.main(args); } }
7、擦除的神秘之处
看个奇怪的问题,考虑下面输出的结果:
1 2 3 Class c1 = new ArrayList<String>().getClass(); Class c2 = new ArrayList<Integer>().getClass(); System.out.println(c1 == c2);
输出的结果竟然是true。
下面我们用Class.getTypeParameters()方法返回TypeVariable对象数组看个究竟:
1 2 System.out.println(Arrays.toString(c1.getTypeParameters())); System.out.println(Arrays.toString(c2.getTypeParameters()));
我们发现输出结果为:
这里只是参数的占位符,所以,在泛型代码内部,无法获得任何有关泛型参数类型的信息。你可以知道诸如类型参数标示符和泛型类型边界这类信息,但却无法知道用来创建某个特定实例的实际的类型参数。Java中的泛型是使用擦除来实现的,所以在使用泛型的时候,任何具体的类型信息都被擦除了,只知道当前使用的是一个对象。所以上面才会出现相等的情况。
7.1、C++的方式
查看下面的一段C++的泛型代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 ##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(); }
C编写的泛型,当模板被实例化时,模板代码知道其模板参数的类型,C 编译器将进行检查,如果泛型对象调用了一个当前实例化对象不存在的方法,则报一个编译期错误。例如上面的manipulate里面调用了obj.f(),因为实例化的HasF存在这个方法,所以不会报错。
而Java是使用擦除实现泛型的,在没有指定边界的情况下,是不能在泛型类里面直接调用泛型对象的方法的,如下面的例子:
1 2 3 4 5 6 7 8 9 10 public class Manipulator <T > { private T obj; public Manipulator (T x) { obj = x; } public void doSomething () { obj.f(); } }
通过没有边界的obj调用f(),编译出错了,下面指定边界,让其通过编译:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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处理时,需要付出额外努力来管理边界。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 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 {} class ErasureAndInheritance { @SuppressWarnings ("unchecked" ) public static void main (String[] args) { Derived2 d2 = new Derived2(); Object obj = d2.get(); d2.set(obj); } }
7.4、边界处的动作
1 2 3 4 5 6 7 8 9 10 11 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、擦除的补偿
正因为类型信息被擦除了,所以和类型相关的代码都无法工作了,如下的:
1 2 3 4 5 6 7 8 9 class Erased <T > { private static final int SIZE = 100 ; public static void f (Object arg) { if (arg instanceof T) {} T var = new T(); T[] array = new T[SIZE]; T[] array = (T)new Object[SIZE]; } }
上面的instanceof方法也没法使用了额,为了在泛型类中能够判断类型,可以引入类型标签:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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" )); System.out.println(ctc.f(1 )); } }
8.1、创建类型实例
我们怎么在一个泛型类中创建泛型的对象呢,上面直接创建的方法也是编译不通过的?我们可以使用泛型工厂的方式。可以保存一个类型标签,使用Class.newInstance()的方式,创建泛型的对象, 但是这种情况,传入的类型标签对应的类必须要有构造函数,所以不推荐这样干,下面说说显示的工厂这种方法(限制其类型,使得只能接受实现了这个工厂的类):
首先来创建一个工厂接口:
1 2 3 interface Factory <T > { T create () ; }
接下来创建一个对象,里面包含了一个需要使用工厂创建的泛型对象:
1 2 3 4 5 6 class Foo <T > { private T x; public <F extends Factory<T>> Foo(F factory){ x = factory.create(); } }
接下来创建显示的工厂:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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(); } } }
这样子我们就可以创建泛型类中的泛型对象了,通过传入上面的显示工厂:
1 2 3 4 5 6 7 8 public class Chapter15_8_1 { public static void main (String[] args) { new Foo<Integer>(new IntegerFactory()); new Foo<Widget>(new Widget.WFactory()); } }
8.2、泛型数组
从上面Erased的例子中可以看出,不能直接创建泛型数组,一般使用ArrayList替代。
1 2 3 4 5 6 7 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 > {}
但是可以按照编译器喜欢的方式来定义一个引用,却永远都不能创建这个确切类型的数组。
1 2 3 class ArrayOfGenericReference { static Generic<Integer>[] gia; }
不能创建这个确切类型的数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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]; gia = new Generic<Integer>[SIZE]; gia = (Generic<Integer>[])new Generic[SIZE]; System.out.println(gia.getClass().getSimpleName()); gia[0 ] = new Generic<Integer>(); gia[1 ] = new Object(); gia[2 ] = new Generic<Double>(); } }
下面是一个泛型数组包装器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 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]; } public T[] rep() { return array; } public static void main (String[] args) { GenericArray<Integer> gai = new GenericArray<Integer>(10 ); Integer[] ia = gai.rep(); Object[] oa = gai.rep(); } }
因为有了擦除,数组的运行时类型就只能是Object[],如果我们立即将其转型为T[],在编译期该数组的实际类型就会丢失,而编译器可能会错过某些潜在的错误检查。正因为这样,最好是在集合内部使用Object[],当使用数组元素时,添加一个对T的类型转换
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 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; } 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); } } }
可以传递一个类型标记,使得rep()方法可以工作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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]; } public T[] rep() { return array; } public static void main (String[] args) { GenericArrayWithTypeToken<Integer> gai = new GenericArrayWithTypeToken<Integer>( Integer.class, 10 ); Integer[] ia = gai.rep(); } }
9、边界
使用无界泛型调用的方法只能是Object可以调用的方法,如果能够将参数类型限定为某个类型子集,就可以用这些类型子集来调用方法了。
使用extends关键字给泛型声明添加边界:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 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(); Item1<GoldenFish> item1 = new Item1<GoldenFish>(fish); item1.doSomething(); } }