泛型的思想及应用

为编写更通用的代码,要使代码能够应用于N种不具体的类型,而不仅仅为几个接口或类服务。这时候引入了泛型的概念,它的意思是"适用于丰富的类型",旨在解除类或方法与所使用的类型之间的约束。使用别人已经构建好的泛型类型会相当容易,但要自己创建一个泛型实例,会遇到许多令人惊讶的事情。掌握C++当中的模板概念,将帮助我们去理解泛型的本质以及java泛型的局限。

容器类

在创造具备重用性的容器类的过程中,泛型应运而生。有些情况下,确实希望容器能够同时持有多种类型的对象。但通常而言,只会使用容器来存储某种类型的对象。而泛型的主要目的之一便是指定容器类要持有什么类型的对象,由编译器来保证类型的正确性。类型参数可推迟决定具体使用什么类型,用<>括住,置于类名后面。而在使用这个类的时侯,必须指明想持有什么类型的对象。

public class Page<T>{
  private T a;
  public Page(T a){ this.a=a;}
  public void set(T a){this.a=a;}
  public T get(){return a;}
}

元组

常常需要一次返回多个对象,而元组能够一次性地解决这样的需求,它允许读取其中元素,但是不允许向其中存放新的对象(依赖final机制实现)。元组可以含有任意个不同类型

的对象。下面的程序是一个2维元组,它持有两个对象。

public class TwoTuple<A,B>{
  //final保证无法将其它值赋予first和second,但可以任意使用这两个对象
  public final A first;
  public final B second;
  public TwoTuple(A a,B b){first=a;second=b;}
}
以上设计形式是考虑到有时确实希望编程人员改变first或second所引用的对象,他们可创建一个新的TwoTuple对象,达到使用具有不同元素的元组。还可以利用继承实现长度更长的元组。使用方式如下:

static TwoTuple<String,Integer> f() {
  return new TwoTuple<String,Integer>("hello",88);
}

泛型接口

还能够使用泛型实现堆栈LinkedStack<T>、一个可以应用于各种类型对象的工具列表RandomList<T>
泛型接口,比如生成器就应用了此机制,它与工厂方法相相比,无需额外的信息(参数)就知道如何创建对象,一般只定义一个无参方法next()

public interface Generator<T>{
  T next();
}

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

现在要获得一个实现了Iterable的Fibonacci生成器,且无法重写Fibonacci这个类的时候,可选择创建一个适配器类来实现所需接口,组合和继承均能够实现适配器设计模式。

public class IterableFibonacci extends Fibonacci implements Iterable<Integer>{
  private int size;
  public IterableFibonacci(int count){ 
    size=count;
  }
  public Iterator<Integer> iterator(){
    return new Interator<Integer>(){
      public boolean hasNext(){ return size>0;}
      public Integer next(){
        size--;
        return IterableFibonacci.this.next();
      }
      public void remove(){
        //懒得实现
      }
    };
  }
  public static void main(String[] args){
    for(int i:new IterableFibonacci(20))
      System.out.print(i+" ");
  }
}

泛型方法

拥有泛型方法的类可以是泛型类,也可以不是泛型类。无论何时,若只使用泛型方法可以取代将整个类泛型化的话,那就应该只使用泛型方法。定义时只需将泛型参数列表置于返回类型之前。使用泛型方法时,通常不必指明参数类型,类型参数推断避免了重复的泛型参数列表的输写,但它只对赋值操作有效。

public class Tool{
  public static <K,V> Map<K,V> map(){
    return new HashMap<K,V>();
  }
  public static <T> LinkedList<T> lList(){
    return new LinkedList<T>();
  }
  public <T> void f(T x){
    System.out.println(x.getClass().getName());
  }
  public static void main(String[] args){
    Tool tool=new Tool();
    tool.f("");tool.f(1.0);
    Map<Person,List<? extends Pet>> petPeople=tool.map();
    LinkedList<String> lls=tool.lList();
  }
}

使用泛型方法来构建一个Set实用工具

public class Sets{
  public static <T> Set<T> union(Set<T> a,Set<T> b){
    //将Set中的所有引用都存入一个新的HashSet对象,并不直接修改参数中的Set
    Set<T> result=new HashSet<T>(a);
    result.addAll(b);
    return result;
  }
  public static <T> Set<T> intersection(Set<T> a,Set<T> b){
    Set<T> result=new HashSet<T>(a);
    result.retainAll(b);
    return result;
  }
  public static <T> Set<T> difference(Set<T> superset,Set<T> subset){
    Set<T> result=new HashSet<T>(superset);
    result.removeAll(subset);
    return result;
  }
  public static <T> Set<T> complement(Set<T> a,Set<T> b){
    return difference(union(a,b),intersection(a,b));
  }
}
当然也可使用Sets.difference()方法求得差异Set<String>。

使用匿名内部类来实现Generator泛型接口

class Customer{
  private static long counter=1;
  private final long id=counter++;
  private Customer(){}
  public static Generator<Customer> generator(){
    return new Generator<Customer>(){
      public Customer next(){
        return new Customer();
      }
    };
  }
}

擦除概念的理解

在泛型机制内部,无法获得任何有关泛型参数类型的信息,这是通过擦除来实现的。意味着你可以了解到类型参数标识符和泛型类型边界这类信息,却无法知道用来创建某个特定实例时实际使用的类型参数。

Class c1=new ArrayList<Integer>().getClass();

Class c2=new ArrayList<String>().getClass();

程序会认为c1==c2,即是相同的类型,List<String>和List<Integer>都被擦除成它们的"原生"类型,即List。所以在使用泛型时,任何具体的类型信息都被擦除了,而唯一知道的就是你在使用一个对象。

边界--处理擦除的方式

给定泛型类的边界,以此告知编译器只能接受遵循这个边界的类型,这里要运用Extends关键字。

class Manipulator2<T extends Hex>{
  private T obj;
  public Manipulator2(T x){
    obj=x;
  }
  public void manipulate(){
    obj.f();
  }
}
编译器实际上会把类型参数替换为它的擦除,就像上面的示例一样,T擦除到了Hex。只有当你希望使用的类型参数比某个具体类型(及它的所有子类型)更加泛化时,也就是说,当你希望代码能够跨多个类工作时,使用泛型才有所帮助,始终应该记住这一点。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值