Java泛型

泛型

1、简单泛型

  泛型的主要目的之一就是用来指定容器要持有什么类型的对象,而且由编译器来保证类型的正确性。

  泛型暂时不指定类型,在使用时决定具体使用什么类型。通过<T>来实现,T就是类型参数。

(1)元组

 1 class TwoTuple<A,B>{
 2     public final A first;
 3     public final B second;
 4     public TwoTuple(A a,B b){
 5         first = a;
 6         second = b;
 7     }
 8  9     @Override
10     public String toString() {
11         return "{ " + first +
12                 ", " + second +
13                 '}';
14     }
15 }

 

(2)堆栈

 1 class LinkedStack<T>{
 2     private class Node {
 3         T item;
 4         Node next;
 5         Node() { item = null; next = null; }
 6         Node(T item, Node next) {
 7             this.item = item;
 8             this.next = next;
 9         }
10         boolean end() { return item == null && next == null; }
11     }
12 13     private Node top = new Node();
14     public void push(T item) { top = new Node(item, top); }
15     public T pop() {
16         T result = top.item;
17         if(!top.end())
18             top = top.next;
19         return result;
20     }
21 }
22 (3)RandomList
23 
24 class RandomList<T>{
25     private ArrayList<T> storage = new ArrayList<>();
26     private Random rand = new Random(47);
27     public void add(T item){
28         storage.add(item);
29     }
30     public T select(){
31         return storage.get(rand.nextInt(storage.size()));
32     }
33 }

 

2、泛型接口

  泛型也可以应用于接口,例如生成器,这是一种专门负责创建对象的类。

 1 import net.mindview.util.Generator;
 2 import java.util.Iterator;
 3  4 class Fibonacci implements Generator<Integer> {
 5     private int count = 0;
 6     public Integer next(){
 7         return fib(count++);
 8     }
 9     private int fib(int n){
10         if(n<2) return 1;
11         return fib(n-2) + fib(n-1);
12     }
13 }
14 15 class IterableFibonacci implements Iterable<Integer> {
16     private Fibonacci fib = new Fibonacci();
17     private int n;
18     public IterableFibonacci(int count){
19         n = count;
20     }
21 22     @Override
23     public Iterator<Integer> iterator() {
24         return new Iterator<Integer>() {
25             @Override
26             public boolean hasNext() {
27                 return n>0;
28             }
29 30             @Override
31             public Integer next() {
32                 n--;
33                 return fib.next();
34             }
35             public void remove() { // Not implemented
36                 throw new UnsupportedOperationException();
37             }
38         };
39     }
40 }

 

3、泛型方法

  泛型方法使得该方法能够独立于类而产生变化。使用泛型方法的时候,通常不必指明参数类型,因为编译器会为我们找出具体的类型,这称为类型参数推断。

1 class GenericMethods{
2     public <T> void f(T x){
3      System.out.println(x.getClass().getSimpleName());
4     }
5 }

 

(1)类型推断

  使用泛型有时候需要向程序中加入更多的代码。如下所示:

1 Map<Person,List<? extends Pet>> petPerson = 
2     new HashMap<Person,List<? extends Pet>>();

 

  在泛型方法中可以通过类型推断来简化一部分工作。如下所示:

1 class New{
2     public static <K,V> Map<K,V> map(){
3         return new HashMap<K,V>();
4     }
5 6     public static void main(String[] args) {
7         Map<Person,List<? extends Pet>> petPerson = New.map();
8     }
9 }

 

  类型推断只对赋值操作有效,其他时候并不起作用。如果将一个泛型方法的结果作为参数,传递给另一个方法时,另一个方法需要显式的类型说明。如下所示:

1 public class ExplicitTypeSpecification{
2     static void f(Map<Person,List<? extends Pet>> petPerson){}
3     public static void main(String[] args) {
4         f(New.<Person,List<? extends Pet>>map());
5     }
6 }

 

