26.请不要使用原生态类型
泛型是在1.5引入的,使用它可以有两点优势:
向集合插入不正确的类型的时,可以在编译时就抛出错误,而不是在运行时才抛出异常,即有类型检查。
从集合检索元素时,编译器帮我们做隐式转换,而不需要我们显示强转,既有隐式转换。
标题中的提到的原生态类型是不带任何实际类型的泛型名称,例如List<E>的原生态类型是List。原生态类型缺乏类型安全性,而继续使用仅仅是为了兼容性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
List list = new ArrayList(); list.add("123" ); list.add(123 ); Integer i = (Integer) list.get(0 ); List<String> list = new ArrayList<>(10 ); list.add("123" ); list.add(123 ); String str = list.get(0 );
而原生类型其实也有几处适用的地方:
必须在类文字中使用原生类型
1
List.class String[].class int .class都是合法的,而List<String.class>和List<?>.class则不合法
在使用instanceof
1 2 3 4 5 6 7 8 9
public void (Collection<E> collection) { if (collection instanceof Set){ Set<?> set = (Set<?>) collection; Iterator<?> it = set.iterator(); while (it.hasNext()){ System.out.println(it.next()); } } }
27.消除非受检的警告
在使用泛型的代码中,很容易出现警告,不要忽略他们。每一条警告都可能在运行时抛出ClassCaseException异常,要尽最大努力消除他们。如果无法消除,但能确定引起警告的代码是类型的安全的,就可以在尽可能小的范围内使用@SuppersssWarnings("unchecked")注解禁止该警告。
28.列表由于数组
数组具有协变性,而集合没有
1 2 3 4 5 6 7
Father[] sons = new Son1[1 ]; sons[0 ] = new Son2(); List<Father> list = new ArrayList<Son1>();
以上很容易看出,由于数组的协变性,很容易随意存入元素,然后再运行时抛出异常。
再举个例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
class A <E > { private E[] arr; public A (Collection<E> collection) { arr = (E[]) collection.toArray(); } } class A <E > { private List<E> list; public A (Collection<E> collection) { list = new ArrayList<>(collection); } }
只想说明一点,数组和泛型不能很好的混合使用。如果在混合使用时,出现异常,应该立马尝试将数组换成列表。
29.优先使用泛型
直接看实例,忽略下标范围检查
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
class Stack { public Object[] elements; private int size = 0 ; private final int DEFAULT_SIZE = 10 ; public Stack () { elements = new Object[DEFAULT_SIZE]; } public void push (Object e) { this .elements[size++] = e; } public Object pop () { return elements[--size]; } }
方案一
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
class Stack <E > { public E[] elements; private int size = 0 ; private final int DEFAULT_SIZE = 10 ; ("unchecked" ) public Stack () { elements = (E[]) new Object[DEFAULT_SIZE]; } public void push (E e) { this .elements[size++] = e; } public E pop () { return elements[--size]; } }
方案二
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
class Stack <E > { public Object[] elements; private int size = 0 ; private final int DEFAULT_SIZE = 10 ; public Stack () { elements = new Object[DEFAULT_SIZE]; } public void push (Object e) { this .elements[size++] = e; } ("unchecked" ) public E pop () { return (E) elements[--size]; } }
原始代码没有使用泛型,容易造成类型安全问题。方案一和方案二都是用了泛型了,更加安全些。
方案一要确保elements中的元素都是通过push存储进去的,不能将elements暴露在外,否则容易造成类型全问题
优先使用方案一,方案一只需强转一次,而方案二在每次pop元素时都要进行强转。
30.优先考虑泛型方法
静态工具方法尤其适合泛型化,例如Collections中的排序和查找算法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
public class MyTest { @Test public void () { Set<String> set1 = Set.of("1" ,"2" ); Set<String> set2 = Set.of("2" ,"3" ); Set<String> resultUnion = A.union(set1,set2); System.out.println(resultUnion); Set<String> resultIntersection = A.intersection(set1,set2); System.out.println(resultIntersection); } } class A { public static <E> Set<E> union (Set<E> set1,Set<E> set2) { Set<E> result = new HashSet<>(set1); result.addAll(set2); return result; } public static <E> Set<E> intersection (Set<E> set1,Set<E> set2) { Set<E> result = new HashSet<>(set1); result.retainAll(set2); return result; } }
下面递归泛型的例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
public class MyTest { @Test public void () { List<String> list = new ArrayList<>(3 ); list.add("1" ); list.add("3" ); list.add("2" ); System.out.println(A.max(list)); } } class A { public static <E extends Comparable<E>> E max (Collection<E> collection) { E max = null ; for (E e : collection){ if (max == null || e.compareTo(max) > 0 ){ max = e; } } return max; } }
31.利用有限制通配符类提升API的灵活性
A是B的子类,但List<A>不是List<B>的子类。可以使用?提高灵活性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
public class MyTest { @Test public void () { Stack<A> stack = new Stack<>(); stack.pushAll(new ArrayList<B>()); } } class A {}class B extends A {}class Stack <E > { List<E> list; public Stack () { list = new ArrayList<>(); } public void pushAll (Collection<? extends E> src) { for (E e : src){ list.add(e); } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
public class MyTest { @Test public void () { Stack<B> stack = new Stack<>(); stack.pushAll(new ArrayList<A>()); } } class A {}class B extends A {}class Stack <E > { List<E> list; public Stack () { list = new ArrayList<>(); } public void pushAll (Collection<? super E> dst) { for (E e : list){ dst.add(e); } } }
总结一下,当外来元素要进来,就是用? extents E, 因为传入的元素必须是E以及子类,才能被E接收。
当内部元素要出去,就使用? super E,因为E要出去,必须要使用E以及父类才能接收。
32.谨慎并用泛型和可变参数
略
33.优先考虑类型安全的异构容器
略
参考文献
近期评论