Effective Item 14 - 关于泛型

自Java 1.5开始使用的泛型,给人比较简单的印象是..."尖括号里写了类型我就不用检查类型也不用强转了"。

好的,那先从API的使用者的角度上想问题,泛型还有什么意义?

有句话叫:Discover errors as soon as possible after they are made, ideally at compile time.

泛型提供的正是这种能力。

比如有一个只允许加入String的集合,在没有声明类型参数的情况下,这种限制通常通过注视来保证。

接着在集合中add一个Integer实例,从集合获取元素时使用强转,结果导致ClassCastException。

这样的错误发生在运行时,compiler就爱莫能助了,而声明了类型参数的情况下则是compile-time error。

相比raw type,泛型的优势显而易见,即安全性和表述性。

有了泛型就一定比raw type强吗?

如果类型参数是Object又如何? 这种用法和raw type有什么区别?

如果仅仅通过代码描述,可以说:raw type不一定支持哪个类型,而Collection<Object>支持任何类型。

正确,但没什么意义。

泛型有种规则叫subtyping rules,比如List<String>是List的子类型,但不是List<Object>的子类型。

下面的代码描述了这种情况:

// Uses raw type (List) - fails at runtime!
public static void main(String[] args) {
    List<String> strings = new ArrayList<String>();
    unsafeAdd(strings, new Integer(42));
    String s = strings.get(0); // Compiler-generated cast
}

private static void unsafeAdd(List list, Object o) {
    list.add(o);
}

上面的情况导致运行时才能发现错误,而下面这种做法则是编译无法通过:

Test.java:5: unsafeAdd(List<Object>,Object) cannot be applied 
to (List<String>,Integer)
    unsafeAdd(strings, new Integer(42));
    ^

而为了应对这种情况Java提供了unbounded wildcard type。

即,对于不确定的类型参数使用‘?’代替。

比如Set<?>可以理解为某个类型的集合。

编码时几乎不会用到raw type,但也有两个例外,而且都和泛型擦除有关。

1. must use raw types in class literals.

2.it is illegal to use the instanceof operator on parameterized type.

既然如此,为什么Java还保留着raw type用法?

Java 1.5发布的时候Java马上要迎来第一个十年,早已存在大量的代码。

老代码和使用的新特性的代码能够互用至关重要,即 migration compatibility 。

好了,接下来说说使用泛型方面的事情。

首先是subtyping,总觉得因为这一特征,泛型有时反而显得麻烦。

书中原话是convariant和invaritant。

比如,数组是convariant的,比如Sub是Super的子类,则Sub[]是Super[]的子类。

反之,泛型是invariant的,List<Sub>不是List<Super>的子类。

鉴于这种区别,数组和泛型的难以混合使用。

比如下面这几种写法都是非法的:

new List<E>[]

new List<String>[]

new E[]

下面通过一段代码说明泛型数组为什么非法:

// Why generic array creation is illegal - won‘t compile!
List<String>[] stringLists = new List<String>[1]; // (1)

List<Integer> intList = Arrays.asList(42); // (2)

Object[] objects = stringLists; // (3)

objects[0] = intList; // (4)

String s = stringLists[0].get(0); // (5)

Effective Item 14 - 关于泛型

时间: 2024-08-03 23:13:27

Effective Item 14 - 关于泛型的相关文章

Item 14:资源管理类要特别注意拷贝行为 Effective C++笔记

