[Java读书笔记] Effective Java(Third Edition) 第 5 章 泛型
第 26 条:请不要使用原生态类型
声明中具有一个或多个类型参数的类或者接口,就是泛型(generic)。
例如List接口只有单个类型参数E, 表示列表的元素类型。这个接口全称List<E>,泛型类和接口统称为泛型(generic type)。
每一种泛型都定义一个原生态类型(raw type),即不带任何实际类型参数的泛型名称。
它的存在主要是为了兼容泛型之前的代码。
如果使用原生态类型,就失去了泛型在安全性和描述性方面的优势。
如果使用像List这样的原生态类型,就会失掉类型安全性,但是如果使用像List<Object>这样的参数化类型,则不会。
如何使用泛型,但不确定或者不关心实际的类型参数,可以用一个问号代替。
例如,泛型Set<E>的无限制通配符类型为Set<?>。这是最普通的参数化Set类型,可以持有任何集合。
通配符是类型安全的,原生态类型不安全。
不要使用原生类型,有2个例外。
1. 必须在类文字中使用原生态类型。
例如:List.class, String[].class, int.class是合法,但是List<String.class>和List<?>.class是不合法。
2. 由于泛型信息可以在运行时被擦除,因此在参数化类型而非无限制通配符类型上使用instanceof操作符是非法的。
利用泛型来使用instanceof操作符的首选方法,例如:
if (o instanceof Set) {
Set<?> s = (Set<?>) o;
}
总之,使用原生态类型会在运行时导致异常。原生态类型只是为了与遗留代码兼容。
第 27 条:消除非受检的警告
用泛型编程会遇到很多编译器警告:
非受检转换警告(unchecked cast warning)、非受检方法调用警告、非受检参数化可变参数类型警告(unchecked parameterized vararg type warning)
以及非受检转换警告(unchecked conversion warning)。
例如:
Set<Lark> exaltation = new HashSet();
编译器会提醒出错地方:warning:[unchecked] unchecked conversion
Java7中开始引入菱形操作符(<>),编译器可以推断出正确的实际参数类型.
Set<Lark> exaltation = new HashSet<>();
尽可能地消除每一个非受检警告。
如果无法消除警告,同时可以证明引起警告的代码是类型安全的,才可以用一个@SuppressWarnings(“unchecked”)注解来禁止这条警告。
SuppressWarnings可以用在任何颗粒度,应该尽量小的范围使用SuppressWarnings注解。
每当使用SuppressWarnings(“unchecked”)注解时,都要添加一条注解,说明为什么这么做是安全的。
总之,每一条警告都表示可能在运行时抛出ClassCastException,要尽量消除这些警告。
第 28 条:列表优于数组
数组与泛型相比有两个重要不同点。
第一,数组是协变的(covariant),泛型是可变的(invariant)。
表示如果Sub为Super的子类型,那么数组Sub[]就是Super[]的子类型。
对于泛型,不同类型Type1和Type2,List<Type1>既不是List<Type2>的子类型,也不是超类型。
例如,不能将String放入Long容器中,但是数组只会在运行时发现,用列表,则可以在编译时发现错误。
第二,数组是具体化的,泛型是通过擦除来实现的。
数组在运行时知道和强化他们的元素类型,如果将String放入Long数组中,会得到ArrayStoreException.
泛型在编译时强化他们的类型信息,在运行时丢弃(擦除)他们的元素信息。擦除使泛型可以与没有泛型的代码互用。
创建泛型、参数化类型或者类型参数的数组是非法的。
例如:new List<E>[]、new List<String>[]和new E[]。
创建泛型数组不是类型安全的。
优先使用集合类型List<E>,而不是数组类型E[],可能会损失一些性能,但换回是更高的类型安全性和互动性。
总之,数组和泛型有完全不同的类型规则。数组是协变且可以具体化的,泛型是不可变且可以被擦除的。
因此,数组提供了运行时的类型安全,但没有编译时的类型安全,反之,泛型也一样。数组和泛型不能很好地混合使用,优先用列表代替数组。
第 29 条: 优先考虑泛型
第28条鼓励优先使用列表而非数组。实际上不可能总是在泛型中使用列表。
Java并不是生来就支持列表,因此有些泛型如ArrayList必须在数组上实现。为了提升性能,其他泛型如HashMap也在数组上实现。
总之,使用泛型比使用需要在客户端代码中进行转换的类型来得更加安全,也更容易。
第 30 条: 优先考虑泛型方法
静态工具方法尤其适合于泛型化。Collections中所有“算法”方法(例如binarySearch和sort)都泛型化了。
public static Set union(Set s1, Set s2) {
Set result = new HashSet(s1);
result.addAll(s2);
return result;
}
这个方法可以编译,但有2条警告。
为了修正警告,使方法变成类型安全的,将方法声明修改为一个类型参数(type parameter),表示这三个集合元素类型(两个参数和一个返回值),并在方法中使用类型参数。
public static <E> Set<E> union(Set<E> s1, Set<E> s2) {
Set<E> result = new HashSet<>(s1);
result.addAll(s2);
return result;
}
声明类型参数的类型参数列表,处在方法的修饰符及其返回值之间。
总之,泛型方法就像泛型一样,使用起来比要求客户端转换输入参数并且返回值的方法更安全,也更容易。
就像类型一样,应该确保方法不用转换就能使用,还应该将现有方法泛型化,使新用户使用起来更轻松,且不会破坏现有的客户端。
第31 条:利用有限制通配符来提升API的灵活性
Java提供了一种特殊的参数化类型,称为有限制的通知符类型(bounded wildcard type)。
例如,E的某个子类型的Iterable接口,通配符类型Iterable<? extends E>。
public void pushAll(Iterable<? extends E> src) {
for (E e : src)
push(e);
}
另一种,E的某种超类的集合,通用符Collection<? super E>。
public void popAll(Collection<? super E> dst) {
while(!isEmpty())
dst.add(pop());
}
为了获取最大限度的灵活性,要在表示生产者或消费者的输入参数上使用通配符类型。
PESC表示producer – extends, consumer – super.
如果参数化类型表示一个生产者T,就用<? extends T>; 如果它表示一个消费者T,就用<? super T>。
例如第30条的union方法:
public static <E> Set<E> union(Set<E> s1, Set<E> s2)
用PESC原则:
public static <E> Set<E> union(Set<? extends E> s1, Set<? extends E> s2)
注意:返回类型仍然是Set<E>,不要用通配符类型作为返回类型。
修改声明后:
Set<Integer> ints = Set.of(1, 3, 5);
Set<Double> dob = Set.of(2.0, 4.0, 6.0);
Set<Number> num = union(ints, dob);
第30条的max方法,初始声明:
public static <T extends Comparable<T>> T max(List<T> list)
修改使用通配符后:
public static <T extends Comparable<? super T>> T max(List<? extends T> list)
这是将通配符运用到类型参数。参数化类型Comparable<T>被有限制通配符类型Comparable<? super T>取代。 comparable是消费者,因此使用时始终应该是Comparable<? super T>优先于Comparable<T>。对于comparator接口也一样,使用时始终应该是Comparator<? super T>优先于Comparator<T>。
还有一点,类型参数和通配符之间具有双重性,许多方法都可以利用其中一个或者另一个进行声明。如下:
public static <E> void swap(List<E> list, int i, int j); // 使用无限制的类型参数
public static void swap(List<?> list, int i, int j); // 使用无限制的通配符
在公共API中,第二种更好些,因为它更简洁。
如果类型参数只在一个方法声明中出现一次,就可以使用通配符取代它。
总之,在API中使用通配符需要技巧,会是API变得很灵活。如果编写是将被广泛使用的类库,则一定要适当地利用通配符类型。基本原则:producer – extends, consumer – super.(PECS)。还有所有的comparable和comparator都是消费者。
第 32 条:谨慎并用泛型和可变参数
可变参数(vararg)方法和泛型都是Java5出现的,但他们不能良好地相互作用。
可变参数作用在于让客户端能够将可变数量的参数传给方法,当调用一个可变参数方法时,会创建一个数组用来存放可变参数。这个数组是一个实现细节,可见的。因此,当可变参数有泛型或者参数化类型时,编译警告信息就会产生。
当一个参数化类型的变量指向一个不是该类型对象时,会产生堆污染(heap pollution)。它导致编译器的自动生成转换失败,破坏了泛型系统的基本保证。
例如,第28条代码改编:
static void deangerous(List<String>… stringLists){
List<Integer> intList = List.of(42);
Object[] objects = stringLists;
object[0] = intList; // heap pollution
String s = stringLists[0].get(0); // ClassCastException
}
上述最后一行代码中有一个不可见转换,由编译器生成。这个转换失败证明类型不安全。
因此将值保存在泛型可变参数数组参数中是不安全的。
在Java7中,增加了SafeVarargs注解,它让带泛型vararg参数的方法的设计者能够自动禁止客户端的警告。通过方法的设计者承诺声明这是类型安全的。
确定何时使用SafeVarargs注解的规则:对于每一个带有泛型可变参数或者参数化类型的方法,都要用@SafeVarargs进行注解。
安全使用泛型可变参数的例子:
@SafeVarargs
static <T> List<T> flatten(List<? extends T>… lists) {
List<T> result = new ArrayList<>();
for (List<? extends T> list : lists)
result.addAll(list);
return result;
}
如不想使用@SafeVarargs,也可用一个List参数代替可变参数:
static <T> List<T> flatten(List<List<? extends T>> lists) {
List<T> result = new ArrayList<>();
for (List<? extends T> list : lists)
result.addAll(list);
return result;
}
总之,可变参数和泛型不能良好地合作。如果选择编写带泛型(或参数化)可变参数的方法,首先要确保该方法时类型安全的,然后用@SafeVarargs对它进行注解。
第 33 条:优先考虑类型安全的异构容器
泛型最常用于集合,如Set<E>和Map<K,V>,以及单个元素的容器,如ThreadLocal<T>和AtomicReference<T>。限制每个容器只能有固定数目的类型参数。
有时候需要更多灵活性,将键(key)进行参数化而不是将容器(container)参数化,然后将参数化的键提交给容器来插入或者获取值。用泛型来确保值得类型与它的键相符。
例如:
public class Favorites {
private Map<Class<?>, Object> favorites = new HashMap<>();
public <T> void putFavorites(Class<T> type, T instance) {
favorites.put(Objects.requireNonNull(type), instance);
} public <T> T getFavorites(Class<T> type) {
return type.cast(favorites.get(type));
}
}
每个Favorites实例得到一个favorites的私有Map<Class<?>, Object>的支持。每个键都可以有一个不同的参数化类型:一个是Class<String>, 接下来是Class<Integer>,异构就是从这里来的。
getFavorites的cast方法时Java转换操作的动态模拟。它只检验它的参数是否为Class对象所表示的类型的实例。如果是,就返回参数,否则抛出ClassCastException异常。
总之,集合API说明了泛型的一般用法,限制每个容器只有固定数目的类型参数。你可以通过将类型参数放在键上而不是容器上来避开这一限制。对于类型安全的异构容器,可以用Class对象作为键。
[Java读书笔记] Effective Java(Third Edition) 第 5 章 泛型的更多相关文章
- [Java读书笔记] Effective Java(Third Edition) 第 7 章 Lambda和Stream
在Java 8中,添加了函数式接口(functional interface),Lambda表达式和方法引用(method reference),使得创建函数对象(function object)变得 ...
- [Java读书笔记] Effective Java(Third Edition) 第2章 创建和销毁对象
第 1 条:用静态工厂方法代替构造器 对于类而言,获取一个实例的方法,传统是提供一个共有的构造器. 类可以提供一个公有静态工厂方法(static factory method), 它只是一个返回类 ...
- [Java读书笔记] Effective Java(Third Edition) 第 6 章 枚举和注解
Java支持两种引用类型的特殊用途的系列:一种称为枚举类型(enum type)的类和一种称为注解类型(annotation type)的接口. 第34条:用enum代替int常量 枚举是其合法值由一 ...
- [Java读书笔记] Effective Java(Third Edition) 第 4 章 类和接口
第 15 条: 使类和成员的可访问性最小化 软件设计基本原则:信息隐藏和封装. 信息隐藏可以有效解耦,使组件可以独立地开发.测试.优化.使用和修改. 经验法则:尽可能地使每个类或者成员不被外界访问 ...
- [Java读书笔记] Effective Java(Third Edition) 第 3 章 对于所有对象都通用的方法
第 10 条:覆盖equals时请遵守通用约定 在不覆盖equals方法下,类的每个实例都只与它自身相等. 类的每个实例本质上都是唯一的. 类不需要提供一个”逻辑相等(logical equality ...
- 【读书笔记 - Effective Java】05. 避免创建不必要的对象
1. 如果对象是不可变的(immutable),它就始终可以被重用. (1) 特别是String类型的对象. String str1 = new String("str"); // ...
- 【java读书笔记】——java开篇宏观把控 + HelloWorld
学完java有一段时间了,一直没有做对应的总结,总认为有一种缺憾.从这篇博客開始,将自己平时的学习笔记进行总结归纳,分享给大家. 这篇博客主要简单的介绍一下java的基础知识,基本的目的是扫盲.原来仅 ...
- 【读书笔记 - Effective Java】02. 遇到多个构造器参数时要考虑用构建器
类有多个可选参数的解决方案: 1. 重叠构造器模式可行,但是当有许多参数的时候,客户端代码会很难编写,并且仍然较难以阅读. 2. JavaBeans模式,调用一个无参构造器来创造对象,然后调用sett ...
- 【读书笔记 - Effective Java】04. 通过私有构造器强化不可实例化的能力
工具类(utility class)不希望被实例化,比如只包含静态方法和静态域的类.为了这个目的,需要让这个类包含一个私有构造器. // 私有构造器示例 public class UtilityCla ...
随机推荐
- IntelliJ IDEA控制台启动Tomcat输出中文乱码问题
IntelliJ IDEA控制台输出中文乱码问题 1. 先解决Tomcat中文乱码问题 参考 Tomcat 输出日志出现中文乱码 2. 解决Idea配置问题 打开IntelliJ IDEA本地安装目 ...
- 【Zookeeper】应用场景概述
一.数据发布与订阅(配置中心) 二.负载均衡 三.命名服务(Naming Service) 四.分布式通知/协调 五.集群管理与Master选举 六.分布式锁 七.分布式事务 一.数据发布与订阅(配置 ...
- Maven 三种archetype说明--转载
版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 原文链接:https://blog.csdn.net/cx1110162/article/deta ...
- 【nodejs代理服务器一】nodejs http-proxy 开发反向代理服务器,防火墙,过滤常见的web渗透
事出有因 最近web系统引来了黑客的攻击,经常被扫描,各种漏洞尝试. 分析攻击日志,有几种常见的攻击手段: 上传webshell 远程执行命令漏洞 sql注入 xxs 攻击 试探各种开源框架爆出来的漏 ...
- C# Winfrom DataGridView常用设置
DataGridView常用设置 using System; using System.Collections.Generic; using System.Drawing; using System. ...
- Linux学习笔记(九)shell基础:echo、命令别名和常用快捷键
一.echo在屏幕上打印内容 echo [选项] [输出内容] -e 支持转义字符控制的字符转换 输出带颜色的文本 二.第一个脚本 编写脚本 注意: #!/bin/bash 此行不是注释,必须有 #! ...
- Linux学习之七-配置Telnet连接Linux服务器
配置Telnet连接Linux服务器 通过telnet可以从windows平台访问linux 服务器 ,实现和ssh 客户端一样的效果,区别在于通过ssh连接更安全. 检查Linux系统中是否安装了t ...
- python学习之基础入门,安装,字符串,数据转换,三元运算符
python基础 我们要开始学习新的编程语言了,加油~~ python是“世界上最好的语言”,学习它当然是认为它是最好的所以我们才学(人生苦短我学python),python运用于不同的领域,采集分析 ...
- mybatis-oracle 新增序列
1.参考 https://blog.csdn.net/qq_29001173/article/details/82106853 2.思考: 2.1获取序列下一个值:seq_car.nextval 2. ...
- Dinic 与 SAP(ISAP?) 模板
发一个最大流模板 DinicDinicDinic //vis为int类型 //sz为总点数 namespace Dinic { inline bool bfs() { int head = 0, ta ...