第5章 泛型

23、请不要在新代码中使用原生态类型

       如原生态类型List和参数化的类型List<Object>之间的区别,前者逃避了泛型检查,后者则明确告知编译器,它能够持有任意类型的对象。如(注意List是接口):

List ls = newLinkedList<String>();//可以

List<Object> ls2 = new LinkedList<String>();//错误

       在不确定或者不在乎集合中的元素类型的情况下,你也许会使用原生态类型,但很危险。java在1.5版本后提供了一种安全的替代方法,称作无限制的通配符类型,如Set<?>,它是安全的(可以理解为没有逃避泛型检查?防止破坏集合的类型约束条件)。

       用到原生态类型的情况(小小例外):(1)在类文字中必须使用原生态类型,如List.class等;(2)与instanceof有关,如if(o instanceof Set),一旦确定o是Set,就必须将它转换成通配符类型Set<?>m = (Set<?>)o,而不是原生态。

24、消除非受检警告

       要尽可能地消除每一个非受检警告(这是一件很好的事)。如果无法消除警告,同时可以证明引起警告的代码是类型安全的,(只有在这种情况下)可以用一个@SuppressWarnings(“unchecked”)注解来禁止这条警告。应该始终在尽可能小的范围中使用SuppressWarnings注解。例如return**函数;该函数产生警告,你不应该将注解放到整个外部方法上,需要创建一个新的局部变量,然后用@SuppressWarnings(“unchecked”) 类型 s = **函数;在添加使用该注解时,都要添加一条注释,说明为什么是安全的(这也是种好习惯)。

25、列表优先于数组

数组与泛型的区别:(1)数组是协变的(covariant),即如果Sub是Super的子类型,那么Sub[]就是Super[]的子类型。泛型是不可变的。产生的影响:

Object[]objectArray = new Long[1];//编译通过

objectArray[0]=  “zifuchuan”;//这会抛出ArrayStoreException异常。

List<Object>ol = new ArrayList<long>();//这边不可以这么写,编译错误

ol.add(“zifuchuan”);

(2)数组是具体化,因此数组在运行时才知道并检查它们的元素类型约束。而泛型则是通过擦除来实现的,因此泛型只在编译时强化它们的类型信息,并在运行时丢弃它们的元素类型信息。

       数组和泛型不能很好地混合使用。如new List<E>[]、newList<String>[]和new E[]编译时导致generic array creation(泛型数组创建)错误。防止出现数组协变导致的错误。

List<String>[]stringLists = new List<String>[1];//不可以

List<?>[] stringLists1 = new List<?>[1];//可以

创建无限制通配符类型的数组是合法的。

       当你得到泛型数组创建错误时,最好的解决办法通常是优先使用集合类型List<E>,而不是数组类型E[]。这样可能会损失一些性能或简洁性,但是换回的却是更高的类型安全性和互用性

26、优先考虑泛型

       主要就是以Stack内部private Object[] elements;在初始化及pop时,类型转化的问题。先泛型化:类为Stack<E>,进一步提出了两种解决方案:(1)将private Object[] elements;改成privateE[] elements;,这样在初始化时数组要求做强制转换elements= (E[]) new Object[默认长度];(2)elements不改,在pop那边将弹出元素强制类型转换Eresult = (E) elements[--size];一般选择方法二,禁止数组类型的未受检转换比禁止标量类型要危险

       可以创建Stack<int[]>但不能创建基本类型的Stack,如Stack<int>。也有些泛型会限制可允许的类型参数值,如<E extends Delayed>,类型必须是Delayed的子类型。

27、优先考虑泛型方法

       静态工具方法尤其适合于泛型化。如public static <E> Set<E> union(Set<E>  s1,Set<E> s2){···}声明类型参数的类型参数列表,处在方法的修饰符及其返回类型之间。

       泛型方法的一个显著特性是,无需明确指定类型参数的值,不像调用泛型构造器的时候是必指定的。

       类型参数出现在变量声明的左右两边,显得有些冗余:Map<String,List<String>> anagrams = new HashMap<String,List<String>>();为了消除这种冗余,可以编写一个泛型静态工厂方法:public static <K,V> HashMap<K,V>newHashMap(){return new HashMap<K,V>();}

       一个泛型函数可用于多个具体类型的函数调用。总之,泛型方法就像泛型一样,使用起来比要求客户端转换输入参数并返回值的方法来得更加安全,也更加容易。

