泛型的概念

40 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 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 类型(booleanbytecharshortintlongfloat  和 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

总结使用泛型的优势

  • 类型安全,如果存储数据时类型不正确,会编译错误,若不使用泛型,很可能发生运行时错误
  • 消除了强制类型的转换,使代码看起来更优雅