(2)通用的Generator

 1 import net.mindview.util.Generator;
 2  3 public class BasicGenerator<T> implements Generator<T>{
 4     private Class<T> type;
 5     public BasicGenerator(Class<T> type){
 6         this.type = type;
 7     }
 8     public T next(){
 9         try {
10             return type.newInstance();
11         }catch (Exception e){
12             throw new RuntimeException(e);
13         }
14     }
15     public static <T> Generator<T> create(Class<T> type){
16         return new BasicGenerator<T>(type);
17     }
18 }

 

(3)Set实用工具实现数学方法

 1 public class Sets{
 2     @SuppressWarnings("unchecked")
 3     protected static <T> Set<T> copy(Set<T> s) {
 4         if(s instanceof EnumSet)
 5             return ((EnumSet)s).clone();
 6         return new HashSet<T>(s);
 7     }
 8  9     //并集
10     public static <T> Set<T> union(Set<T> a, Set<T> b) {
11         Set<T> result = copy(a);
12         result.addAll(b);
13         return result;
14     }
15     //交集
16     public static <T> Set<T> intersection(Set<T> a, Set<T> b) {
17         Set<T> result = copy(a);
18         result.retainAll(b);
19         return result;
20     }
21     //差集
22     public static <T> Set<T> difference(Set<T> superset, Set<T> subset) {
23         Set<T> result = copy(superset);
24         result.removeAll(subset);
25         return result;
26     }
27     //包含除了交集以外的所有元素
28     public static <T> Set<T> complement(Set<T> a, Set<T> b) {
29         return difference(union(a, b), intersection(a, b));
30     }
31 }

 

4、擦除

  Java泛型是使用擦除来实现的,这意味着当你在使用泛型时,任何具体的类型信息都被擦除了,你唯一知道的就是你在使用一个对象。因此List<String>和List<Integer>在运行时事实上是相同的类型,都被擦除成它们的“原生”类型List。

(1)迁移兼容性

  泛型类型只有在静态类型检查期间才出现,在此之后,程序中的所有泛型类型都将被擦除,替换为他们的非泛型上界。擦除的核心动机是它使得泛化的客户端可以用非泛化的类库来使用,反之亦然,这经常被称为“迁移兼容性”。

(2)擦除的问题

  泛型的所有关于参数的类型信息都丢失了,所以不能用于显式地引用运行时类型的操作之中,例如转型、instanceof操作和new表达式。

 

5、擦除的补偿

(1)由于擦除原因,无法通过instanceof比较类型。如果引入类型标签,就可以转而使用动态的isInstance()。

1 public class ClassTypeCapture<T>{
2     Class<T> kind;
3     public ClassTypeCapture(Class<T> kind){
4         this.kind = kind;
5     }
6     public boolean f(Object arg){
7         return kind.isInstance(arg);
8     }
9 }

 

(2)创建类型实例

  通过工厂对象来创建实例。如果使用类型标签,就可以使用newInstance()来创建这个类型的新对象。

 1 class ClassAsFactory<T>{
 2     T x;
 3     public ClassAsFactory(Class<T> kind){
 4         try{
 5             x = kind.newInstance();
 6         }catch(Exception e){
 7             throw new RuntimeException(e);
 8         }
 9     }
10 }

 

  如果类没有默认的构造器,上面的案例会创建失败。为了解决这个问题,可以通过显示的工厂来实现。

 1 interface FactoryI<T>{
 2     T create();
 3 }
 4 
 5 class Foo2<T>{
 6     private T x;
 7     public <F extends FactoryI<T>> Foo2(F factory){
 8         x = factory.create();
 9     }
10 }
11 
12 class IntegerFactory implements FactoryI<Integer>{
13     public Integer create(){
14         return new Integer(6);
15     }
16 }

 

  另一种方式是模板方法设计模式。

 1 abstract class GenericWithCreate<T>{
 2     final T element;
 3     GenericWithCreate(){ element = create(); }
 4     abstract T create();
 5 }
 6  7 class X{}
 8  9 class Creator extends GenericWithCreate<X>{
10     X create(){ return new X(); }
11 }

 

