Java基础:泛型
2015-06-16 20:05
387 查看
泛型不协变
数组是协变的,即如果Integer是Number的子类型,则Integer[]也是Number[]的子类型Integer[] is = new Integer[] {1, 2, 3}; Number[] ns = is; ns[0] = new Integer(0); ns[0] = new Double(0); // will cause java.lang.ArrayStoreException in runtime
以上代码可以通过编译,但是在运行时会抛出异常,这就是数组协变的代价,它需要再运行时才能正真的检测到类型异常。下面是类似的逻辑的但是用泛型实现的代码
List<Integer> is = new ArrayList<Integer>(); is.add(1);is.add(2); List<Number> ns = is; // compile error List<Object> os = is; // compile error
虽然一般来说List泛型在类型擦除后使用的就是一些对象引用而已,相当于用Object替换了类型参数,但还是不能直接赋值。
通配符与上下界
泛型不协变的特性增加了类型安全性,但是使用上却似乎更加不便了。除了使用严格的类型参数外我们还可以使用类型通配符如List<Integer> is = new ArrayList<Integer>(); List<?> xs = is; int len = xs.size(); for (int i=0; i<len; i++) { System.out.println((Integer)xs.get(i) + 10086); } xs.add(new Integer(1)); // compile error
此处我们实现了不同类型参数之间的一个(比较特别的)赋值,然而经过这次赋值后,
xs变量代表的
List已经失去了其在编译期的类型,因为它使用了类型通配符
?。从其中再取出元素时需要进行显式的转换,编译器不会再帮你检查。更糟糕的是我们不能在通过
xs.add向列表添加元素了。
幸亏除了单独使用通配符之外还可以给他确定一个上界或者下界。
List<Integer> is = new ArrayList<Integer>(); List<? extends Number> xs = is; System.out.println(xs.get(0).intValue() + 10086);
extends确定了泛型容器内元素类型的一个上界,也就是说保证容器内部的元素都是Number或者Number的子类,所以从容器中再次获取元素时我们可以直接以Number类型来对待这个元素。但是要想以Integer的方式操作元素还是需要进行强制转换,因为根据定义我们只能确定用Number类型对待它时是绝对安全的,但是至于它到底是那个子类,重新取出元素时我们并不知道。这个就和数组的处理非常相似了
Number[] x = new Integer[]{1, 2, 3}; x[0]; // process as type Number, cast needed if want Integer type
注意如果同时还要向容器内放入元素那么这个容器声明就有问题,这和数组是不一样的,参考PECS规则。
List<Integer> is = new ArrayList<Integer>(); List<? extends Number> xs = is; xs.add(new Integer(1)); // compile error
<?>与 的区别
public static void process(List<?> list) { list.add(list.get(0)); // compile error } public static <T> void process(List<T> list ){ list.add(list.get(0)); }
PECS规则
producer-extends,consumer super. Effective Java p120
通配符可以使用extends与super来给其定类型的上下界,什么时候使用extends什么时候使用super可以用PECS这个规则来概括。即
当需要从某个泛型对象获取元素时(该对象作为一个生产者),使用extends来说明其泛型类型的上界
当需要把元素存入对象时(该对象所为一个消费者),使用super来说明其泛型类型的下界
extends
比如要从某个容器内获取元素时,或者这个泛型对象并不是一个容器但是它的值需要由别人提供并且被你用到,如果普通的一个函数参数。它们都是生产者,比较常见的有容器和迭代器。大部分情况下我们规定泛型界限时都用extends即可。super
它用于充当消费者角色的对象类型说明。消费者,即它们会用到你提供的值。Comparable,Comparator均是这种类型。还有就是用来存储运算结果的一些容器类。static void inflate(List<? super Integer> out) { out.add(1); out.add(2); } public static void main(String args[]) { List<Object> d1 = new ArrayList<Object>(); List<Number> d2 = new ArrayList<Number>(); List<Integer> d3 = new ArrayList<Integer>(); inflate(d1); inflate(d2); inflate(d3); }
运行之后
d1, d2, d3三个列表内均存储了
inflate函数产生的两个数字。如果我们把
inflate的参数类型改为
static void inflate(List<? extends Integer> out); // List<Object>, List<Number> conflict static void inflate(List<Integer> out); static void inflate(List<?> out); static void inflate(List<Object> out);
都无法实现上述功能(使得d1,d2,d3得到填充)。
容器泛型
从PECS规则来讲,容器类所支持的一些操作使得容器本身既是一个生产者也是一个消费者。所以当需要在一个函数内同时对容器进行添加和获取这两类操作时,上界和下界限制都存在,也就是说相关的函数参数中泛型参数不再需要界限限定,它肯定是一个明确的类型。Comparable泛型
有关Comparable泛型相比一般的泛型定义复杂许多,可以参照下面这个范式:public static <T extends Comparable<? super T>> T max(List<? extends T> a) { ... }
为什么使用
Comparable<? super T>而不是诸如:
Comparable<T> // too strict Comparable<? extends T>
第一个要求过于严格,需要刚好是该类型实现了相应Comparable接口,如果一个类继承了一个已经实现了Comparable接口的类,但它自己没有重新去实现,这个类就无法被第一个Comparable定义使用。
public static void main(String args[]) { List<Sedan> sedans = new ArrayList<Sedan>(); max(sedans); // compile error, invalid type bound, no Comparator<Sedan> } public static <T extends Comparator<T>> T max(List<? extends T> list) { return null; } class Vehicle implements Comparator<Vehicle> { private int speed; public int compare(Vehicle o1, Vehicle o2) { return o1.speed - o2.speed; } } class Sedan extends Vehicle { }
? extends T就更不行了,
? super T就是让函数接受父类实现Comparable的情况。来看
Comparable<E>接口定义:
public interface Comparator<T> { int compare(T o1, T o2); boolean equals(Object obj); } Comparable<? super T> x = Comparable<T> y = Comparable<? extends T> z =
其中的
compare需要用到类型参数,可以想象一下使用extends和super时的不同情况,如果有个Comparable实现它也是可以被super修饰时赋值的。这个和在讨论容器作为消费者时的情况差不多了。
相关文章推荐
- JAVA环境变量配置
- Maven eclipse 或者myeclipse 报错问题 Check $M2_HOME environment variable and mvn script match.
- Java内存与垃圾回收调优
- Java并发编程-15-并发任务间数据交换
- 简易Java(02):如何构建您自己的Java库?
- 我的Java基础知识总结1
- Java如何对HashMap按值进行排序
- Java Client for Google Cloud Storage
- Java 字符串分隔 split
- java两种创建String对象的区别
- 我的Java开发学习之旅------>Java String对象作为参数传递的问题解惑
- Spring注解详解
- Java套接字2
- Spring事务配置的五种方式(转载)
- 第一课Java
- Eclipse 警告提示:Access restriction:The type JPEGCodec is not accessible due to restriction on
- 在Eclipse安装ADT
- Java类被使用的几种情况
- velocity整合springMVC
- 简易Java(17):Java中的实例初始化器是什么?