我一直都认为泛型是程序语言设计中一个非常基础,重要的概念,Java 中的泛型到底是怎么样的,为什么会有泛型,泛型怎么发展出来的。通透理解泛型是学好基础里面中非常重要的。于是,我对《Java编程思想》这本书中泛型章节进行了研读。可惜遗憾的是,自己没有太多的经验,有些东西看了几次也是有点懵。只能以后有机会,再进行学习了。但是自己也理解了挺多的。下面就是自己对于泛型的理解与感悟。如有不对,望指出。
概念由来: Java 一开始设计之初是没有泛型这个特性的,直到jdk 1.5中引入了这个特性。Java 的泛型是由擦除来实现的。要知道擦除是什么?往下看。
概念:一般的类和方法,只能使用具体的类型;要么是基本类型,要么是自定义的类。如果要编写可以应用于多种类型的代码,这种刻板的限制对代码的束缚就会很大。泛型实现了参数化类型的概念,使代码应用于多个类型。泛型在编程语言中出现时,其最初的目的是希望类和方法具有广泛的表达能力。
简单泛型 有很多原因促成泛型的出现,其中最重要的一个原因就是为了创造容器类。我们暂时不指定类型,而是稍后再决定具体使用什么类型。要达到这个目的,需要使用类型参数,用尖括号括住,放在类名的后面。然后使用这个类的时候,再用实际的类型替换此类型参数。在下面例子中,T就是类型参数。代码如下:
public class Holder<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<String> h = new Holder<>(); h3.set("hello"); String a = h3.get(); } } class Automobile{} 但是往往很多的源码中一些通用的类都是有多个泛型参数,譬如 java.util.function.BiFunction 中就有三个类型参数 T,U,R 。
接口泛型 泛型在接口上的运用是非常多的,例如迭代器(Iterable)中的 Iterator 。
public interface Itreator<T>{ // 判断是否还有元素 boolean hasNext(); // 洗一个元素 E next(); .... } 这个是我们都很常用的吧,其实接口使用泛型和类使用泛型没什么区别。
泛型方法 泛型方法,在返回参数类型前面添加泛型参数列表,由<>括着
eg:
泛型方法使得该方法独立于类而产生变化。在需要编写泛型代码的时候,基本的的指导原则是:无论何时,只要你能做到,就尽量使用泛型方法。意思是如果使用泛型方法可以代替整个类的泛型化,那就用泛型方法,因为它可以使事情更加清楚明白。另外对于static的方法而言,无法访问泛型类的类型参数,所以如果static方法需要使用泛化能力,就必须使其成为泛型方法。
泛型的擦除 在看 《Java编程思想》 中泛型章节中 ’擦除的神秘之处‘ 这一小节的时候,看的我特别的晕晕乎乎的,然后再往下面看时就越来越混了。 特别是看到’边界‘,’通配符‘ 这块了就有点懵了。首先看下什么是擦除。在泛型代码内部,无法获得有关泛型参数类型的信息。 Java 的泛型是由擦拭来实现的,这意味着当你使用泛型的时,任何具体的类型都被擦除了,你唯一知道的就是你在使用一个对象。由于 Java 一开始没有引入 泛型这个特性,在为了兼容 JDK 老版本的情况下。擦除是 Java 泛型实现的一种折中。 因此你在运行时 List 和 List 是一样的,注意是在运行时,但是在编译时,List 表示这个 String 类型的 List 容器, List 表示这个时 Integer 类型的List容器。 举个例子,例子来源于 Java编程思想
#include <iostream> using namespace std; temple<class T> class Manipulator{ T obj; public : Manipulator(T x){obj = x;} void manipylate() {obj.f();} }; class HasF{ public: void f(){cout<<"HasF::f()"<< endl;} }; int main(){ HasF hf; Manipulator<HasF> manipulator(hf); manipulator.manipylate(); }输出 HasF:f()
Manipulator 类存储了一个类型T的对象,在 manipylate 方法里,他在 obj上调用了方法 f() ; 它是怎么知道 f() 是类型参数T 中有的方法呢? 当你实例化这个模板的时,c++编译器将进行检查,如果他在 Manipulator 被实例化的这刻,它看到HasF如果拥有这个方法 f(), 就编译通过,否则不通过。 这个代码就算不会 c++ 的人应该也看的懂吧。我们看下 Java 的版本:
public class HasF{ public void f(){ System.out.println("HasF::f()"); } } class Manipulator<T>{ private T obj; public Manipulator(T obj){ this.obj = obj; } public void manipulator(){ //错误: 这个是不可以调用 f() 这个方法的。 // obj.f(); } public static void main(String[] args){ HasF hf = new HasF(); Manipulator<HasF> manipulator = new Manipulator<>(hf); manipulator.manipulator(); } }看到没有,Java 中有了擦除, Java 编译器不知道 obj 中有没有 f() 这个方法的事情。
泛型的边界 T extends Class