(3)泛型数组

  无法通过 T[] array = new T[sz] 来创建泛型数组,一般的解决方法是在需要泛型数组的地方都使用ArrayList。

  在创建泛型数组时,有以下三种情况:

    ①创建时强制转型

 1 public class GenericArray<T>{
 2     private T[] array;
 3     @SuppressWarnings("unchecked")
 4     public GenericArray(int sz){
 5         array = (T[])new Object[sz];
 6     }
 7     public T[] rep(){ return array; }
 8  9     public static void main(String[] args) {
10         GenericArray<Integer> gai = new GenericArray<Integer>(10);
11         Integer[] ia = gai.rep();//引起ClassCastException
12         Object[] oa = gai.rep();
13     }
14 }

 

    ②方法返回时强制转型

 1 class GenericArray2<T>{
 2     private Object[] array;
 3     @SuppressWarnings("unchecked")
 4     public GenericArray(int sz){
 5         array = new Object[sz];
 6     }
 7     public T[] rep(){ return (T[])array; }
 8 
 9     public static void main(String[] args) {
10         GenericArray<Integer> gai = new GenericArray<Integer>(10);
11         Integer[] ia = gai.rep();//引起ClassCastException
12         Object[] oa = gai.rep();
13     }
14 }

 

    ③使用Array.newInstance()

      以上两种方法都无法创建具体类型的数组,无法推翻底层的数组类型,只能是Object[]。通过传入类型标记Class<T>,可以从擦除中恢复。

 1 class GenericArray3<T>{
 2     private T[] array;
 3     @SuppressWarnings("unchecked")
 4     public GenericArray(Class<T> type,int sz){
 5         array = (T[]) Array.newInstance(type,sz);
 6     }
 7     public T[] rep(){ return array; }
 8 
 9     public static void main(String[] args) {
10         GenericArray<Integer> gai = new GenericArray<Integer>(Integer.class,10);
11         Integer[] ia = gai.rep();//可以正常运行
12         Object[] oa = gai.rep();
13     }
14 }

 

6、边界

  边界使得你可以在用于泛型的参数类型上设置限制条件,可以按照自己的边界类型来调用方法。

 1 public class Test {
 2     public static void main(String[] args) {
 3         Man m = new Man();
 4         m.hear();
 5         m.smell();
 6     }
 7 }
 8  9 interface SuperPower{}
10 interface SuperHearing extends SuperPower{
11     void hearSubtleNoises();
12 }
13 interface SuperSmell extends SuperPower{
14     void trackBySmell();
15 }
16 17 class SuperHero<POWER extends SuperPower>{
18     POWER power;
19     SuperHero(POWER power){ this.power = power; }
20     POWER getPower(){ return power; }
21 }
22 23 class CaineHero<POWER extends SuperHearing & SuperSmell> extends SuperHero<POWER>{
24     CaineHero(POWER power){ super(power); }
25     void hear(){ power.hearSubtleNoises(); }
26     void smell(){ power.trackBySmell(); }
27 }
28 29 class SuperHearSmell implements SuperHearing,SuperSmell{
30 31     @Override
32     public void hearSubtleNoises() {
33         System.out.println("hearSubtleNoises");
34     }
35 36     @Override
37     public void trackBySmell() {
38         System.out.println("trackBySmell");
39     }
40 }
41 42 class Man extends CaineHero<SuperHearSmell>{
43     Man(){ super(new SuperHearSmell()); }
44 }

 

7、通配符

(1)List<? extends Fruit>协变

  表示具有任何从Fruit继承的类型的列表。List<? extends Fruit>可以合法地指向一个List<Apple>。一旦执行这种类型的向上转型,就将丢失掉向其中传递任何对象的能力,甚至是传递Object也不行。

 1 List<? extends Fruit> flist = 
 2     Arrays.asList(new Apple());
 3 //Compile Error:can't add any type of object
 4 //add()的参数是<? extends Fruit>,编译器不知道需要Fruit的哪个
 5 //具体的子类型,因此不接受任何类型的Fruit
 6 //flist.add(new Apple());
 7 //flist.add(new Fruit());
 8 //flist.add(new Object());
 9 flist.add(null);//Legal but uninteresting
