持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第2天,点击查看活动详情
泛型的引入
例如:生产瓶子的厂家,一开始并不知道将来会用瓶子装什么,什么都可以装,但是有的时候,在使用时,想要限定某个瓶子只能用来装什么,这样不会装错,而用的时候也可以放心的使用,无需再三思量;生活中是在使用这个瓶子时在瓶子上“贴标签” ,这样就轻松解决了问题
还有,在 Java 中在声明方法时,当在完成方法功能时如果有未知的数据需要参与,这些未知的数据需要在调用方法时才能确定,那么把这样的数据通过形参表示
那么在方法体中,用这个形参名来代表那个未知的数据,而调用者在调用时,对应的传入值就可以了
- 受以上两点启发,JDK1.5 设计了泛型的概念泛型即为“类型参数”,这个类型参数在声明它的类、接口或方法中,代表未知的通用的类型
- 例如:Java.lang.Comparable 接口和 Java.util.Comparator 接口,是用于对象比较大小的规范接口,这两个接口只是限定了当一个对象大于另一个对象时返回正整数,小于返回负整数,等于返回0
- 但是并不确定是什么类型的对象比较大小,之前的时候只能用 Object 类型表示,使用时既麻烦又不安全,因此 JDK1.5 就给它们增加了泛型
public interface Comparable<T>{
int compareTo(T o) ;
}
public interface Comparator<T>{
int compare(T o1, T o2) ;
}
复制代码
其中就是类型参数,即泛型
泛型的好处
Java Bean
圆类型
class Circle{
private double radius;
public Circle(double radius) {
super();
this.radius = radius;
}
public double getRadius() {
return radius;
}
public void setRadius(double radius) {
this.radius = radius;
}
@Override
public String toString() {
return "Circle [radius=" + radius + "]";
}
}
复制代码
比较器
import java.util.Comparator;
public class CircleComparator implements Comparator{
@Override
public int compare(Object o1, Object o2) {
//强制类型转换
Circle c1 = (Circle) o1;
Circle c2 = (Circle) o2;
return Double.compare(c1.getRadius(), c2.getRadius());
}
}
复制代码
测试类
public class TestGeneric {
public static void main(String[] args) {
CircleComparator com = new CircleComparator();
System.out.println(com.compare(new Circle(1), new Circle(2)));
System.out.println(com.compare("圆1", "圆2"));//运行时异常:ClassCastException
}
}
复制代码
- 那么在使用如上面这样的接口时,如果没有泛型或不指定泛型,很麻烦,而且有安全隐患
- 因为在设计(编译)Comparator 接口时,不知道它会用于哪种类型的对象比较,因此只能将 compare 方法的形参设计为 Object 类型,而实际在 compare 方法中需要向下转型为 Circle,才能调用 Circle 类的 getRadius() 获取半径值进行比较
使用泛型
比较器
class CircleComparator implements Comparator<Circle>{
@Override
public int compare(Circle o1, Circle o2) {
//不再需要强制类型转换,代码更简洁
return Double.compare(o1.getRadius(), o2.getRadius());
}
}
复制代码
测试类
import java.util.Comparator;
public class TestGeneric {
public static void main(String[] args) {
CircleComparator com = new CircleComparator();
System.out.println(com.compare(new Circle(1), new Circle(2)));
// System.out.println(com.compare("圆1", "圆2"));//编译错误,因为"圆1", "圆2"不是Circle类型,是String类型,编译器提前报错,而不是冒着风险在运行时再报错
}
}
复制代码
- 如果使用泛型,那么既能保证安全,又能简化代码
- 因为把不安全的因素在编译期间就排除了
- 既然通过了编译,那么类型一定是符合要求的,就避免了类型转换
泛型的相关名词
<类型>
这种语法形式就叫泛型<T>
是类型变量(Type Variables),而<T>
是代表未知的数据类型,可以指定为<String>``<Integer>``<Circle>
等,那么<类型>
的形式称为类型参数;- 类比方法的参数的概念,可以把
<T>
称为类型形参,<Circle>
称为类型实参
自从有了泛型之后, Java 的数据类型就更丰富了
Class:Class
类的实例表示正在运行的 Java 应用程序中的类和接口
- 枚举是一种类,注释是一种接口
- 每个数组属于被映射为 Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该
Class
对象 - 基本的 Java 类型(
boolean
、byte
、char
、short
、int
、long
、float
和double
)和关键字void
也表示为Class
对象
GenericArrayType:泛化的数组类型,即T[]
ParameterizedType:参数化类型,例如:Comparator,Comparator
TypeVariable:类型变量,例如:Comparator 中的 T,Map<K,V> 中的 K,V
WildcardType:通配符类型,例如:Comparator<?> 等
在哪里可以声明类型变量
声明类或接口时,在类名或接口名后面声明类型变量,把这样的类或接口称为泛型类或泛型接口
【修饰符】 class 类名<类型变量列表> 【extends 父类】 【implements 父接口们】{
}
【修饰符】 interface 接口名<类型变量列表> 【implements 父接口们】{
}
例如:
public class ArrayList<E>
public interface Map<K,V>{
....
}
复制代码
声明方法时,在【修饰符】与返回值类型之间声明类型变量,把声明(是声明不是单纯的使用)了类型变量的方法称为泛型方法
【修饰符】 <类型变量列表> 返回值类型 方法名(【形参列表】)【throws 异常列表】{
//...
}
例如:java.util.Arrays 类中的
public static <T> List<T> asList(T... a){
....
}
复制代码
<类型>
类型常见的一些写法
看一些源代码能经常看到一些不同泛型 <类型>
的写法,举几个 🌰
- E - Element(在集合中使用,因为集合中存放的是元素)
- T - Type(表示 Java 类,包括基本的类和自定义的类)
- K - Key(表示键,比如 Map 中的 key)
- V - Value(表示值)
- N - Number(表示数值类型)
- ? - (表示不确定的 Java 类型)
- S、U、V - 2nd、3rd、4th types
总结使用泛型的优势
- 类型安全,如果存储数据时类型不正确,会编译错误,若不使用泛型,很可能发生运行时错误
- 消除了强制类型的转换,使代码看起来更优雅