28、利用有限制通配符来提升API的灵活性

       再次强调一个常识:参数化类型是不可变的。对于两个截然不同的类型Type1和Type2,List<Type1>既不是List<Type2>的子类型也不是它的超类型,例如Type是String与Object也是一样。这是有意义的!防止String存入Object后转成Integer这些就会出错。但如果你能保证转换的正确性,该如何打破这种约束(好的编程习惯,下面的值得记住):

       以Stack为例,我们定义了pushAll(Iterable<E> src){···},我们想对Stack<number>存入Integer元素,则会出错。我们可以通过有限制的通配符类型(<? extends E>),将传入参数类型改成<?extends E> src就可以了。同样,popAll(Collection<E>dst){···}是从堆栈中弹出每个元素到dst中,我们想将Number弹入Object中还是不可以,将传入参数改成Collection<? super E> dst,就可以向dst中添加了。

       为了获得最大限度的灵活性,要在表示生产者(抽象层面,获得数据)或者消费者(处理数据,将数据给别的容器)的输入参数上使用通配符类型。参数化类型表示一个T生产者,就使用<?extends E>,说明从目标对象获得的数据是E的子类,赋值给E就不会出错;如果它表示一个T消费者,就使用<? super T>,说明把E传给它的超类才是安全的。(像String传给Object的List,取出来转成Integer则不满足后面消费者的条件)。

       有限制通配符是把双刃剑,如java编程思想所体现的。如果声明具体类型的有限制通配符,如List<? extends Fruit> f = newArrayList<Apple>();,f中无法add任何非null对象···。Fruit a = f.get(0);是可以的。因此用的时候还得分清情况。

       java的类型推导规则复杂,有时并不能很好工作,如<E> Set<E> union(Set<? extendsE> s1, Set<? extends E> s2);调用Set<Number> numbers =union(Integer,Double)会出错,它不知道E是啥。解决方法显示指定E,如Set<Number> numbers = Union.<Number>union(Integer,Double);//Union.(类名)不能少,否则后面的<Number>会出错,显示“Numbercannot be resolved to a variable”。

       对于public static void swap(List<?>list,int i,int j){list.set(i,list.set(j,list.get(i)));}会出错,问题在于list的类型为List<?>,你不能把null之外的任何值放到List<?>(因为List<?>表示“某种特定类型的非原生List,只是我们不知道那种类型是什么”)。但可以编写一个私有的辅助方法来捕捉通配符类型(必须是泛型方法):privatestatic <E> void swapHelper(List<E> list,int i,int j){ list.set(i,list.set(j,list.get(i)));}就可以了。

29、优先考虑类型安全的异构容器

       将键进行参数化而不是将容器参数化,称作类型令牌。如:

public class Favorites {

         private Map<Class<?>,Object> favorites = new HashMap<Class<?>,Object>();

         public<T> void putFavorite(Class<T>type,T instance){

                   if(type== null)

                            throw new NullPointerException("Type is null");

                   favorites.put(type,instance);//可以添加!通配符不是属于通配符类型的Map类型,而是它的键的类型

         }

         public<T> T getFavourite(Class<T> type){

                   return type.cast(favorites.get(type));//value值只能是Object类型,因此需要动态转换成Class对象所表示的类型

         }

}

       Favorites实例是类型安全的(如请求String不会返回Integer),也是异构的(所有键都是不同类型),因此将Favorites称为类型安全的异构容器

       Favorites类有两种局限性:(1)putFavorite的instance存在漏洞,用原生态形式使用Class对象跳过泛型检查,从而将String放入HashSet<Integer>中。确保不出现这种错误,使用动态的转换,如favorites.put(type, type.cast(instance));(2)它不可以用在不可具体化的类型中。即可以保存最喜欢的String或String[],但不能将List<String>.Class作为自己最喜欢的类(这是语法错误)。可以用有限制通配符来加强类型约束。

       将一个类型Class<?> annotationType对象传递给一个有限制通配符的参数,如Class<? extends Annotation>,强制转换会产生警告。类Class提供一个安全(且动态)的方法asSubclass,如annotationType. asSubclass(Annotation.class);返回该参数类的子类,如果失败抛出ClassCastException异常。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值