@lemonguge
2015-06-23T02:31:44.000000Z
字数 9835
阅读 395
JAVA
在面向对象编程语言中,多态算是一种泛化机制。例如,你可以将方法的参数类型设为基类,那么该方法就可以接受从这个基类中导出的任何类作为参数。这样做能够具备更好的灵活性,但是,考虑到除了final类不能扩展,所以这种灵活性大多时候也会有一些性能损耗。
但是,这样做拘泥于单继承体系,也会使程序受限太多。如果方法的参数是一个接口,而不是一个类,这种限制就放松了许多。可是有的时候,即便使用了接口,对程序的约束性也还是太强。因为一旦指明了接口,它就要求你实现该接口的所有方法。
泛型的目的:为了编写更通用的代码,使代码能够应用于“某种不具体的类型”,而不是一个具体的接口或类。
这就是Java SE5的重大变化之一:泛型的概念。泛型实现了参数化类型的概念,使代码可以应用于多种类型。有很多原因促成了泛型的出现,而最引入注目的一个原因,就是为了创造容器类。此时泛型是用来指定容器要持有什么类型的对象,而且由编译器来保证类型的正确性。
在Java SE5之前,我们使用Object类型来存储任何类型的对象,在使用时必须进行强制类型转换。在泛型出现之后,我们更喜欢暂时不指定类型,而是稍后再决定具体使用什么类型,编译器会为我们负责转型操作,并且保证类型的正确性。
Java泛型的一个局限性:基本类型无法作为类型参数,不过可以用相对应的包装类型进行转换。
泛型类:使用类型参数,用尖括号扩住,放在类名后面。
使用这个类的时候,再用实际的类型替换此类型参数。在下面的例子中,T就是类型参数(type)。
class Cellphone { }// 泛型类public class Holder<T> { // T为类型参数,不固定可自定义private T a;public Holder(T a) { this.a = a; }public void set(T a) { this.a = a; }public T get() { return a; }public static void main(String[] args) {// 创建对象的时候指明了类型参数的值Holder<Cellphone> h3 = new Holder<Cellphone>(new Cellphone());Cellphone a = h3.get(); // No cast needed// h3.set("Not an Cellphone"); // Error// h3.set(1); // Error}}
使用泛型类时,必须在创建对象的时候指定类型参数的值。
泛型也可以用于接口。例如生成器(generator),这是一种专门负责创建对象的类,生成器无需额外的信息就知道如何创建新对象。
interface Generator<T> { T next(); } // 生成器,方法的返回类型是参数化的T// 向上抽取固定电话和移动电话而产生的基类class Phone {private static int counter = 0;private final int id = counter++;@Overridepublic String toString() { return getClass().getSimpleName() + " " + id; }}class Telephone extends Phone { } // 固定电话class Cellphone extends Phone { } // 移动电话// 实现了生成器接口的类,用于生成电话public class Generate implements Generator<Phone>{private Class[] phones = { Telephone.class, Cellphone.class };private static Random rand = new Random(47);private int size ;public Generate(int size) {this.size = size;}@Overridepublic Phone next() { // 实现接口中的方法,随机产生电话的导出类try {if(size-->0)return (Phone) phones[rand.nextInt(phones.length)].newInstance();return null;} catch (Exception e) {throw new RuntimeException(e);}}public static void main(String[] args) {Generate gen = new Generate(3);for (int i = 0; i < 5; i++)System.out.println(gen.next());}} /* Output:Cellphone 0Telephone 1Cellphone 2nullnull*///:~
泛型方法的定义:只需将泛型参数列表置于返回值之前。
public class GenericMethods {public <T> void f(T x) {System.out.println(x.getClass().getName());}public static void main(String[] args) {GenericMethods gm = new GenericMethods();gm.f("");gm.f(1); // 自动装箱gm.f(1.0);gm.f(1.0F);gm.f('c'); // 等价于gm.<java.lang.Character>f('c'),显式指明类型gm.f(gm);}} /* Output:java.lang.Stringjava.lang.Integerjava.lang.Doublejava.lang.Floatjava.lang.Charactergenerics.GenericMethods*///:~
是否拥有泛型方法,与其所在的类是否是泛型没有关系。泛型方法使得该方法能独立于类而产生变化,无论何时,只要你能做到,你就应该尽量使用泛型方法。
static方法而言,由于无法访问泛型类的类型参数,只能成为泛型方法。类型参数推断:使用泛型方法的时候,通常不必指明参数类型,因为编译器会为我们找出具体的类型。
编译器要么通过传入参数判断,要么通过返回类型来判断。
当然,在泛型方法中,我们也可以显式地指明类型。显式类型参数说明:必须在点操作符与方法名之间插入尖括号,然后把类型置于尖括号内。在定义该方法的类的内部,如果是static方法,点操作符之前加上类名;否则在点操作符之前加上this。
擦除的出现是:为了原来的类库兼容才使用了擦除,将会把类型参数T换成Object。
我们可以声明ArrayList.class,但是不可以声明ArrayList<String>.class。
public class ErasedTypeEquivalence {public static void main(String[] args) {Class c1 = new ArrayList<String>().getClass();Class c2 = new ArrayList<Integer>().getClass();System.out.println(c1 == c2);}} /* Output:true*///:~
ArrayList<String>和ArrayList<Integer>很容易认为是不同的类型,实际上却是同样的类型,为什么会这样呢?
import java.util.*;class Frob {}class Fnorkle {}class Quark<Q> {}class Particle<POSITION,MOMENTUM> {}public class LostInformation {// 泛型声明所声明的类型参数public static void show(Class clazz) {System.out.println(Arrays.toString(clazz.getTypeParameters()));}public static void main(String[] args) {List<Frob> list = new ArrayList<Frob>();Map<Frob, Fnorkle> map = new HashMap<Frob, Fnorkle>();Quark<Fnorkle> quark = new Quark<Fnorkle>();Particle<Long, Double> p = new Particle<Long, Double>();show(list.getClass());show(map.getClass());show(quark.getClass());show(p.getClass());}} /* Output:[E][K, V][Q][POSITION, MOMENTUM]*///:~
从输出中看到,只是用作参数占位符的标识符,并非有用的信息。ArrayList<String>和ArrayList<Integer>都被擦除成它们的“原生类型”,即ArrayList。
在泛型代码内部,无法获得任何有关泛型参数类型的信息,把类型参数当作一个
Object来使用。
class Foo<T> {T var; // 把这个参数化类型当作一个Object}
在泛型代码内部,不能用于显式地引用运行时类型的操作之中,例如转型、instanceof操作和new表达式。因为所有关于参数的类型信息已经被擦除了,它只是一个Object。
public class Erased {public static <T> void f(Object arg) {// Cannot perform instanceof check against type parameter T.// Use its erasure Object instead since further generic type information will be erased at runtime// ! if (arg instanceof T) { }// ! T var = new T(); // Cannot instantiate the type T// ! T[] array = new T[5]; // Cannot create a generic array of TT[] array = (T[])new Object[5]; // Unchecked cast from Object[] to T[]}}
在泛型代码内部,任何在运行时需要知道确切类型信息的操作都将无法工作,因为其类型信息已经被擦除了。不过我们可以类型标记Class<T>传入到泛型代码中,以便从擦除中恢复。因为类型标记是创建该类任何对象的蓝图,包含该类所有的信息,所以类型标记Class对象又称为最便利的工厂对象。
在编译时,会存储元素会进行类型检查;在运行时,通过获取元素的类型进行自动转型动作。
由于获取不到类型信息,所以不能通过new T()来实现。可以通过使用类型标记的newInstance()调用默认的构造器来创建这个类型的新对象。
class Employee {}public class Erased {public static <T> T create(Class<T> kind) {// ! T var = new T();try {T var = kind.newInstance();return var;} catch (Exception e) {throw new RuntimeException(e);}}public static void main(String[] args) {Employee e = create(Employee.class); // OK// ! Integer i = create(Integer.class); // RuntimeException}} /* Output:Exception in thread "main" java.lang.RuntimeException: java.lang.InstantiationException: java.lang.Integerat Erased.create(Erased.java:9)at Erased.main(Erased.java:14)Caused by: java.lang.InstantiationException: java.lang.Integerat java.lang.Class.newInstance(Class.java:359)at Erased.create(Erased.java:6)... 1 more*///:~
上面这段代码可以通过编译,但是无法创建Integer的实例,因为Integer没有任何默认的构造器。由于这个错误是运行期异常,所以Sun的伙伴们对这种方法并不赞成,他们建议使用显式的工厂,并将限制其类型,使得只能接受实现了这个工厂的类:
interface FactoryI<T> { T create(); } // 一个额外的功能,工厂用于获取实例对象class IntegerFactory implements FactoryI<Integer>{@Overridepublic Integer create() {return 0; // 自动包装}}class Widget{}class WidgetFactory implements FactoryI<Widget>{@Overridepublic Widget create() {return new Widget();}}class Foo<T>{private T t;public <F extends FactoryI<T>> Foo(F factory){ // extends用来确定上限t = factory.create(); // 在泛型代码内,使用工厂来获取参数化类型的对象}}public class FactoryConstraint {public static void main(String[] args) {new Foo<>(new IntegerFactory()); // OKnew Foo<>(new WidgetFactory()); // OK}} ///:~
当然,我们也可以使用模版方法设计模式。在泛型类中,我们仅仅定义好一个基本框架,将具体的实现延迟到子类。
abstract class TemplateCreate<T>{final T element;public TemplateCreate() {this.element = create();}public abstract T create(); // 后期绑定的方法void show(){ System.out.println(element.getClass().getSimpleName()); }}class X { }class XCreate extends TemplateCreate<X>{@Overridepublic X create() { return new X(); } // 实现模版方法}class IntegerCreate extends TemplateCreate<Integer>{@Overridepublic Integer create() { return 1; }}public class Template {public static void main(String[] args) {new XCreate().show();new IntegerCreate().show();}} /* Output:XInteger*///:~
在泛型代码内部,可以使用类型标记的isInstance()来代替instanceof。
class Building {}class House extends Building {}public class ClassTypeCapture<T> {Class<T> kind;public ClassTypeCapture(Class<T> kind) {this.kind = kind;}public boolean check(Object arg) {return kind.isInstance(arg);}public static void main(String[] args) {ClassTypeCapture<Building> ctt1 = new ClassTypeCapture<Building>(Building.class);System.out.println(ctt1.check(new Building()));System.out.println(ctt1.check(new House()));ClassTypeCapture<House> ctt2 = new ClassTypeCapture<House>(House.class);System.out.println(ctt2.check(new Building()));System.out.println(ctt2.check(new House()));}} /* Output:truetruefalsetrue*///:~
正如之前所看到的// ! T var = new T();代码,我们无法创建泛型数组。一般的解决方案是在任何想要创建泛型数组的地方都使用ArrayList。
我们知道数组也是一个对象,数组被创建时就已经确定了它们的实际类型。强制将Object[]向下转型会出现ClassCastException,这是一个运行期异常。
class Generic<T> { } // 泛型类public class ArrayOfGeneric {static final int SIZE = 100;static Generic<Integer>[] gia;@SuppressWarnings("unchecked")public static void main(String[] args) {// ClassCastException: [Ljava.lang.Object; cannot be cast to [Lgenerics.Generic;// ! gia = (Generic<Integer>[])new Object[SIZE];gia = (Generic<Integer>[]) new Generic[SIZE];System.out.println(gia.getClass().getSimpleName());gia[0] = new Generic<Integer>();// ! gia[1] = new Object(); // Compile-time error// Discovers type mismatch at compile time:// ! gia[2] = new Generic<Double>();}} /* Output:Generic[]*///:~
成功创建泛型类数组的唯一方式就是创建一个被擦除类型的新数组,然后对其转型。
正如之前所看到的T[] array = (T[])new Object[5];代码,我们能够在泛型代码内创建一个对象数组,然后将其转型,这样做仅会获得编译期警告。
public class GenericArray<T> {private T[] array;@SuppressWarnings("unchecked")public GenericArray(int sz) {array = (T[]) new Object[sz];}public T[] rep() { return array; }public static void main(String[] args) {GenericArray<Integer> gai = new GenericArray<Integer>(10);// This causes a ClassCastException:// [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer;// ! Integer[] ia = gai.rep(); // 实际的运行类型是Object[]Object[] oa = gai.rep(); // OK}}
当我们在泛型代码内部,将Object[]转型为T[],那么在编译期该数组的实际类型就将丢失,导致编译器可能会错过某些潜在的错误检查。为了解决这个问题,我们泛型代码内就使用Object[],然后当我们使用数组元素时,添加一个对T的转型。
public class GenericArray2<T> {private Object[] array;public GenericArray2(int sz) {array = new Object[sz];}public void put(int index, T item) {array[index] = item;}@SuppressWarnings("unchecked")// 使用数组元素时进行转型// 也许有人会想,T被擦除了为Object了,那Object转Object还有意义吗?这样转型的目的是为了编译期不报错。public T get(int index) {return (T) array[index];}@SuppressWarnings("unchecked")public T[] rep() {return (T[]) array; // Warning: Unchecked cast}public static void main(String[] args) {GenericArray2<Integer> gai = new GenericArray2<Integer>(10);for (int i = 0; i < 10; i++)gai.put(i, i);for (int i = 0; i < 10; i++)System.out.print(gai.get(i) + " ");System.out.println();try {Integer[] ia = gai.rep(); // 还是产生了异常} catch (Exception e) {System.out.println(e);}}} /* Output:0 1 2 3 4 5 6 7 8 9java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer;*///:~
直到现在,我们仍然不能获取到参数化类型数组,它们始终是Object[]。因此,没有任何一种方式可以推翻底层的数组类型!所以我们应该传递一个类型标记创建参数化类型数组。
java.lang.reflect.Array.newInstance(Class<?> componentType, int length)
import java.lang.reflect.Array;import java.util.Arrays;public class GenericArrayWithTypeToken<T> {private T[] array;@SuppressWarnings("unchecked")public GenericArrayWithTypeToken(Class<T> type, int sz) {array = (T[]) Array.newInstance(type, sz);}public void put(int index, T item) {array[index] = item;}public T get(int index) { return array[index]; }public T[] rep() { return array; }public static void main(String[] args) {GenericArrayWithTypeToken<Integer> gai = new GenericArrayWithTypeToken<Integer>(Integer.class, 10);gai.put(0, 1); // 自动装箱Integer[] ia = gai.rep(); // OKSystem.out.println(Arrays.toString(ia));}} /* Output:[1, null, null, null, null, null, null, null, null, null]*///:~