Java泛型(超详细介绍)
一、泛型
什么是泛型?为什么要使用泛型?
泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参列表,普通方法的形参列表中,每个形参的数据类型是确定的,而变量是一个参数。在调用普通方法时需要传入对应形参数据类型的变量(实参),若传入的实参与形参定义的数据类型不匹配,则会报错。
那参数化类型是什么?
以方法的定义为例,在方法定义时,将方法签名中的形参的数据类型也设置为参数(也可称之为类型参数),在调用该方法时再从外部传入一个具体的数据类型和变量。
泛型的本质是为了将类型参数化, 也就是说在泛型使用过程中,数据类型被设置为一个参数,在使用时再从外部传入一个数据类型;而一旦传入了具体的数据类型后,传入变量(实参)的数据类型如果不匹配,编译器就会直接报错。
这种参数化类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
1.为什么要使用泛型,看下面程序存在的缺陷?
User类:
public class User {
private String name;
public User(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void pay(){
System.out.println(this.name + "正在支付。。。");
}
}
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
/**
* 当前程序先不使用泛型,分析存在什么缺点?
* 不好看,代码写的比较多。每一次从集合中取出的元素要想访问子类中特有的方法,必须向下转型。
* 大部分都是要写向下转型的。因为Object类中的方法肯定是不够用的。一定会调用子类方法。
*/
public class GenericTest01 {
public static void main(String[] args) {
// 创建集合
Collection c = new ArrayList();
// 创建User类型的对象
User u1 = new User("张三");
User u2 = new User("李四");
User u3 = new User("王五");
// 添加到集合中
c.add(u1);
c.add(u2);
c.add(u3);
// 遍历集合
Iterator it = c.iterator();
while(it.hasNext()){
Object obj = it.next();
if(obj instanceof User) {
// 支付
// 这里没有使用泛型机制,那么要想调用pay()方法,必须进行向下转型
User user = (User) obj;
user.pay();
}
}
}
}
2.泛型
User类:
public class User {
private String name;
public User(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void pay(){
System.out.println(this.name + "正在支付。。。");
}
}
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
/**
* 使用泛型机制。
*/
public class GenericTest02 {
public static void main(String[] args) {
// 程序编写的时候,是否可以使用泛型,看哪里?
// 看帮助文档中有没有“<>”符号。
// 有这个符号的都可以使用泛型。
// 创建一个集合,要求这个集合中只能存放User类型的对象。不能存储其他类型。
//Collection<User> users = new ArrayList<User>();
Collection<User> users = new ArrayList<>();
// 向集合中添加User对象
User u1 = new User("张三");
User u2 = new User("李四");
User u3 = new User("王五");
users.add(u1);
users.add(u2);
users.add(u3);
// 编译器报错,不能添加其他类型,只能添加User类型。
//users.add("abc");
// 遍历集合
Iterator<User> it = users.iterator();
while(it.hasNext()){
User user = it.next();
user.pay();
}
}
}
运行结果:
3.Java7的新特性:钻石表达式
< >里面的对象,写前面一个就行
Collection<String> strs = new ArrayList<>();
4.泛型的擦除与补偿(了解)
泛型的作用
编译时类型安全:
在编译阶段检查集合元素的类型,避免运行时出现
ClassCastException
。代码可读性:
明确集合中元素的类型,减少类型转换代码。
⑴.泛型擦除(Type Erasure)(子类——>Object)
机制: 编译后擦除:泛型信息在编译后被移除,替换为 Object 类型(或泛型上限类型)。
示例:
List<String> list = new ArrayList<>();
// 编译后变为 List<Object>
原因:其本质是为了让JDK1.4和JDK1.5能够兼容同一个类加载器。在JDK1.5版本中,程序编译时期会对集合添加的元素进行安全检查,如果检查完是安全的、没有错误的,那么就意味着添加的元素都属于同一种数据类型,则加载类时就可以把这个泛型擦除掉,将泛型擦除后的类型就是Object类,这样擦除之后的代码就与JDK1.4的代码一致。
运行时无关性:泛型是编译时技术,JVM 不感知泛型。
由于加载类的时候,会默认将类中的泛型擦除为Object类型,所以添加的元素就被转化为Object类型,同时取出的元素也默认为Object类型。而我们获得集合中的元素时,按理说取出的元素应该是Object类型,为什么取出的元素却是实际添加的元素类型呢?
⑵.泛型补偿(Type Compensation)(Object——>子类)
机制: 运行时自动强转:从集合中取出元素时,JVM 根据元素的实际类型隐式执行强制类型转换。
示例:
List<String> list = new ArrayList<>();
list.add("hello"); // 编译后变为 List<Object>
String s = list.get(0); // 自动转换为 String,无需手动强转
这里又做了一个默认的操作,我们称之为泛型的补偿。在程序运行时,通过获取元素的实际类型进行强转,这就叫做泛型补偿(不必手动实现强制转换)。
获得集合中的元素时,虚拟机会根据获得元素的实际类型进行向下转型(父转子),也就是会恢复获得元素的实际类型,因此我们就无需手动执行向下转型操作,从本质上避免了抛出类型转换异常。
意义:
开发者透明:无需手动写类型转换代码。
避免类型错误:编译时已确保类型安全,运行时不会抛出 ClassCastException。
阶段 | 泛型擦除 | 泛型补偿 |
---|---|---|
时间 | 编译时 | 运行时 |
操作 | 泛型信息替换为 Object |
隐式执行 Object → 实际类型 的转换 |
目的 | 兼容旧版本,简化 JVM 实现 | 恢复实际类型,保证代码逻辑正确性 |
5.泛型的使用:在类上定义泛型
⑴.泛型类基本概念
作用:让类可以处理多种数据类型,同时保