泛型
1.泛型介绍
1.1泛型类与泛型方法
泛型类是在类名+”<T>“, 泛型方法是在返回类型前面+”<T>“。这里的T是指一种不确定参数,随便起什么名字都可以。
下面的代码演示了泛型类与泛型方法与其调用:
public class GenericsClass<T> { public <K> boolean genericsMethod(T obj1, K obj2){ return obj1==obj2; } public static void main(String[] args){ String testStr = "obj"; GenericsClass<String> genericsClass = new GenericsClass<String>(); genericsClass.genericsMethod(testStr, testStr); } }
1.2为什么使用泛型
在java1.5后开始引入泛型,其目的有两个:一是提高代码的复用性, 二是让开发者在代码编译期间更早的发现代码可能出现的异常.
关于第一点: 提高代码的复用性。假设现在有一个Box类:
package joseph.learning.generics; public class Box { public String obj;public String getObj() { return obj; } public void setObj(String obj) { this.obj = obj; } }
里面有一个String类型的field,这样做的话假如我想往Box里面加入Integer类型的field就做不到了,现在只能加入String类型的Field,代码得不到复用,如果改成下面的这样子,就可以加入任何类型的field了:
public class Box<T> { public T obj;
public T getObj() { return obj; } public void setObj(T obj) { this.obj = obj; } }
调用方式:
Box<String> box1 =new Box<String>(); Box<Integer> box2 =new Box<Integer>(); Box<Boolean> box3 =new Box<Boolean>();
但其实不加入泛型也能做到代码复用,将field改为Object类型就好了,其实泛型类型的field在编译出来的class文件中就是经过处理转为成object类型的。
2.边界符
现在我们要实现这样一个功能,查找一个泛型数组中大于某个特定元素的个数:
public static <T> int countGreaterThan(T[] anArray, T elem) { int count = 0; for (T e : anArray) if (e > elem) // compiler error ++count; return count; }
但是这样是错误的, 因为除了int, dobule, float, char, byte这些基本数据类型,其他类不一定能使用操作符>,所以编译错报错,那么使用边界符可以解决这一问题。
public static <T extends Comparable<T>> void compare(T [] arry, T element){ int count = 0; for(T ele : arry){ if(ele.compareTo(element)>0){ count++; } } }
这样就相对与告诉编译器,我传进来的参数T都是实现了Comparable接口,都能调用compareTo这个方法。
3.通配符
借用一下上面的Box类,抛出一个问题,Box<Number>与Box<Integer>, Box<Double>有什么联系吧,答案是没有。
我们先定义几个简单类:
class Fruit {} class Apple extends Fruit {} class Orange extends Fruit {}
然后定义一个类GenericsReading,有个read方法在内部类在Reader里面去读取list:
public class GenericsReading { List<Fruit> fruitList =Arrays.asList(new Fruit()); List<Apple> AppleList =Arrays.asList(new Apple()); static class Reader<T>{ T read(List<T> list){ return list.get(0); } } void reading(){ Reader<Fruit> fruitReader =new Reader<Fruit>(); fruitReader.read(fruitList); //fruitReader.read(AppleList); Errors: List<Fruit> cannot be applied to List<Apple> } }
我们发现往其中添加AppleList会报错,因为List<Fruit>与List<Apple>没有联系。
如果使用通配符就可以解决这一个问题:
static class WildCardReader<T>{ T wildCardRead(List<? extends T> list){ return list.get(0); } } void wildCardReading(){ WildCardReader<Fruit> fruitReader =new WildCardReader<Fruit>(); fruitReader.wildCardRead(fruitList); fruitReader.wildCardRead(AppleList); }
‘? extends T’ 相当于告诉编译器我传入的参数都是T的子类(包括自己本身)。
PESC原则
1. List<? extends Fruit> fruitList = new ArrayList<Fruit>(); 只能get不能Add。
原因:
- 当我们尝试add一个Apple的时候,fruitList可能指向new ArrayList<Orange>();
- 当我们尝试add一个Orange的时候,fruitList可能指向new ArrayList<Apple>();
- 当我们尝试add一个Fruit的时候,这个Fruit可以是任何类型的Fruit,而flist可能只想某种特定类型的Fruit,编译器无法识别所以会报错。
所以对于实现了<? extends T>的集合类只能将它视为Producer向外提供(get)元素,而不能作为Consumer来对外获取(add)元素。
2. List<? super Apple> AppleList = new ArrayList<Fruit>(); 只能Add不能get。
原因: 当我们尝试通过list来get一个Apple的时候,可能会get得到一个Fruit,这个Fruit可以是Orange等其他类型的Fruit。
3. 总结:
- “Producer Extends” – 如果你需要一个只读List,用它来produce T,那么使用? extends T。
- “Consumer Super” – 如果你需要一个只写List,用它来consume T,那么使用? super T。
- 如果需要同时读取以及写入,那么我们就不能使用通配符了。
类型擦除
类型擦除就是说Java泛型只能用于在编译期间的静态类型检查,然后编译器生成的代码会擦除相应的类型信息,这样到了运行期间实际上JVM根本就知道泛型所代表的具体类型。
1. 在jdk1.5之后泛型才引用进来,为了兼容之前的版本,事实上编译出的class文件是没有泛型的,泛型参数 ‘T’都会转为object处理。
避免方法: 使用边界符即‘T extends interface’, 这样‘T’都会转为为相应的接口类型。
2.
Java泛型很大程度上只能提供静态类型检查,然后类型的信息就会被擦除,所以像下面这样利用类型参数创建实例的做法编译器不会通过:
public void add(List<T> list){ T t =new T(); list.add(t); }
因为编译器根本不知道T到底是什么类型。这时我们可以使用反射:
public static <E> void append(List<E> list, Class<E> cls) throws Exception { E elem = cls.newInstance(); // OK list.add(elem); }
3. 我们无法对泛型代码使用instance of关键字:
public static <E> void rtti(List<E> list) { if (list instanceof ArrayList<Integer>) { // compile-time error // ... } }
这时我们设置边界,使用通配符?
public static void rtti(List<?> list) { if (list instanceof ArrayList<?>) { // OK; instanceof requires a reifiable type // ... } }
原文引用:http://www.importnew.com/26387.html