JDK1.5引入了泛型,在泛型引入之前,从集合中读取到的每个对象都必须进行转换,如果不小心引入了错误的对象,在运行时就会出错。有了泛型之后,编译器会在编译阶段检查你是否插入了错误的对象。这样是程序更加安全。
所谓的原生态类型,就是不太任何实际类型参数的泛型名称,例如List<E>相对应的原生态类型就是List。
本条说的主要是使用通配符的要点,可以参考这几篇文:
泛型之中的通配符(Wildcards)使用
Java泛型– 通配符
Java 泛型通配符?解惑
消除编译期出现的警告,可以避免的运行时出现ClassCastException异常。
如果无法消除警告。同时可以证明引起警告的代码安全的,才可以使用一个@SuppressWarning("unchecked")注解来禁止这条警告。
被@SuppressWarning("unchecked")注解警告的代码范围应该尽可能的小,不要在整个类上加这个注解。
列表和数组的两个主要不同点:
1、数组是协变的(即如果Sub是Super的子类型,那么数组类型Sub[]就是Super[]的子类型),而泛型是不可变的(即对于任意两个不同的类型Type1和Type2,List<Type1>既不是List<Type2>的子类型,也不是List<Type2>的超类型)。
看个栗子:
Object[] objectArray = new Long[1]; objectArray[0] = "I don't fit in";上面程序会在编译时通过,但运行时失败。而下面的代码会在编译时失败:
List<Object> o1 = new ArrayList<Long>();//编译失败 o1.add("I don't fit in");第二个区别是:数组是具体化的,因此数组在运行时才知道并检查它们的元素类型约束。而泛型只在编译时强化它们的类型信息。
正是由于这些区别,导致数组和泛型不能很好混用,下面这些都是非法操作:
//非法 new List<E>[] new List<String>[] new E[]为什么泛型数组会出错,下面通过示例说明:
//创建了一个泛型数组,这是非法的 List<String>[] stringLists = new List<String>[1];//(1) //创建并初始化了一个包含单个元素的List<Integer>,合法 List<Integer> intList = Arrays.asList(42);//(2) //将List<Stirng>数组保存到一个Object数组变量中,这是合法的 Object[] objects = stringLists;//(3) //将List<String>保存到Object数组的位移元素中,合法 objects[0] = intList;//(4) string s = stringLists[0].get(0);//(5)我们将一个List<Integer>实例保存到了原本声明只包含List<String>实例的数组中。在(5)中我们从这个数组中获取列表的唯一元素,编译器自动地将获取到的元素转换成String,但它是个Integer。于是抛出一个ClassCastException异常,所以在第一行会产生一个编译错误。
总之,数组和泛型游着非常不同的类型规则。数组是协变且可以具体化的;但是泛型是不可协变的且可以被排除的。因此,数组提供了运行时的类型安全,但是没有编译时的类型安全,反之,对于与泛型也一样。一般来说,数组和泛型不能很好地混合使用。如果你发现自己将它们混合起来使用,并且得到了编译时错误或者警告,你的第一反应该是用列表代替数组。
这是第6条中的堆栈实现:
public class Stack { private Object[] elements; private int size = 0; private static final int DEFAULT_INITIAL_CAPACITY = 16; public Stack(){ elements = new Objects[DEFAULT_INITIAL_CAPACITY]; } public void push(Object e) { ensureCapacity(); elements[size++] = e; } public Object pop() { if(size = 0) { throw new EmptyStackException(); } return elements[--size]; } private void ensureCapacity() { if(elements.length == size) { elements = Arrays.copyOf(elements, 2 * size + 1); } } }可以强化这个类用泛型实现:
public class stack<E> { private E[] elements; private int size = 0; private static final int DEFAULT_INITIAL_CAPACITY = 16; public Stack { //这句话报错 elements = new E[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; return result; } }在上面这个类中,elements = new E[DEFAULT_INITIAL_CAPACITY];这句话会报错。因为不能创建不可具体化的类型数组。如E。
解决方法一:创建一个Object[]数组,强转为E[]:
elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];这会产生一个警告(类型转化的警告),但是我们确信这是合法的,所以可以加注解消除警告:
@SupressWarning("uncheked") Stack { ... }第二种解决方式:将E[] 改为Object[],并进行强转:
E result = (E) elemments[--size];最后示范泛型Stack类的使用。程序以相反的顺序打印出它的命令行参数,并转换成大写字母:
public static void main(String[] args) { Stack<String> stack = new Stack<String>(); for(String arg : args) { stack.push(args); } while(!stack.isEmpty()) { System.out.println(stack.pop().toUpperCase()); } }使用泛型比使用需要在客户端代码中进行转换的类型来的更安全,也更容易,在设计新类的时候,要确保它们不需要这种转换就可以使用。这通常意味着要把类做成泛型的。
下面这个方法,返回两个集合的联合:
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<E>(s1); result.addAll(s2); return result; }使用上面的方法:
public static void main(String[] args) { Set<String> guys = new HashSet<String>(Arrays.asList("Tom", "Dick", "Harry")); Set<String> stooges= new HashSet<String>(Arrays.asList("Larry", "Moe", "Curly")); } Set<String> aflcio = union(guy, stooges); Systen.out.println(aflcio);泛型方法像泛型一样,使用起来比要求客户端转换输入参数并返回值的方法来的更加安全,也更加容易,在设计泛型方法时,应确保新方法可以不用转换就能使用。
略
略