Item 14: Think carefully about copying behavior in resource-managing classes. 在Item 13:使用对象来管理资源中提出了基于RAII的资源管理对象,auto_ptr和shared_ptr. 智能指针可以有不同的拷贝策略.当你实现这样一个资源管理对象时,需要特别注意.比如一个典型的RAII风格的互斥锁实现: class Lock { public: explicit Lock(Mutex *pm):mutexPtr(p

Effective Item 17 - 关于方法的参数声明

给方法的参数加上限制是很常见的,比如参数代表索引时不能为负数.对于某个关键对象引用不能为null,否则会进行一些处理,比如抛出相应的异常信息. 对于这些参数限制,方法的提供者必须在文档中注明,并且在方法开头时检查参数,并在失败时提供明确的信息,即detect errors as soon as possible after they occur,这将成为准确定位错误的一大保障. 如果没有做到这一点,最好的情况是方法在处理过程中失败并抛出了莫名其妙的异常,错误的源头变得难以定位,但这是最好的情况.

Effective Item 5 - 覆盖equals方法时需要注意

多数情况下,我们不会去覆盖equals方法. 什么时候不需要覆盖equals? ·类的每个实例本质上是唯一的,我们不需要用特殊的逻辑值来表述,Object提供的equals方法正好是正确的. ·超类已经覆盖了equals,且从超类继承过来的行为对于子类也是合适的. ·当确定该类的equals方法不会被调用时,比如类是私有的. 如果要问什么时候需要覆盖equals? 答案正好和之前的问题相反. 即,类需要一个自己特有的逻辑相等概念,而且超类提供的equals不满足自己的行为. (PS:对于枚举而言

Effective Item 3 - 避免不必要的对象

通常,我们更喜欢重用一个对象而不是重新创建一个. 如果对象是不可变的,它就始终可以被重用. 下面是一个反面例子,Joshua Bloch明确指出[DON'T TO THIS]: String s = new String("stringette"); 该语句每次执行时都创建一个新的实例. String构造器中的参数"stringette"本身是一个实例,功能方面等同于那些通过构造器创建的对象. 如果这种语句放到循环里,效果会变得更糟. 于是我们只需要: String

Effective Item 13 - 慎用tagged class

其实作者的原标题是<Prefer class hierarchies to tagged classes>,即用类层次优于tagged class. 我不知道有没有tagged class这么一说,其实作者指的tagged class的是一个类描述了多种抽象,可以根据某个field决定不同的实例. 下面是书中例子,使用shape和部分表示长度的field构成形状并计算面积,脑补一下什么是tagged class: class Figure { enum Shape { RECTANGLE, C

Effective Item 15 - 枚举

于Java 1.5增加的enum type. enum type是由一组固定的常量组成的类型,比如四个季节.扑克花色. 在出现enum type之前,通常用一组int常量表示枚举类型. 比如这样: public static final int APPLE_FUJI = 0; public static final int APPLE_PIPPIN = 1; public static final int APPLE_GRANNY_SMITH = 2; public static final i

Effective Item 9 - 尽量使可访问性最小化

模块设计是否良好,有个重要的因素在于,相对外部模块是否隐藏内部数据以及实现细节. 设计良好的模块会隐藏实现细节,并将API与其实现隔离开来. 模块之间通过API进行通信,对于内部工作情况互不可见. 即,封装(encapsulation)--软件设计的基本原则之一. 为什么要封装? 通过封装可以有效地接触各个模块之间的耦合关系,使这些模块可以独立地开发.测试.优化.使用.理解和修改. 即: ·可以增加开发效率,模块可以并行开发. ·封装可以减轻维护的负担,可以更有效的进行优化,且不会影响其他模块的

Effective Item 6 - 覆盖equals方法时不要忘记hashCode方法

任何覆盖了equals方法的类都需要覆盖hashCode方法. 忽视这一条将导致类无法与基于散列的数据结构一起正常工作,比如和HashMap.HashSet和Hashtable. 下面是hashCode相关规范: ·在程序执行期间,只要对象的equals方法的比较操作所用到的信息没有被修改,那么对这个对象调用多少次hashCode,起结果必须始终如一地返回同一个证书. 如果是同一个程序执行多次,每次调用的结果可以不一致. ·如果两个对象根据equals方法比较是相等的,那么两个对象的hashCo

Effective Item 2 - 遇到多个构造器参数时考虑使用Builder

静态工厂和够构造器有一个共同的局限性:遇到大量的参数时无法很好的扩展. 先说说构造器. 其实field不多时重叠构造器(telescoping constructor)是个不错的方法,易于编写也易于调用,这种方式在参数数量较少时也很常见. 但问题是参数很多(可能越来越多)时,比如(现在已经很难找到对多个参数进行重叠构造的代码了,于是在这里直接引用一下书中的代码): public class NutritionFacts { private final int servingSize; // (m