以下为我在《Effective Java》中留下的读书笔记,对一些重要的知识点提取出了摘要.
23、不要在新代码中使用原生态类型
术语 | 示例 |
参数化的类型 | List<String> |
实际类型参数 | String |
泛型 | List<E> |
形式类型参数 | E |
无限制通配符类型 | List<?> |
原生态类型 | List |
有限制类型参数 | <E extends Number> |
递归类型参数 | <T extends Comparable<T>> |
有限制通配符类型 | List<? extends Number> |
泛型方法 | static <E> List<E> asList(E[] a) |
类型令牌 | String.class |
泛型有子类型化的规则,List<String>是List的一个子类型,而不是参数化类型List<Object>的一个子类型
Set<Object>、Set<?> 与 Set
Set<Object>是个参数化类型,可以包含任何对象类型的一个集合;Set<?>是一个通配符类型,只能包含某种未知对象类型的一个集合;Set则是原生态类型,脱离了泛型系统,不安全.
不能将任何元素(除了null之外)放到Collection<?>中.
使用原生态类型的情况:
1、类文字中必须使用原生态类型 List.class
2、instanceof 操作符使用原生态类型
疑惑:Set<E> 与 Set<?>的区别
24、消除非受检查警告
unchecked cast warnings(非受检强制类型转换)、非受检方法调用警告、非受检普通数组创建警告、非受检转换警告(unchecked conversion warnings)
如果无法消除警告,同时可以证明引起警告的代码是类型安全的,只有在这种情况下才可以用
@Suppress Warnings("unchecked") 注解来禁止这条警告.
粒度最好控制在变量一级.如果在长度不止一行的方法或者构造器中使用了此注解,可以考虑声明一个新的局部变量,将注解移植到变量级别上去.
每当使用SuppressWarnings("unchecked")注解时,都要添加一条注释,说明为什么这么做是安全的.
25、列表优先于数组
数组是协变的 covariant : Sub为Super的子类型,那么数组类型Sub[]就是Super[]的子类型 。
擦除 erasure 泛型只在编译时强化它们的类型信息,并在运行时丢弃它们的元素类型信息.
创建泛型、参数化类型或者类型参数的数组是非法的.new List<E>[]、new List<String>[]、new E[].在编译时都会导致一个generic array creation 错误.
数组和泛型不能很好地混合使用(E[]),用列表代替数组(List<E>)
26、优先考虑泛型
将类泛型化的步骤:
1、给它的声明添加一个或者多个多个类型参数.
2、用相应的类型参数替换所有的Object类型, 然后试着编译最终的程序.
a.不能创建不可具体化的类型的数组,如E[].每当编写用数组支持的泛型时,都会出现这个问题.
解决办法:
i.创建一个Object的数组实例并将它转换成泛型数组类型.但是不能完全地保证类型安全的.因此,需要让Object[]仅仅只通过能保证它类型安全的方法进行访问.(证明了未受检的转换是安全的)同时增加注解
@Suppress Warnings("unchecked") 来禁止掉警告.
ii.将E[]域的类型改为Object[].再把从数组中获取的元素由Object转换成E.
禁止数组类型的未受检转换比禁止标量类型的更危险. 但更多情况为了方便依旧是禁止的标量类型
子类型关系,每个类型都是它自身的子类型.
27、优先考虑泛型方法
静态工具方法尤其适合泛型化.
泛型方法的显著特性是,无需明确指定类型参数的值,不像调用泛型构造器的时候是必须指定的.
在Java1.7以前,在调用泛型构造器的时候,要明确传递类型参数的值使得类型参数出现在了变量声明的两边,显得有些冗余:
Map<String,String> map = new HashMap<String,String>();
为了消除这种冗余,可以编写一个泛型静态工厂方法,与想要使用的每个构造器相对应.例如,下面是一个与无参的HashMap构造器相对应的泛型静态工厂方法:
public static <K,V> HashMap<K,V> newHashMap(){
return new HashMap<K,V>();
}
递归类型限制:<T extends Comparable<T>>,读作"针对可以与自身进行比较的每个类型T"
补充:恒等函数——返回未被修改的参数.
28、利用有限制通配符来提升API的灵活性
为了获得最大限度的灵活性,要在表示生产者或者消费者的输入参数上使用通配符类型.例如pushAll(Iterable<? extends E>),popAll(Collection<? super E>)
Collection<? entends E> Collection<? super E>
Get and Put Principle PESC producer-extends,consumer-super原则
不要用通配符类型作为返回类型.
public static <E> Set<E> union(Set<? extends E> s1, Set<? extends E> s2){......}
Set<Integer> integers = ...;
Set<Double> doubles = ...;
Set<Number> numbers = union(integers, doubles);
编译器不能推断你希望它拥有的类型,会出现错误.(Java1.8测试似乎没有出现此错误,可能是默认判断拥有的类型为声明时的类型)不太优雅的处理方式:
Set<Number> numbers = Union.<Number>union(integers, doubles);
类型参数和通配符之间具有双重性,许多方法都可以利用其中一个或者另一个进行声明. (? 和 E)
一般来说,如果类型参数只在方法声明中出现一次,就可以用通配符取代它.
通配符存在的问题:
public static void swap(List<?> list, int i, int j){
list.set(i,list.set(j, list.get(i)));
}
不能把null之外的任何值放到List<?>中.
借助类型参数的辅助方法可以实现这个方法:
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)));
}
29、优先考虑类型安全的异构容器
public interface Favorites {
public <T> void putFavorite(Class<T> type, T instance);
public <T> T getFavorite(Class<T> type);
public static void main(String[] args) {
Favorites f = null; //should new implementary instance
f.putFavorite(String.class, "Java");
f.putFavorite(Integer.class, 0xcafebabe);
f.putFavorite(Class.class, Favorites.class);
String favoriteString = f.getFavorite(String.class);
Integer favoriteInteger = f.getFavorite(Integer.class);
Class<?> favoriteClass = f.getFavorite(Class.class);
}
}
Favotites实现
public class Favorites{
private Map<Class<?>, Object> favorites = new HashMap<>();
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));
}
}
Class实例的cast方法,将对象引用动态地转换成了Class对象所表示的类型.
cast方法是Java的cast操作符的动态模拟.它值检查它的参数是否为Class对象所表示的类型的实例.如果是,就返回参数;否则就抛出ClassCastException异常.
Favorites的局限性
1、使用原生态类型实例会破坏类型安全.为确保Favorites永远不违背它的类型约束条件的方式是,拥有运行时的类型安全.如下:
public <T> void putFavorite(Class<T> type, T instance){
favorites.put(type, type.case(instance));
}
Collections中有一些集合包装类采用了同样的技巧,例如checkedSet、checkedList、checkedMap.用这些包装类在混有泛型和遗留代码的应用程序中追溯"谁把错误的类型元素添加到了集合中"很有帮助.
2、不能用在不可具体化的类型中.例如List<String>.因为无法获得List<String>的Class对象.
class.asSubClass 将Class<?> 转换成 Class<? entends Annotation>,如果失败,抛出ClassCastException异常:
static Annotation getAnnotation(AnnotatedElement element, String annotationTypeName){
Class<?> annotationType = null;
try {
annotationType = Class.forName(annotationTypeName);
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException(e);
}
return element.getAnnotation(annotationType.asSubclass(Annotation.class));
}