Effective Java 读书笔记(四):泛型
1 不要使用原始类型
(1)术语
| 术语 | 例子 |
|---|---|
| 参数化类型(Parameterized type) | List<String> |
| 实际类型参数(Actual type parameter) | String |
| 泛型类型(Generic type) | List<E> |
| 形式类型参数(Formal type parameter) | E |
| 无限制通配符类型(Unbounded wildcard type) | List<?> |
| 原始类型(Raw type) | List |
| 有限制类型参数(Bounded type parameter) | <E extends Number> |
| 递归类型限制(Recursive type bound) | <T extends Comparable<T>> |
| 有限制通配符类型(Bounded wildcard type) | List<? extends Number> |
| 泛型方法(Generic method) | static <E> List< E > asList(E[] a) |
| 类型令牌(Type token) | String.class |
(2)为什么不使用原始类型?
- 原始类型
// 按照这么写并不会报错(List内部由一个Object数组维护),但是使用上很容易出错。
List list = new ArrayList();
list.add("Hello");
list.add(100);
- 原始类型失去了安全性(强转异常)
- 原始类型失去了可读性(Object)
2 消除未检查警告
(1)概述
- 消除未检查警告可以减少ClassCastException的发生。
- 当警告无法消除且代码没有问题的情况下,使用@SuppressWarnings("unchecked")注解来禁止这个警告。
- 消除警告尽可能具体。
(2)示例
public <T> T[] toArray(T[] a) {
if (a.length < size) {
@SuppressWarnings("unchecked")
T[] result = (T[]) Arrays.copyOf(elements, size, a.getClass());
return result;
}
System.arraycopy(elements, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
3 列表优于数组
(1)数组是协变的,泛型是收约束的。
- 如果Sub是Super的一个子类型,那么数组类型Sub[]也是数组类型Super[]的子类型。
// 运行时报错
Object[] objectArray = new Long[1];
objectArray[0] = "I don't fit in"; // Throws ArrayStoreException
// 无法编译通过
List<Object> ol = new ArrayList<Long>(); // Incompatible types
ol.add("I don't fit in");
(2)数组是具化的,泛型是可擦除的。
数组在运行时才知道并检查元素类型。
E,List<E>,和List<String>这些类型在技术上都被称为不可具化类型,即运行时展示信息比编译时展示信息要少的类型。
(3)数组提供了运行时类型安全性,不保证编译时安全性,泛型则反过来。
4 优先考虑泛型
public class Stack<E> {
private E[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
// The elements array will contain only E instances from push(E).
// This is sufficient to ensure type safety, but the runtime
// type of the array won't be E[]; it will always be Object[]!
@SuppressWarnings("unchecked")
public Stack() {
elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];
}
public void push(E e) {
ensureCapacity();
elements[size++] = e;
}
public E pop() {
if (size == 0) throw new EmptyStackException();
E result = elements[--size];
elements[size] = null; // Eliminate obsolete reference return result;
}
// no changes in isEmpty or ensureCapacity
}
5 优先使用泛型方法
(1)泛型方法
- 原始类型方法
public static Set union(Set s1, Set s2) {
Set result = new HashSet(s1);
result.addAll(s2);
return result;
}
- 泛型方法
public static <E> Set<E> union(Set<E> s1, Set<E> s2) {
Set<E> result = new HashSet<>(s1);
result.addAll(s2);
return result;
}
(2)恒等函数分发器
- 每次创建新的恒等函数对象
public static <T> UnaryOperator<T> identityFunction() {
return (t) -> t;
}
- 缓存一个泛型单例,节约内存且足够应付所有的情况
private static UnaryOperator<Object> IDENTITY_FN = (t) -> t;
@SuppressWarnings("unchecked")
public static <T> UnaryOperator<T> identityFunction() {
return (UnaryOperator<T>) IDENTITY_FN;
}
(3)递归类型限制
public static <E extends Comparable<E>> E max(Collection<E> c) {
if (c.isEmpty()) throw new IllegalArgumentException("Empty collection");
E result = null;
for (E e : c){
if (result == null || e.compareTo(result) > 0){
result = Objects.requireNonNull(e);
}
}
return result;
}
注:当列表是空的时候,这个方法会抛出IllegalArgumentException异常。一种更好的办法是返回一个Optional<E>。
6 使用有限制通配符来增加API的灵活性
(1)demo1
public class Stack<E> {
public Stack();
public void push(E e);
public E pop();
public boolean isEmpty();
// 生产者
public void pushAll(Iterable<? extends E> src) {
for (E e : src)
push(e);
}
// 消费者
public void popAll(Collection<? super E> dst) {
while (!isEmpty())
dst.add(pop());
}
}
// 使用pushAll
Stack<Number> numberStack = new Stack<>();
Iterable<Integer> integers = ... ;
numberStack.pushAll(integers);
// 使用popAll
Stack<Number> numberStack = new Stack<Number>();
Collection<Object> objects = ... ;
numberStack.popAll(objects);
- ? extends E修饰的集合:存放E或E的子类
- ? super E修饰的集合:存放E或E的父类
(2)demo2
// 修改前
public static <E> Set<E> union(Set<E> s1, Set<E> s2);
// 修改后
public static <E> Set<E> union(Set<? extends E> s1, Set<? extends E> s2);
// 使用
Set<Integer> integers = Set.of(1, 3, 5);
Set<Double> doubles = Set.of(2.0, 4.0, 6.0);
Set<Number> numbers = union(integers, doubles);
- 泛型作为返回值时,E代表返回E或E的子类。
具体参考:泛型总结
(3)demo3
// 修改前
public static <T extends Comparable<T>> T max(List<T> list);
// 修改后
public static <T extends Comparable<? super T>> T max(List<? extends T> list);
// 使用
// ScheduledFuture不直接实现Comparable,但是它的父类接口实现了Comparable,所以为了支持这种情况需要修改为<T extends Comparable<? super T>>。
List<ScheduledFuture<?>> scheduledFutures = ... ;
max(scheduledFutures);
- Comparable接口通常都是消费者,所以在一般情况下,你应该优先用Comparable<? super T>,而不是Comparable<T>。
- 对于Comparator接口也应该如此,也就是应该优先使用Comparator<? super T>,而不是Comparator<T>。
(4)demo4
// 无界类型参数
public static <E> void swap(List<E> list, int i, int j);
// 无界通配符:该方式优于上一个方式,但是由于无界通配符类型无法修改,即需要借助helper进行修改,但这对于调用者无需关心。
public static void swap(List<?> list, int i, int j) {
swapHelper(list, i, j);
}
private static <E> void swapHelper(List<E> list, int i, int j) {
list.set(i, list.set(j, list.get(i)));
}
7 考虑类型安全的异构容器
(1)异构容器
public class Favorites {
private Map<Class<?>, Object> favorites = new HashMap<>();
public <T> void putFavorite(Class<T> type, T instance) {
favorites.put(Objects.requireNonNull(type), instance);
}
// 确保类型安全,不安全将抛出异常
public <T> void putFavorite(Class<T> type, T instance) {
favorites.put(type, type.cast(instance));
}
public <T> T getFavorite(Class<T> type) {
return type.cast(favorites.get(type));
}
}
// 使用
public static void main(String[] args) {
Favorites f = new Favorites();
f.putFavorite(String.class, "Java");
f.putFavorite(Integer.class, 0xcafebabe);
f.putFavorite(Class.class, Favorites.class);
String favoriteString = f.getFavorite(String.class);
int favoriteInteger = f.getFavorite(Integer.class);
Class<?> favoriteClass = f.getFavorite(Class.class);
System.out.printf("%s %x %s%n", favoriteString,
favoriteInteger, favoriteClass.getName());
}
(2)限定类型的令牌
// 表示类,方法,属性和其他程序元素的反射类型实现
public interface AnnotatedElement {
<T extends Annotation> T getAnnotation(Class<T> annotationType);
}
// 如果一个Class<?>的对象希望传递给接收Class<T>的泛型方法,可以将对象转换为Class<? extends Annotation>,但是会有编译时警告,所以需要借助asSubclass转换所调用的Class对象来表示由其参数表示的类的子类。
static Annotation getAnnotation(AnnotatedElement element,String annotationTypeName) {
Class<?> annotationType = null; // Unbounded type token
try {
annotationType = Class.forName(annotationTypeName);
} catch (Exception ex) {
throw new IllegalArgumentException(ex);
}
return element.getAnnotation(annotationType.asSubclass(Annotation.class));
}
Effective Java 读书笔记(四):泛型的更多相关文章
- Effective Java 读书笔记之四 泛型
泛型的本质是参数化类型.只对编译器有效. 一.请不要在新代码中使用原生态类型 1.泛型类和接口统称为泛型,有一个对应的原生态类型. 2.原生类型的存在是为了移植兼容性. 3.无限制通配类型和原生态类型 ...
- Effective java读书笔记
2015年进步很小,看的书也不是很多,感觉自己都要废了,2016是沉淀的一年,在这一年中要不断学习.看书,努力提升自己 计在16年要看12本书,主要涉及java基础.Spring研究.java并发.J ...
- Effective Java读书笔记完结啦
Effective Java是一本经典的书, 很实用的Java进阶读物, 提供了各个方面的best practices. 最近终于做完了Effective Java的读书笔记, 发布出来与大家共享. ...
- Effective Java 读书笔记(一):使用静态工厂方法代替构造器
这是Effective Java第2章提出的第一条建议: 考虑用静态工厂方法代替构造器 此处的静态工厂方法并不是设计模式,主要指static修饰的静态方法,关于static的说明可以参考之前的博文&l ...
- [Effective Java 读书笔记] 第三章类和接口 第十三 -- 十四条
第十三条 使类和成员的可访问性最小化 总得来说,我们应该尽量将成员的访问范围限制到最小!有利于解耦,开发.测试和优化都能够更加独立. 对于成员(域,方法,嵌套类和嵌套接口),有四种可能的访问级别,访问 ...
- [Effective Java 读书笔记] 第二章 创建和销毁对象 第三 四条
第三条 用私有构造器或者枚举类型强化singleton属性 singleton指只能被实例化一次的类,即将构造器设置为私有,使用公有静态成员来实例化,且只实例化一次对象 第四条 通过私有构造器强化不可 ...
- Effective Java 读书笔记之九 并发
一.访问共享的可变数据时要同步 1.synchronized关键字既然保证访问的可见性也能保证原子性.而volatile修饰符只能保证变量的线程可见性. 2.增量操作符等不是原子性,多线程操作时可能导 ...
- Effective Java 读书笔记之七 通用程序设计
一.将局部变量的作用域最小化 1.在第一次使用变量的地方声明 2.几乎每个变量的声明都应该包含一个初始化表达式:try-catch语句是一个例外 3.使方法小而集中是一个好的策略 二.for-each ...
- Effective Java 读书笔记之一 创建和销毁对象
一.考虑用静态工厂方法代替构造器 这里的静态工厂方法是指类中使用public static 修饰的方法,和设计模式的工厂方法模式没有任何关系.相对于使用共有的构造器来创建对象,静态工厂方法有几大优势: ...
随机推荐
- [转发]ASP.NET Core2集成Office Online Server(OWAS)实现办公文档的在线预览与编辑(支持word\excel\ppt\pdf等格式)
转载自:https://www.cnblogs.com/Andre/p/9549874.html Office Online Server是微软开发的一套基于Office实现在线文档预览编辑的技术框架 ...
- 『MXNet』第六弹_Gluon性能提升 静态图 动态图 符号式编程 命令式编程
https://www.cnblogs.com/hellcat/p/9084894.html 目录 一.符号式编程 1.命令式编程和符号式编程 2.MXNet的符号式编程 二.惰性计算 用同步函数实际 ...
- android -------- java.net.UnknownServiceException
最近升级了Android的API版本时 ,导致我的网络请求失败了, 出现了这个错误 java.net.UnknownServiceException, 这个错误,我在网上查到这个主要是由于,我们的Ok ...
- osg编译日志-release
1>------ 已启动生成: 项目: ZERO_CHECK, 配置: Release x64 ------1> Checking Build System1> CMake does ...
- ES6深入浅出-8 新版的类(下集)-3.书籍推荐
- python学习:python打包成exe
1) 安装pyinstaller pip install pyinstaller 目前已经支持 python 3.7 版本 2) 打开cmd窗口,进入到要打包的python文件所在目录, pyinst ...
- js Date.parse()时区问题
比较两个时间,parse() 方法可解析一个日期时间字符串,并返回 1970/1/1 午夜距离该日期时间的毫秒数.Date.parse时间多了8小时. 正确的方法: var nowDate = Dat ...
- Swift4.0复习Optional
1.Optional基本使用: 当我们声明一个Optional对象时,无论该对象是在文件作用域还是在函数体内作为局部对象声明,如果不对它初始化,那么它的值默认为空(nil). // 声明a为Int类型 ...
- win10安装MySQL 8
下载并解压MySQL Server(官方有MySQL Installer 8.0.13,但是我安装没有成功) 将MySQL Server的bin目录加入到系统变量Path中. 找一个my.ini文件放 ...
- 【Leetcode_easy】914. X of a Kind in a Deck of Cards
problem 914. X of a Kind in a Deck of Cards 题意:每个数字对应的数目可以均分为多组含有K个相同数目该数字的数组. 思路:使用 map 结构记录数组中每个元素 ...