10 Apple a = (Apple)flist.get(0);//No warning
11 Fruit f = flist.get(0);//No warning
12 flist.contains(new Apple());//参数是Object
13 flist.indexOf(new Apple());//参数是Object

 

(2)List<? super Fruit>逆变

  超类型通配符可以安全地传递一个类型对象到泛型类型中。List<? super Fruit>意味着向其中添加Fruit或Fruit的子类型是安全的。

1 List<? super Fruit> flist = new ArrayList<Fruit>();
2         flist.add(new Apple());
3         flist.add(new Fruit());
4 //Error:Incompatible Type
5 //Fruit f = flist.get(0);
6 Object f = flist.get(0);//OK,but type information has been lost

 

(3)无界通配符List<?>

  List实际上表示“持有任何Object类型的原生List”,List<?>表示“具有某种特定类型的非原生List,只是我们不知道那种类型是什么”,List<? extends Object>表示“类型是Object的导出类”。

  无界通配符的一个重要应用:处理多个泛型参数时,允许一个参数可以是任何类型,同时为其他参数确定某种特定类型。

1 Map<String,?> map = new HashMap<String,Integer>;
2 map = new HashMap<String,String>;

 

  原生Holder与Holder<?>是大致相同的事物,但存在不同。它们会揭示相同的问题,但是后者将这些问题作为错误而不是警告报告。

 1 static void rawArgs(Holder holder,Object arg){
 2     //holder.set(arg);
 3     //Warning:Unchecked call to set(T) as member 
 4     //of the raw type Holder
 5     //holder.set(new Wildcards());//Same Warning
 6     
 7     //Can't do this:don't have any 'T'
 8     //T t = holder.get();
 9     
10     //OK,but type infomation has been lost
11     Object obj = holder.get();
12 }
13 
14 //Similar to rawArgs(),but errors instead of warnings
15 static void unboundedArg(Holder<?> holder,Object arg){
16     //holder.set(arg);
17     //Error:set(capture of ?) in Holder<capture of ?> 
18     //cannot be applied to (Object)
19     //holder.set(new Wildcards());//Same Error
20     
21     //Can't do this:don't have any 'T'
22     //T t = holder.get();
23     
24     //OK,but type infomation has been lost
25     Object obj = holder.get();
26 }

 

(4)捕获转换

  未指定的通配符类型被捕获,并被转换为确切类型。在f2()中调用f1(),参数类型在调用f2()的过程中被捕获,因此它可以在对f1()的调用中被使用。不能从f2()中返回T,因为T对于f2()来说是未知的。

1 static <T> void f1(Holder<T> holder){
2     T t = holder.get();
3      System.out.println(t.getClass().getSimpleName());
4 }
5 6 static <T> void f2(Holder<T> holder){
7     f1(holder);
8 }

 

8、问题

(1)任何基本类型都不能作为类型参数

(2)实现参数化接口

  一个类不能实现同一个泛型接口的两种变体。将泛型参数移除掉后,这段代码就可以正常编译了。

1 interface Payable<T>{}
2 3 class Employee implements Payable<Employee>{}
4 5 //Compile Error:cannot be inherited with different type arguments
6 class Hourly extends Employee implements Payable<Hourly>{}

 

(3)转型和警告

  使用带有泛型类型参数的转型或instanceof不会有任何效果。

  由于擦除原因,编译器无法知道这个转型是否安全,并且pop()方法实际上并没有执行任何转型。如果没有@SuppressWarnings注解,编译器将对pop()产生“Unchecked cast”警告。

1 private int index = 0;
2 private Object[] storage;
3 @SuppressWarnings("unchecked")
4 public T pop(){ return (T)storage[--index]; }

 

