为什么需要泛型?
一般的类和方法,只能使用具体的类型:要么是基本类型,要么是自定义的类。
如果要编写可以应用于多种类型的代码,这种刻板的限制对代码的束缚就会很大。
在面向对象编程语言中,多态算是一种泛化机制。例如,你可以将方法的参数类型设置为基类,那么该方法就可以接受从这个基类中导出的任何类作为参数。如果方法的参数是一个接口,那么更方便。
我们希望达到的目的是编写更通用的代码,要使代码能够应用于“某种不具体的类型”,而不是一个具体的接口或类。
泛型将类或方法与所使用的类型之间的约束关系进行了解耦。
有许多原因促成了泛型的出现,而最引人注目的一个原因,就是为了创造容器类。
容器,就是存放要使用的对象的地方。事实上,所有的程序,在运行时都要求你持有一大堆对象。
泛型用来指定容器要持有什么类型的对象,并且由编译器来保证类型的正确性。
Java泛型的核心概念就是:告诉编译器想使用什么类型,然后编译器帮你处理一切细节。
泛型类--元组类库
需求:仅一个方法调用就能返回多个对象:
问题:return语句只允许返回单个对象
解决方法:创建一个对象,用它来持有想要返回的多个对象
元组tuple
它是将一组对象直接打包存储于其中的一个单一对象。这个容器对象允许读取其中元素,但是不允许向其中存放新的对象。public class TwoTuple<A, B> {
public final A first;
public final B second;
public TwoTuple(A a, B b) {
first = a;
second = b;
}
public String toString() {
return "(" + first + ", " + second + ")";
}
}
泛型接口
泛型用于接口。比如生成器generator,这是一种专门负责创建对象的类。实际上,这是工厂方法设计模式的一种应用。不过,当使用生成器创建新的对象时,它不需要任何参数,而工厂方法一般需要参数。public interface Generator<T> { T next(); }
public class CoffeeGenerator implements Generator<Coffee>, Iterable<Coffee> {…}
Java泛型的一个局限性:基本类型无法作为类型参数
泛型方法
泛型方法使得该方法能够独立于类而产生变化。以下是一个基本的指导原则:
无论何时,只要你能做到,你就应该尽量使用泛型方法。
对于一个static的方法而言,无法访问泛型类的类型参数。所以,如果static方法需要使用泛型能力,就必须使其成为泛型方法。
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.f(gm);
}
}
综合上面所讲的,使用Java已经构建好的泛型类型会比较容易
但是如果你要自己创建一个泛型实例,就会遇到许多令你吃惊的事情
擦除
Class类有方法getTypeParameters(),将“返回一个TypeVariable对象数组,表示有泛型声明所声明的类型参数”。这好像是在暗示你可能发现参数类型的信息,但是实际上,你能够发现的只是用作参数占位符的标识符,并非有用的信息。 TypeVariable<Class<T>>[] | getTypeParameters() |
结论是:在泛型代码内部,无法获得任何有关泛型参数类型的信息。
理解擦除以及应该如何处理它,是你在学习Java泛型时面临的最大障碍。
要记住:泛型类型只有在编译期类型检查期间才出现,在此之后,程序中的所有泛型类型都将被擦除,替换为它们的非泛型上界。
泛型是一种折中。要支持之前的非泛型代码,允许非泛型代码和泛型代码共存。为了实现这种迁移兼容性,每个类库和应用程序都必须与其他所有的部分是否使用了泛型无关。这样,它们必须不具备探测其他类库是否使用了泛型的能力。因此,某个特定的类库使用了泛型这样的证据必须被“擦除”。
擦除带来的问题就是泛型不能用于显式地引用运行时的类型的操作之中,例如转型、instanceof操作和new表达式。
擦除的补偿
擦除丢失了在泛型代码中执行某些操作的能力,任何在运行时需要知道确切类型信息的操作都将无法工作。通过引入类型标签,显式地传递你的类型的Class对象,对擦除进行补偿,以便你可以在类型表达式中使用它。
class Building {
}
class House extends Building {
}
public class ClassTypeCapture<T> {
Class<T> kind;
public ClassTypeCapture(Class<T> kind) {
this.kind = kind;
}
public boolean f(Object arg) {
//类.isInstance(对象)
return kind.isInstance(arg);
}
public static void main(String[] args) {
ClassTypeCapture<Building> ctt1 = new ClassTypeCapture<Building>(
Building.class);
System.out.println(ctt1.f(new Building()));
System.out.println(ctt1.f(new House()));
ClassTypeCapture<House> ctt2 = new ClassTypeCapture<House>(House.class);
System.out.println(ctt2.f(new Building()));
System.out.println(ctt2.f(new House()));
}
} /*
* Output: true true false true
*/// :~
也可参考:使用泛型反射实现BaseDao
边界
给定泛型的边界,以此告知编译期只能接受遵循这个边界的类型extends super
总结
1. 泛型是JDK1.5以后才有的,可以在编译时期进行类型检查,将类和方法与所使用的类型之间的约束关系进行了解耦,从而可以编写更通用的代码。
2. 泛型只在编译时期有效,编译后的字节码文件中不存在有泛型信息!如果需要在运行时使用类型信息,需要显式地传递你的类型的Class对象来对泛型进行补偿。