泛型(四)

以下为我在《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));
    }



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值