Effective java4——泛型

数组与泛型集合的区别:

(1)数组是协变的,即如果Sub为Super的子类型,那么数组类型Sub[]就是Super[]的子类型;

泛型是不可变的,即对于任何两个不同类型Type1和Type2,List<Type1>既不是List<Type2>的子类型,也不是List<Type2>的超类型。

Object[] objectArray = new Long[1];

objectArray[0] = "I don't fit in";

上述代码在编译时没有错误,只有在运行时才会报ArrayStoreException。

List<Object> ol = new ArrayList<Long>();

ol.add("I don't fit in");

上述代码的第一句就会在编译时报类型不兼容错误。

使用泛型可以在编译时检查元素或者参数类型,对于代码调试更加方便。

(2)数组是具体化的,因此数组在运行时才知道并检查元素的类型约束;因此泛型只在编译时检查类型约束,而在运行时丢弃。

由于数组具体化与泛型类型擦除的区别,创建泛型、参数化类型或者类型参数的数组都是非法的,如:

new List<E>[];

new List<String>[];

new E();

都会在编译时报泛型数组创建错误。

像E、List<E>和List<String>等这样的类型称作是不可具体化的类型,所谓不可具体化的类型是指在运行时表示法包含的类型信息比它在编译时表示法包含的类型信息更少。

唯一可具体化的参数化类型是无限制的通配符类型,如List<?>,Map<?,?>等,创建无限制通配符类型的数组是合法的。

JDK泛型有个小局限是:不能创建具体类型的泛型,如List<int>,List<float>就会产生编译时错误,可以通过使用基本乐讯的包装类来避开这个限制。

泛型方法

使用泛型方法可以不用在代码中显式进行类型转换,还可以进行类型推导。

(1)编写一个集合并集的泛型方法:

public static <E> Set<E> union(Set<E> s1,Set<E> s2){

      Set<E> result = new HashSet<E>(s1);

      result.addAll(s2);

       return result;

}

(2)泛型类型推导:

正常声明泛型集合如下:

Map<String,List<String>>anagrams = new HashMap<String,List<String>>();

通过集合声明时的泛型参数类型就可以推导出集合实例的泛型参数类型,这个过程叫泛型类型推导,如果支持泛型类型推导,则上面代码的HashMap就可以不再指定泛型参数类型,但是目前JDK还没有内置泛型类型推导,我们可以自己进行一个小的模拟实现:

public static <K,V> HashMap<K,V> newHashMap(){

      return new HashMap<K,V>();

}

Map<String ,List<String>> anagrams = newHashMap();

泛型单例工厂

泛型单例工厂模式用于创建不可变但又适合于许多不同类型的对象,由于泛型是通过类型擦除实现的,因此可以给所有必要的类型参数使用单个对象,例子如下:

public interface UnaryFunction<T>{

    T apply(T arg);

 }

private static UnaryFunction<Object> IDENTITY_FUNCTION =

    new UnaryFunction<Object>(){

         public Object apply(Object arg){

             return arg;

         }

}

public static <T> UnaryFunction<T> identityFunction(){

     return (UnaryFunction<T>) IDENTITY_FUNCTION;

}

由于泛型是类型擦除的,在运行时对于无状态的泛型参数类型只需要一个泛型单例即可。

泛型递归类型限制

使用泛型可以通过某个包含该类型参数本身的表达式来限制类型参数,如

<T extends Comparable<T>>

读作“针对可以与自身进行比较的每个类型T”,即互比性。

下面的例子是找出列表中实现了Comparable接口的元素的最大值:

public static <T extends Comparable<T>> T max(List<T> list){

       Iterator<T> i = list.iterator();

       T result = i.next();

       while(i.hasNext()){

       T t = i.next();

        if(t.compareTo(result) >0){

            result  = t;

        }

      return result;

}

}

泛型通配符

