Effective Java读书笔记七(Java Tips.Day.7)

TIP27 优先考虑泛型方法


实现一个合并两个Set容器的方法

  1. 无泛型参数的方法

    ```
    public static Set union(Set s1, Set s2){
        Set result = new HashSet(s1);
        result.addAll(s2);
        return result;
    }
    ```
    

    这个方法可以编译,但会有两个uncheck警告。

  2. 参考TIP.24,我们将方法改为如下实现:

     ```
        public static <E> Set<E> union(Set<E> s1, Set<E> s2){
            Set<E> result = new HashSet<E>(s1);
            result.addAll(s2);
            return result;
        }
        ```
    

    现在不会有uncheck警告了,但仍有局限: 要求两个参数和返回值的三个集合类完全相同。


泛型方法的特性:无需明确指定类型参数的值

  1. 类型推导(type inference): 如果编译器发现union的两个参数都是Set<String>类型,那么就会知道类型参数E必须为String。这个过程称为类型推导。

  2. 泛型静态工厂方法:
    考虑创建一个Map:

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

    类型参数出现在了变量声明的左右两边,显得有些冗余,因此考虑编写一个泛型静态工厂方法:

    public static <K, V> HashMap(K, V) newHashMap(){
        return new HashMap<K, V);
    }

    然后用下面这段简洁的代码取代上面那个重复的声明:

    Map<String,List<String>> anagrams = newHashMap();
  3. 还有个更复杂的应用场景,我们以后会另外展开说明。


TIP28 利用有限制通配符来提升api的灵活性


对于任何两个截然不同的类型Type1和Type2而言,List<Type1> 既不是 List<Type2> 的子类型,也不是它的父类型。请参考TIP25,参数化类型是不可变的。

也许这违背直觉,但很有意义。你可以将任何对象放入一个 List<Object> 中,但只能将一个String对象放入 List<String> 中。

为了解决与此相关的问题,JAVA提供了一种特殊的参数化类型:称作有限制的通配符来处理类似的情况。

常用的两种: List<? extends E>List<? super E>

考虑以下的Stack类:

public class Stack<E>{
    public Stack();
    public void push(E e);
    public E pop();
    public boolean isEmpty();
}

考虑增加一个方法pushAll,让它按顺序将一系列的元素全部放到堆栈中,第一次尝试如下:

public void pushAll(Iterable<E> src){
    for (E e : src){
        push(e);
    }
 }

这个方法编译正确,但并不尽如人意。如果参数的元素类型与堆栈的元素类型完全匹配,就不会有问题。
但假如企图这样使用, 就会发生错误:

//haha,参数化类型是不可变的。
Stack<Number> numberStack = new Stack<Number>();
Iterable<Integer> integers = ...;
numberStack.pushAll(integers);

很明显,pushAll的输入参数类型不应该为”E的Iterable接口”,而应该为”E的某个子类型的Iterable接口”;所以,使用 <? extends E> 可以解决这个问题。看看修改后的pushAll:

public void pushAll(Iterable<? extends E> src){
    for (E e : src){
        push(e);
    }
 }

考虑增加一个方法popAll,与pushAll相对,第一次尝试如下:

public void popAll(Collection<E> dst){
    while(!isEmpty()){
        dst.add(pop());
   }
} 

如果目标集合的元素类型与堆栈元素类型完全匹配,这段代码正确无误,运行的很好。但如果有一个Stack<Number> 和 类型Object的集合变量,以下为调用过程:

//显然也无法通过编译
Stack<Number> numberStack = new Stack<Number>();
Collection<Object> objects = ...;
numberStack.popAll(objects );

popAll的参数类型不应该为”E的集合”,而应该是”E的超类的集合”,看看修改后的popAll:

public void popAll(Collection<? super E> dst){
    while(!isEmpty()){
        dst.add(pop());
   }
} 

结论很明显,为了获得最大的灵活性,要在表示生产者或消费者的输入参数上使用通配符类型。如果某个输入参数既是消费者又是生产者,那么就无需使用通配符类型,因为此时必须要求严格的类型匹配。

PECS producer-extends, consumer-super

  • 如果参数化类型表示一个T的生产者,就使用 <? extends T> 。例如pushAll方法的参数,就是提供T对象的生产者。
  • 如果参数化类型表示一个T的消费者,就使用 <? super T> 。例如popAll方法的参数,就是需要使用T对象的消费者。

天啊,这个问题还有很多值得研究的地方,今天先讲到这儿吧…

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值