(4)重载

  由于擦除的原因,重载方法将产生相同的类型签名,导致程序不能编译。

1 public class UseList<W,T>{
2     void f(List<T> v){}
3     void f(List<W> v){}
4 }

 

(5)基类劫持了接口

  一旦为Comparable确定了ComparablePet参数,那么其他任何实现类都不能与ComparablePet之外的任何对象比较。在前面的“实现参数化接口”章节里面的第一个例子,就体现了基类劫持接口。

 1 public class ComparablePet
 2 implements Comparable<ComparablePet> {
 3   public int compareTo(ComparablePet arg) { 
 4       return 0; 
 5   }
 6 } 
 7  8 class Cat extends ComparablePet implements Comparable<Cat>{
 9   // Error: Comparable cannot be inherited with
10   // different arguments: <Cat> and <Pet>
11   public int compareTo(Cat arg) { return 0; }
12 } ///:~
13 14 class Hamster extends ComparablePet
15     implements Comparable<ComparablePet>{
16     public int compareTo(ComparablePet arg) { 
17         return 0; 
18     }
19 }

 

9、自限定

  class Subtype extends BasicHolder<Subtype> {}这样用,就构成自限定了。从定义上来说,它继承的父类的类型参数是它自己。

  从使用上来说,Subtype对象本身的类型是Subtype,且Subtype对象继承而来的成员(element)、方法的形参(set方法)、方法的返回值(get方法)也是Subtype了(这就是自限定的重要作用)。这样Subtype对象就只允许和Subtype对象(而不是别的类型的对象)交互了。

 1 class BasicHolder<T> {
 2     T element;
 3     void set(T arg) { element = arg; }
 4     T get() { return element; }
 5     void f() {
 6         System.out.println(element.getClass().getSimpleName());
 7     }
 8 }
 9 10 class Subtype extends BasicHolder<Subtype> {}
11 12 public class CRGWithBasicHolder {
13     public static void main(String[] args) {
14         Subtype st1 = new Subtype(), st2 = new Subtype(), st3 = new Subtype();
15         st1.set(st2);
16         st2.set(st3);
17         Subtype st4 = st1.get().get();
18         st1.f();
19     }
20 } /* Output:
21 Subtype
22 */

 

10、异常

  由于擦除原因,将泛型应用于异常是非常受限的。但是,类型参数可能会在一个方法的throws子句中用到,这使得你可以编写随检查型异常的类型而发生变化的泛型代码。

 1 interface
 2 Processor<T,E extends Exception> {
 3     void process(List<T> resultCollector) throws E;
 4 }
 5  6 class
 7 ProcessRunner<T,E extends Exception>
 8         extends ArrayList<Processor<T,E>> {
 9     List<T> processAll() throws E {
10         List<T> resultCollector = new ArrayList<T>();
11         for(Processor<T,E> processor : this)
12             processor.process(resultCollector);
13         return resultCollector;
14     }
15 }
16 17 class Failure extends Exception {}
18 19 class Processor1 implements
20         Processor<String,Failure> {
21     static int count = 3;
22     public void process(List<String> resultCollector)
23             throws Failure1_1, Failure1_2 {
24         if(count-- > 1)
25             resultCollector.add("Hep!");
26         else
27             resultCollector.add("Ho!");
28         if(count < 0)
29                 throw new Failure1();
30     }
31 }
32 33 public class Test {
34     public static void main(String[] args) {
35         ProcessRunner<String,Failure> runner =
36                 new ProcessRunner<String,Failure>();
37         for(int i = 0; i < 3; i++)
38             runner.add(new Processor1());
39         try {
40             System.out.println(runner.processAll());
41         } catch(Failure e) {
42             System.out.println(e);
43         } 
44     }
45 }

 

  参考于《Java编程思想》,第352~432页

 

 

posted @ 2021-06-30 10:12  sumAll  阅读(64)  评论(0编辑  收藏  举报