由于泛型参数化类型是不可变的,对于任何类型的Type1和Type2而言,List<Type1>既不是List<Type2>的子类型,也不是它的超类型,由此会产生可以将任何对象放进List<Object中,却只能将字符串放在List<String>中的问题,解决此类问题我们需要使用泛型的通配符。

例子如下:

自定义堆栈的API如下:

public class Stack<E> {

    public Stack();

    public void push(E e);

    public E pop();

    public boolean isEmpty();

    public void pushAll(Iterable<E> src){

       for(E e :src){

           push(e);

       }

}

public void popAll(Collection<E> dst){

       while(!isEmpty()){

          dst.add(pop());

       }

  }

}

上述代码编译完全没有问题,但是如果想完美运行还需要使用泛型通配符。

(1)生产者限制通配符extends:

使用如下的测试数据对pushAll方法进行测试:

Stack<Number> numberStack = new Stack<Number>();

Iterable<Integer> integers = "";

numberStack.push(integers);

在运行时pushAll方法会报参数类型不匹配错误,解决这个问题可以使用限制通配符类型,将pushAll方法修改如下:

public void pushAll(Iterable<? extends E> src){

        for(E e :src){

         push(e);

   }

}

Iterable< ? extends E> 的意思是集合元素的类型是自身的子类型,即任何E的子类型,在本例子Integer是Number的子类,因此正好符合此意。

(2)消费者限制通配符super:

使用下面的测试数据对popAll方法进行测试:

Stack<Integer> intergerStack = new Stack<Integer>();

Iterable<Number> numbers = "";

integerStack.popAll(numbers);

在运行时popAll方法会报参数类型不匹配错误,解决这个问题可以使用限制通配符类型,将popAll方法修改如下:

public void popAll(Collection<? super E) dst){

         while(!isEmpty()){

             dst.add(pop());

       }

}

Collection<? super E>的意思是集合元素的参数类型是自身的超类型,即任何E的超类,在本例中可以将Integer类型的元素添加到其超类Number的集合中。

上述的两个通配符可以简记为PECS原则,即poducer-extends,consumer-super

(3)无限制通配符?:

对于同时具有生产者和消费者双重身份的对象来说,无限制通配符?更合适,一个交互集合元素的方法声明如下:

public static void swap(List<?> list,int i,list j);

一般来说,如果类型参数只在方法声明中出现一次,就可以使用通配符取代它,如果是无限制的类型参数,就使用无限制通配符?代替。

类型安全的异构容器

一般情况下,集合容器的只能有固定的类型参会,如异构Set只有一个类型参数表示它的类型,一个Map有两个类型参数表达键和值的类型,但是有些情况下我们需要更多的灵活性,即将容器的键进行参数化而不是将容器参数化,然后将参数化的键提交给容器,来插入或者获取值,用于泛型系统来确保值的类型与它的键类型相符。

在JDK1.5之后Class被泛化了,类的类型从字面上来不再只是简单的Class,而是Class<T>,例如String.class属于Class<Strng>类型,Integer.class属于Class<Integer>类型。

下面使用Favorites类来演示:

public class Favorites{

    private Map<Class<?>,Object> favorites = new HashMap<Class<?>,Obejct>();

    public <T> void putFavorites(Class<T> type,T instance){

        if(type == null){

           throw new NullPointerException("Type is null");

       }

     favorites.put(type,type.cast(instance));

  }

  public <T> T getFavorite(Class<T> type){

      return type.cast(favorites.get(type));

  }

  public static void main(String[] args){

         Favorites f = new Favorites();

         putFavorite(String.class,"java");

         putFavorite(Integer.class,oxcafebabe);

        putFavorite(Class.class,Favorite.class);

        String favoriteString = f.getFavorite(String.class);

        Int favoriteInteger = f.getFavorite(Integer.class);

        Class<?> favoritesClass = f.getFavorite(Class.class);

        System.out.println("%s %x %s%n“,favoriteString,favoriteInteger,favoritesClass);

    }

}

程序正常打印出java cafebabe Favorites。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值