标签:
一、泛型类
在类名后面加上类型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(); } } ///:~
标签:
原文地址:http://www.cnblogs.com/liusc0424/p/4656422.html