Java泛型(generics)是JDK 5中引入的一个新特性,允许在定义类和接口的时候使用类型参数(type parameter)。声明的类型参数在使用时用具体的类型来替换。泛型最主要的应用是在JDK 5中的新集合类框架中。对于泛型概念的引入,开发社区的观点是褒贬不一。从好的方面来说,泛型的引入可以解决之前的集合类框架在使用过程中通常会出现的运行时刻类型错误,因为编译器可以在编译时刻就发现很多明显的错误。而从不好的地方来说,为了保证与旧有版本的兼容性,Java泛型的实现上存在着一些不够优雅的地方。当然这也是任何有历史的编程语言所需要承担的历史包袱。后续的版本更新会为早期的设计缺陷所累。
开发人员在使用泛型的时候,很容易根据自己的直觉而犯一些错误。比如一个方法如果接收List<Object>作为形式参数,那么如果尝试将一个List<String>的对象作为实际参数传进去,却发现无法通过编译。虽然从直觉上来说,Object是String的父类,这种类型转换应该是合理的。但是实际上这会产生隐含的类型转换问题,因此编译器直接就禁止这样的行为。
泛型给我们带来了哪些好处?
泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。
泛型(Generic type 或者 generics)是对 Java 语言的类型系统的一种扩展,以支持创建可以按类型进行参数化的类。可以把类型参数看作是使用参数化类型时指定的类型的一个占位符,就像方法的形式参数是运行时传递的值的占位符一样。
可以在集合框架(Collection framework)中看到泛型的动机。例如,Map 类允许您向一个 Map 添加任意类的对象,即使最常见的情况是在给定映射(map)中保存某个特定类型(比如 String)的对象。
因为 Map.get() 被定义为返回 Object,所以一般必须将 Map.get() 的结果强制类型转换为期望的类型,如下面的代码所示:
Map m = new HashMap();
m.put("key", "blarg");
String s = (String) m.get("key");
要让程序通过编译,必须将 get() 的结果强制类型转换为 String,并且希望结果真的是一个 String。但是有可能某人已经在该映射中保存了不是 String 的东西,这样的话,上面的代码将会抛出 ClassCastException。
理想情况下,您可能会得出这样一个观点,即 m 是一个 Map,它将 String 键映射到 String 值。这可以让您消除代码中的强制类型转换,同时获得一个附加的类型检查层,该检查层可以防止有人将错误类型的键或值保存在集合中。这就是泛型所做的工作。
泛型的好处
Java 语言中引入泛型是一个较大的功能增强。不仅语言、类型系统和编译器有了较大的变化,以支持泛型,而且类库也进行了大翻修,所以许多重要的类,比如集合框架,都已经成为泛型化的了。
这带来了很多好处:
1,类型安全。 泛型的主要目标是提高 Java 程序的类型安全。通过知道使用泛型定义的变量的类型限制,编译器可以在一个高得多的程度上验证类型假设。没有泛型,这些假设就只存在于程序员的头脑中(或者如果幸运的话,还存在于代码注释中)。
2,消除强制类型转换。 泛型的一个附带好处是,消除源代码中的许多强制类型转换。这使得代码更加可读,并且减少了出错机会。
3,潜在的性能收益。 泛型为较大的优化带来可能。在泛型的初始实现中,编译器将强制类型转换(没有泛型的话,程序员会指定这些强制类型转换)插入生成的字节码中。但是更多类型信息可用于编译器这一事实,为未来版本的 JVM 的优化带来可能。由于泛型的实现方式,支持泛型(几乎)不需要 JVM 或类文件更改。所有工作都在编译器中完成,编译器生成类似于没有泛型(和强制类型转换)时所写的代码,只是更能确保类型安全而已。
4,代码复用。
//泛型类,因为要用的比较所以必须实现了比较接口才可以 class A<T extends Comparable> { T obj; public A() {} public A(T obj) { this.obj = obj; } public void show() { System.out.println(obj.getClass().getName()); } //泛型方法 public T max(T a, T b) { if (a.compareTo(b) > 0) return a; else return b; } //静态的泛型方法不依赖于类的类型参数 public static <E extends Comparable> E min(E a, E b) { if (a.compareTo(b) > 0) return b; else return a; } //这样是错误的 // public static T min(T a, T b) { // if (a.compareTo(b) > 0) return b; // else return a; // } } public class Test { public static void main(String[] args) { A<Integer> a = new A<Integer>(); System.out.println(a.max(new Integer(4), new Integer(78))); A<String> a2 = new A<String>(new String("Hello!")); a2.show(); System.out.println(a2.max(new String("abcd"), new String("abde"))); System.out.println(A.min(new Integer(45), new Integer(90))); } }
输出:
78
java.lang.String
abde
45
擦除
Java泛型是使用擦除来实现的,这意味这当你在使用泛型的时候,任何具体的类型信息都被擦除了, 你唯一知道的就是你在使用一个对象。
import java.util.ArrayList; public class Test { public static void main(String[] args) { Class c1 = new ArrayList<String>().getClass(); Class c2 = new ArrayList<Integer>().getClass(); System.out.println((c1 == c2)); } } 输出: true
这里由于擦除的原因,List<String>和List<Integer>在运行时事实上是相同类型。 这两种形式都被擦成他们的“原生”类型,即List。
例如:
C++我们可以这样写, 因为当实例化这个这个模板的时候,C++编译器将进行检查,因此我们可以对他们进行比较 #include <iostream> #include <cstdio> using namespace std; template<typename T> T imax(T a, T b) { if (a > b) return a; else return b; } int main() { printf("%d ",imax(4,9)); return 0; }
而在我们上边的那个max函数,就不能单纯这样写,因为他们的具体的类型信息都别擦除我们不知道他们能否进行比较,所以我们通过限制反省的边界来达到我们的比较的目的。
话说Java的泛型使用起来好蛋疼, 不如C++灵活。