注:以下内容基于Java 8,所有代码都已在Java 8环境下测试通过
目录:
1. 泛型是什么
泛型,即类型参数,类似于函数中的参数,泛型可以将类型参数化。这句话过于抽象,为了便于解释,下面根据个人理解将泛型与函数中的参数做个对比。
从简单的人手,首先说一下函数参数经历的两个阶段:
-
函数定义
在定义函数时,只知道参数的类型而不知道具体的值(此时的参数叫做形参)。此时,为了定义函数在参数上的操作,我们需要给参数取个名字,比如下面这个函数:
private void testF(int a, int b) { System.out.println(a); System.out.println(b); }
在这个函数定义中,只知道有两个
int
类型的参数且名字分别叫a、b,但具体的值此时尚不知道。 -
函数调用
在调用函数时,我们需要按照函数的要求传入两个
int
类型的变量://调用函数 testF(1. 2);
从上面可以看出来,对于函数参数而言:函数在定义时知道参数的类型和名字,但不知道具体的值,在函数调用时才知道具体的值是多少。而泛型更进一步,以泛型方法为例,在定义一个泛型方法时,仅仅知道参数的名字,而参数的类型和具体的值只有在调用函数的时候才能确定!
另外,与函数参数不同的是,类、接口、函数中都可以加入泛型机制,即为类、接口、函数指定类型参数,分别被叫做泛型类、泛型接口、泛型函数。
2. 泛型的作用
根据《On Java 8》的描述:“泛型的主要目的之一就是用来约定集合要存储什么类型的对象,并且通过编译器确保规约得以满足”。
Talk is cheap,let me show you the code,假设我们现在有个很简单的需求:需要用一个集合存放String类型的变量,并将这个集合中的内容打印输出。使用泛型可以这样写:
2.1 通过泛型实现
import java.util.LinkedList;
public class Main {
public static void main(String[] args) {
LinkedList<String> stringList = new LinkedList<>(); //<String> 是一个泛型
stringList.add("abc");
stringList.add("def");
//stringList.add(1); //当添加一个非 String 类型的变量时,编译器会报错
for (int i = 0; i < stringList.size(); i++) {
System.out.println(stringList.get(i));
}
}
}
在上面的代码中:
<String>
是一个泛型,它限制了stringList
中只能存放String
类型的变量(约定集合要存储什么类型的对象)stringList.add(1);
,由于泛型的存在,当stringList
试图添加一个非String类型的变量时,编译器会报错(通过编译器确保规约(也就是只能存放String类型变量)得以满足)
第一点实际展示了如何使用泛型,这再后面还会再详细介绍,先具体说一下第二点:如何理解通过编译器确保规约得以满足?在Java中,Object类是所有类的超类,通过向下转型,下面这段代码可以实现和上面类似的功能:
2.2 通过Object和转型实现
import java.util.LinkedList;
public class Main {
public static void main(String[] args) {
LinkedList stringList = new LinkedList(); //未使用泛型机制,此时可以放入任何类型的变量
stringList.add("abc");
stringList.add("def");
stringList.add(1); //可以添加非 String 类型的变量,编译器不会报错
for (int i = 0; i < stringList.size(); i++) {
System.out.println((String) stringList.get(i));
}
}
}
上面这段代码在定义stringList
时未使用泛型机制,因此可以添加任意类型的变量,即使添加的变量类型不同编译器也不会报错。但!!!在执行代码时会出现错误:
这个错误是由于我们错误的将Integer
类型强制转换为String
类型而引起的。
当然,这个报错我们可以通过反射机制避免,但这种错误处理方式太过繁琐,而且会大大降低代码的复用性(若通过泛型,可以很轻松的将功能改为存放并输出Integer
类型对象,而使用泛型则需要添加新的判断逻辑)。
另外,我们不能寄希望于其他人(甚至自己)会一直按照规定调用代码。因此,最好的方式就是尽可能的在程序编译阶段就将问题暴露出来,而不是等用户真正使用产品的时候再去修改本不该出现的bug。而泛型就有这个能力:通过编译器确保规约得以满足!
3. 小结
在这篇博客里,简单概述了一下泛型,接下来将分几篇博客分别介绍泛型类、泛型接口、泛型方法等,随着使用的逐渐深入,对泛型将会有更多的了解。