【读书笔记】《Effective Java》(4)--泛型

来源:互联网 发布:excel数据透视表 编辑:程序博客网 时间:2024/05/06 09:50

趁着中秋小长假,把泛型这一章的内容整理出来,这一章讲真,看着很乱,一是因为对泛型在Java上的实现了解的还不够清晰,第二个我觉的书上给的泛型的注意点分散在各个小节里,不好总览。

所以首先这一章开头先补充一些泛型的知识,然后再看估计会好理解一些。
另外,我也有一个疑惑,无限制的通配符实现不一样吗,为什么对于它总有些例外。

泛型

背景知识:

  1. 泛型的含义:泛型类似于C++中的模板(这算是以词解词吧喂)。即在定义时并不指定参数参数类型,而在使用时实例化所需要类型的特性,用于简化代码,增强代码复用,减少强制转换。

    如果觉得解释的比较模糊,我们来举一个例子:
    约定“<>”内的就是定义时的需要指定的参数,T为占位符,声明 “List” 就表示了一个存放 “T”类型的List。在实际使用时,通过指定T:“List” ,就可以实例化一个存放String类型的List了。而声明“List”不仅可以做到实例化String的,还可以其他所有引用类型,这就做到了增强代码复用的作用,对比原来的List存放Object使用时需要强制转化,泛型则不需要。

  2. Java泛型的实现:Java的泛型实现比较特别,在我们(程序员)书写代码时是区别各种“<>”内的不同类型的,但在编译后,在.class文件中,则没有这些不同类型的区别,各种泛型全部被还原成了(有类型限定的还原成被限定的类型),没有区别,这被称为“类型擦除”。Java这样做是为了兼容没有泛型特性之前的代码,但这样的泛型并不彻底(没想到你是这样的泛型),Java泛型看起来更像是语法糖。随之而来的,是Java的泛型有一些特别的现象,因为泛型只有在编译器层面才可见,那么在实际运行中JVM是如何区别几个之间有没有继承关系,方法是重写还是重载呢?因为这些问题,Java编译器在解释泛型时引入了一些新的概念,而且为了保证JVM的正常工作,编译器对泛型代码进行了更进一步的限制。

  3. 参考知识来源: Java核心技术点之泛型


23. 请不要在新代码中使用原生态(raw type)类型

  • 在1.5版本以后的代码建议使用泛型而不是对应的原生态类型,这有助于错误的发现,使用Java的特性,避免强制转换。是用原生态类型失去了泛型在安全性和表述性方面的所有优势。

  • 如果一些情况下客户代码不在乎类型参数T到底是什么的话,也建议使用泛型,如<?>,表示接受任何类型,相比于“T”限制了某一类型,“?”的范围过得多(?被成为无限制的通配符)。

  • 背景知识:类型参数“T”与“?”的不同

    1. “T”在具体使用中会被指定为一个特定的类型,而“?”始终表示某一个类,实际上,它可以接收任何“T”。
    2. 此外,在使用通配符的集合只能取元素,不能存放除null以外的任何元素(我自己理解这是因为泛型有类型推断的功能,“T”在某些省略的情况下也可以由编译器推断出来,而“?”则不可以推断,但是我没查到是不是这样,所以括号内的仅供参考)。
    3. 一般的泛型集合上不允许使用instanceof操作符,而在通配符的泛型集合上面,则是允许的,这也和“类型擦除”有关系,instanceof会在运行时检测对象的类型,而泛型在运行时已经被擦除了,所以所有的List都是一样的,set也都是一样的
    4. 创建泛型数组是不被允许的,这会引起潜在的类型转化错误,但是创建一个无限制通配符的泛型数组却是被允许的。

24. 消除非受检警告

  • 编写泛型时遇到的警告:非受检强制转化警告、非受检方法调用警告、非受检普通数组创建警告、非受检转换警告。
  • 简单粗暴的解决方法:如果觉得代码无误,类型安全,但就是有非受检警告,可以使用@SuppressWarnings("unchecked")来消除警告,并且最好注释清楚原因。

    使用@SuppressWarnings("unchecked")注解,需要要注意的是将该注解用在尽可能晓得范围内,能在变量上使用的不在方法上使用,能在方法上使用的不在类上使用。

  • 轻柔的解决方法:认真对待每一条非受检警告,一一解决它们,这会让我们编写泛型代码的能力持续上升,同时意识到非受检操作是很不安全的。


25. 列表优先于数组

  • 泛型和数组的不同点:

    1. 数组协变的,泛型是不可变的。举个例子说明就是List

26. 优先考虑泛型

  • 技巧:因为声明一个泛型数组是合法的,而new一个泛型数组是不合法的,那么当自定义一个泛型时,内部需要使用到泛型数组,如何new呢?一个方法是先new 一个Object数组,然后强制转换成对应的泛型的,编译器会警告这不是类型安全的,但我们在检查后确认无误,可以这样使用。

  • 本条建议:花些时间编写自己的泛型类是值得的。


27. 优先考虑泛型方法

  • 静态工具方法尤其适合于泛型化,其他方法也可以从泛型中受益。
  • 泛型方法的声明:需要在返回类型和方法修饰符之间声明类型参数列表

    public void myMethod(Class1 class1,...){...} //普通方法的声明

    public <E\> void myMethod<E\>(Class1<E\> class1,...){...}//泛型方法的声明

  • 为了消除变量声明等号两边都要传递类型参数的冗余,可以使用泛型静态工厂方法

    //泛型静态工厂方法 public static <K,V> HashMap<K,V> newHashMap(){    return new HashMap<K,V>();}//调用Map<String,List<String>> anagrams=newHashMap();
  • 还有泛型单例工厂(但是没看懂….)

28. 利用有限制通配符来提升API的灵活性

  • 为了应对泛型不可变的问题引入有限制的通配符,这使得方法不仅仅可以接受类型参数,还可以接受对类型参数扩展的类,和类型参数的子类。

  • PECS原则:producer-extends,consumer-super(生产者方法需要有限制的通配符? extends T,消费者方法需要有限制的通配符? surper T)。

  • 注意事项:

    1. 所有的Comparable和Comparator都是消费者

29. 优先考虑类型安全的异构容器

  • 类型安全和异构容器: 一个集合如果可以预测返回值的类型,则是类型安全的;如果这个集合的键并不是同一个类型的,则称之为异构的。当我们需要一个集合存放不同种类的类型的时候可以使用这种技巧。

  • 实现方法:通过包装一个Map,将其中的key保存为Class的,value保存为Object的,可以实现一个异构容器。

//书上的示例    public class Favorites{        private Map<Class<T>,Object> favorites=new HashMap<Class<T>,Object>();        public <T> void putFavorite(Class<T> type,T instance){            if(type==null)                throw new NullPointerException("Type is Null");            favorites.put(type,instance);        }        public <T> T getFavorite(Class<T> type){            return type.cast(favorites.get(type));        }    }

在网上查资料时发现还有很多类似的Effective Java的笔记,看别人总结的也挺好,不知道要不要继续下去了,这里链接一个别人的《Effective Java》笔记

0 0