Java 1.5发行版本中增加了泛型,在没有泛型之前,从集合中读取到的每一个对象都必须进行类型转换,如果有人不小心插入了类型错误的对象,在运行时的转换处理就会出错。
而有了泛型之后呢,可以告诉编译器每个集合中都接受那些对象类型,编译器自动地为你的插入进行转化,并在编译时告知是否插入了类型错误的对象,这样尽早的暴露错误,保证后续的运行安全,且在编译的时候明确了集合中保存对象的类型,在取出的时候就不用进行类型转换了。
声明中具有一个或多个类型参数的类或者接口就是泛型类或者接口,两者统称为泛型。
每种泛型定义一组参数化类型,格式为,先是类或接口的名称,接着用尖括号(<>)把对应于泛型形式类型参数的实际类型参数列表括起来。
例如,List<E>其中List为本泛型类的原生态类型,E为类型化参数,通常用E,你也可以用其它字母或单词。
每个泛型都定义一个原生态类型,即不带任何实际类型参数的泛型名称,如List为List<E>原生态类型。
泛型读法,List<String>为用来保存String类型对象的列表。
请不要在新代码中使用原生态类型【Item 23】
1)泛型的优势
a. 集合中错误的类型对象插入会产生一条编译时的错误消息,准确告诉你错在哪儿,这遵守了出错之后应该尽快发现,最好是在编译时发现
b.从集合中删除元素时不再需要进行手工转换了
2)由于要保持兼容性,JDK要继续保持对原生态类型的支持,而在有了泛型之后我们在编写的code就应该使用泛型,继续使用原生态类型会失掉泛型在安全性和表述性方面的所有优势
3)泛型有子类化的规则,List<String>是原生态类型List的一个子类型,而不是参数化类型List<Object>的子类型,因此使用像List这样的原生态类型,就会失掉类型的安全性,但是如果使用像List<Object>这样的参数化类型,则是类型安全的,因为在本质上参数化类型是两种不相关的类型,仅仅是看起来像有父子关系
4)如果要使用泛型,但不确定或者不关心实际的类型参数,就可以使用一个问号代替,例如,Set<E>的无限制通配符类型为Set<?>(读作某个类型的集合)
5)无限制通配符类型与原生态类型的区别在于无限制通配符类型是安全的,原生态类型则不安全,原生态类型可以将任何类型的对象加入集合中,这样很容易破坏该集合的类型约束条件,而不能将任何元素(除了null之外)放到Collection<?>中,如果这么错,会有编译时错误。因为无限制通配符是允许某一种位置类型对象放入,不是任何类型都可以,从而做到类型安全
6)泛型使用的注意点
a.泛型信息可以在运行时被擦除,也就是说在类文字中必须使用原生态类型,也就是说List.class,String[].class和int.class都是合法的,但是List<String.class>和List<?>.class则不合法,也就说在编译时泛型类或接口是一个类型,但是在运行时被擦除了,各种泛型类型都是其原生态类型
b.因为泛型信息在运行时会被擦除,因此在参数化类型而非无限制通配符类型上使用instanceof操作符是非法的
7)Set<Object>是个参数化类型,表示可以包含任何对象类型的一个集合;Set<”>则是一个通配符类型,表示只能包含某种未知对象类型的一个集合;Set则是一个原生态类型。前两者是类型安全的,最后一个则不安全
8)泛型相关术语术语 示例 参数化的类型 List<String> 实际类型参数 String 泛型 List<E> 形式类型参数 E 无限制通配符类型 List<?> 原生态类型 List 有限制类型参数 Lsit<E extends Number> 递归类型限制 <T extends Comparable<T>> 有限制通配符类型 List<? extends Number> 泛型方法 static <E> List<E> asList(E[] a) 类型令牌 String.class 消除非受检警告【Item 24】
1)要尽可能地消除每一个非受检警告,消除了所有警告,就可以确保代码是类型安全的,避免运行时ClassCastException异常
2)如果无法消除警告,同时可以证明引起警告的代码是类型安全的,可以用一个@SuppressWarnings(“unchecked”)注解来禁止这条警告
3)应该始终在尽可能小的范围中使用SupressWarnings注解,它通常是个变量声明,或是非常简短的方法或构造器,永远不要在整个类上使用SuppressWarnings,这样做可能会掩盖严重的警告
4)每当使用SuppressWarnings(“unchecked”)注解时,都要添加一条注释,说明为什么这么做是安全的,这样有助于其他人的理解,并且尽量减少其他人修改代码后导致不安全的概率
5)每一个unchecked警告都预示着可能在运行时出现ClassCastException异常,所以要重视- 列表优先于数组【Item 25】
1)数组是协变的,泛型是不可变的,数组协变的意思是如果Sub是Super的子类,那么数组类型Sub[]也是Super[]的子类,而对于泛型来说,对于任意两个不同的类型Type1和Type2,即使有父子类的关系,List<Type1>和List<Type2>不会存在父子关系
2)数组是具体化,因此数组会在运行时才知道并检查它们的元素类型约束,而泛型是通过擦除来实现的,因此泛型只在编译时强化它们的类型信息,并在运行时丢弃(或者擦除)它们的元素类型信息
3)数组和泛型不能很好的混合使用
4)不可具体化的类型是指其运行时表示法包含的信息比它的编译时表示法包含的信息更少的类型。唯一可具体化的参数化类型是无限制的通配符类型,只有无限制通配符类型的数组是合法的
5)当你得到泛型数组创建错误时,最好的解决办法通常是优先使用集合类型List&ly;E>,而不是数组类型E[]
6)List的toArray方法会在内部锁定列表
7)元素类型信息会在运行时从泛型中擦除 - 优先考虑泛型【Item 26】
1)泛型的参数化类型不能用基本类型,但是可以通过基本类型的包装类型来代替就可以
2)使用泛型比使用需要在客户端代码中进行转换的类型来得更加安全,也更加容易,泛型通过类型参数保证了类型安全 - 优先考虑泛型方法【Item 27】
1)静态工具方法尤其适合泛型化
2)泛型方法无需明确指定类型参数的值,不像调用泛型构造器的时候是必须指定的,编译器通过检查方法参数的类型来计算类型参数的值,这种方式叫做类型推导,也就是说如方法中用到Set<E>在方法参数中,当调用方法时传入的Set<String>实参,编译器就能推导出形式类型参数E的值为String
3)泛型方法的优点
a.使用起来比要求客户端转换输入参数并返回值的方法来得更加安全,也更加容易
b.要确保新方法可以不用转换就能使用,这就意味着要将它们泛型化
4)应该将现有的方法泛型化,使新用户用起来更加的轻松,并且不会破坏现有的客户端 - 利用有限制通配符来提升API的灵活性【Item 28】
1)参数化类型是不可变的,也就是说<E>,E被指定是什么具体类型就是什么类型,不能是其它类型
2)而有限制通配符类型增加了泛型的灵活性,要在表示生产者或者消费者的输入参数上是用通配符类型,如果输入参数既是消费者又是生产者,那么就不适合用通配符类型
3)有限制通配符类型有两种<? extends T>,<? super E>,在使用时遵循如下规则:
PECS表示producer-extends,consumer-super
4)不要用通配符类型作为返回类型,如果使用了,那么它会强制在客户端代码中使用通配符类型,如果类的用户必须考虑通配符类型,类的API获取就会出错
5)如果通配符类型使用得当,其对于用户应该是无形的,它们使方法能够接受它们该接受的参数,拒绝它们应该拒绝的参数
6)类型参数和通配符之间具有双重性,一般来说,如果类型参数只在方法声明中出现一次,就可以用通配符取代它,如果是无限制的类型参数,就用无限制的通配符取代它,如果是有限制的类型参数,就用有限制的通配符取代它
7)可以通过编写一个私有的辅助方法来捕捉通配符类型,为了捕捉通配符类型,辅助方法必须是泛型方法
8)如果编写的是将被广泛使用的类库,则一定要适当的利用通配符类型,遵循PECS的原则
9)记住所有的Comparable和Comparator都是消费者 - 优先考虑类型安全的异构容器【Item 29】
1)泛型最常用于集合,如Set和Map,以及单元素的容器,如ThreadLocal和AtomicReference
2)类Class在jdk 1.5版本中也被泛型化,类的类型从字面上来看不再只是简单的Class,而是Class<T>,例如String.class属于Class<String>类型,Integer.class属于Class<Integer>类型
3)类型安全的异构容器使用了Class的泛型设计