泛型,就是将数据类型作为参数的代码规则
泛型的一些名词定义
在泛型类 Response<T>
中,Response
是泛型的原生类型,T
为类型参数,Response<T>
称为泛型。
在泛型方法 public <T1> T1 getGenerics()
中,<T1>
即为泛型,没有原生类型,T1
为类型参数
为什么需要泛型
1、同一段代码,适配多种数据类型
2、实现1的基础上,编译规范数据类型,避免运行时因数据类型不匹配而报错
泛型类、泛型接口与泛型方法的定义与辨析
用 <T>
来定义泛型的存在
可以将泛型看作一个 final
的类型变量,其中 <>
内的字符 T
为泛型的类型参数变量申明,而其他没有尖括号的 T
,则为类型使用
泛型的定义,一般使用大写的字母,例如: T
(type 单个泛型)、K
、V
(key、value 键值对)、E
(element 元素)、R
(return 返回类型);但实际上,如果你想用其他自定义的字符也没问题,就是代码看起来没那么规范,类似变量名用中文拼音。
泛型方法不一定定义在泛型类里;定义在泛型类里包含泛型的方法,也不一定就是泛型方法
// 泛型类 有<T>
public class Response<T> {
private T data;
// 将 listener 的泛型 T,与类的泛型 T 关联起来
private ResponseListener<T, String> listener;
// 如果不用类型的泛型 T,也可以给 listener 的泛型 T 赋值为 Integer 或者其他
private ResponseListener<Integer, String> listener2;
// 定义泛型接口,此处的T,与类型的 T 并没有关系,只是刚好同名的变量名
interface ResponseListener<T, R> {
R transformResponse(T data);
}
// 注意:以下定义方式编译器会报错,必须要添加多个泛型类型再传参中转
// interface ResponseListener2<R> {
// R transformResponse(T data);
// }
// 非泛型方法,参数为类的泛型,已固定
public void setData(T data) {
this.data = data;
}
// 非泛型方法,返回类型为类的泛型,已固定
public T getData() {
return data;
}
// 泛型方法,此处 T 与类定义中的 T 不是同一个
// 相当于在类中定义了一个成员属性 data 后,在方法参数中又定义了一个 data
// 此外,这个方法定义在普通类里面也是可以的,泛型方法可以存在于普通类中
public <T> T getFirst(ArrayList<T> array) {
return array.get(0);
}
// 实际开发中,为了不造成阅读误解,尽量不要用上面的命名方式,改成:
public <T2> T2 getFirst(ArrayList<T2> array) {
return array.get(0);
}
}
private void usage() {
Response<Integer> intResponse = new Response<>();
// 此处会自动进行拆包,将 Integer 类型转化为 int
// 获取到的 data,为定义时的 Integer
int responseData = intResponse.getData();
ArrayList<String> stringArray = new ArrayList<>();
stringArray.add("first");
// stringArray 的泛型为 String,而 intResponse 的泛型为 Integer
// 尽管泛型类中的泛型方法与类所用的泛型参数同样为 T,但二者不同
String first = intResponse.getFirst(stringArray);
}
限定类型变量
在实际开发中,定义泛型时,经常需要加入一些类型的限定,此时需要用到 extend
关键字。
NOTE:无论是限定的父类(类定义时,用extends
)还是接口(类定义时,用implements
),在泛型限定时,用的,都只有 extend
在泛型的限定中,可以用 &
连接符限定一个类加多个接口。java 虚拟机并非真的实现了泛型,而是在泛型定义后,在编译时,使用了类型擦除,如果有 extends
限定泛型参数继承,则将类型擦除为 extends
后第一个类型;否则将类型擦除为 Object
。因而,如果有限定类型,类型必须放在第一个
public class Food {
// 食物入口时的感觉
public String tasteComment() {
return "It's food.";
}
}
public interface Yummy {
// 太好吃了,所以要表达
void comment();
}
public class Meat extends Food {
@Override
public String tasteComment() {
return "Wow, meat!";
}
}
public class Steak extends Meat implements Yummy {
@Override
public String tasteComment() {
return "Steak is juicy and delicious!";
}
@Override
public void comment() {
System.out.println(tasteComment());
}
}
// 如设置餐厅主打餐时,必须是食物,同时得好吃,所以同时派生自 Food 和 Yummy
// 其中 Food 为 class,是父类,必须摆放在第一个
// 如果还派生自其他接口,如 Serializable 等,接口的位置无需在意
// 如:<T extends Food & Yummy & Serializable> 或 <T extends Food & Serializable & Yummy>
private <T extends Food & Yummy> void setDailyMainDish(T dish) {
// 从单一职责角度出发,尽量拆分方法
// clientReact(dish);
// 拆分为两个方法:
clientHaveFood(dish);
clientReact(dish);
}
// 实际开发中,尽量遵循单一职责,建议将此方法,拆分为下面两个方法:
//private <T extends Food & Yummy> void clientReact(T dish) {
// System.out.println("client thought:" + dish.tasteComment());
// dish.comment();
//}
private <T extends Food> void clientHaveFood(T dish) {
System.out.println("client thought:" + dish.tasteComment());
}
private <T extends Yummy> void ClientReactYummyFood(T dish) {
dish.comment();
}
public static void main(String[] args) {
Restaurant restaurant = new Restaurant();
// Steak 是 Food 的子类,且满足了 Yummy 接口
restaurant.setDailyMainDish(new Steak());
// Meat 继承自 Food,但没有满足 Yummy 接口,因而编译器会报错
// restaurant.setDailyMainDish(new Meat());
}
泛型中的约束
- 泛型的类型参数不能直接构造出类
不同类型支持的构造方法不同,因而泛型的类型参数无法直接构造
- 静态域中不能有类型参数的变量和方法
虚拟机创建对象时,会先执行 static 相关代码,再执行构造方法。而在构造方法还没有执行时,静态域中的变量和方法无法获悉泛型类型参数的具体类型,因而不能在静态域中使用类型参数
- 泛型的类型参数不能为基本类型
泛型支持的参数类型必须是对象,而在 JDK 中,基本类型不是对象
public class Response<T> {
private T data;
Response() {
// 不可以用泛型的类型参数直接构造实例
// data = new T();
}
// 不可以用类型参数定义静态变量 或 静态方法
// private static T defaultData;
// public static void setDefaultData(T data) {
// defaultData = data;
// }
// 但是泛型方法可以是静态的
public static <T> void setDefaultData(T data) {
}
}
private void usage() {
// 泛型中,不允许基础类型参数,必须使用其对应包装类
// Response<int> intResponse = new Response<>();
Response<Integer> intResponse = new Response<>();
// 此处会自动进行拆包,将 Integer 类型转化为 int
// 获取到的 data,为定义时的 Integer
int responseData = intResponse.getData();
}
泛型类型的局限性
- JDK 对泛型的类型判定,只能到其原生类型
获取泛型的类型,或是对类型进行判断,都只支持到其原生类型。即,在上面的 Response<T>
中,getClass()
返回的,只有 Response
- 可以定义泛型的数组,但初始化只能用原生类初始化
public static void main(String[] args) {
Response<Integer> intResponse = new Response<>();
Response<String> stringResponse = new Response<>();
// 虽然 intResponse 和 stringResponse 的泛型不同,但其类型判定只到原生类型,因而是相同的
boolean isSameClass = intResponse.getClass() == stringResponse.getClass();
// 对于 instanceOf 关键字的判断,只能到其原生类型,后面两种泛型的判断并不支持
boolean isResponse = intResponse instanceof Response;
// boolean isResponse2 = intResponse instanceof Response<Integer>;
// boolean isResponse3 = intResponse instanceof Response<T>;
// 定义数组时,可以添加泛型。但数组的初始化,只能使用原生类
Response<Integer>[] arrays = new Response[10];
// Response<Integer>[] arrays = new Response<Integer>[10];
arrays[0] = intResponse;
// 虽然构建时,没有传入参数类型,但在定义时规定了,因而会报错
// arrays[1] = stringResponse;
}
- 方法重载时,无法识别泛型的类型参数
// 以下两个方法无法兼容,尽管入参的泛型不同
// 但是入参泛型的基本类型相同,在经过类型擦除后就是同一类型
// java 编译器对重载的检查只有方法名和入参类型,不包括返回类型
//private String getFirst(ArrayList<String> stringList) {
// return stringList.get(0);
//}
//
//private Integer getFirst(ArrayList<Integer> intList) {
// return intList.get(0);
//}
// 以上两个方法可合并为:
private <T> T getFirst(ArrayList<T> list) {
return list.get(0);
}
- 泛型类结合
Throwable
时的局限性
泛型类不能继承自 Throwable
,且无法 catch
泛型,但可以 throws
泛型类
public class GenericsException {
// 编译器主动报异常,并提示:Generic class may not extend 'java.lang.Throwable'
// class GException<T> extends Exception
// class GException<T> extends Throwable
public <T extends Throwable> void handleThrowable(T throwable) throws T {
try {
// do something
// 无法 catch 泛型
// } catch (T t) {
// 但可以catch throwable,然后抛出一个泛型的 throwable
} catch (Exception e) {
throw throwable;
}
}
}
泛型类型的继承规则
泛型的类型参数不一致时,泛型是两个不同的类型,泛型的继承关系与 类型参数 的继承关系无关;
泛型的继承关系可以理解为原生类的继承关系。当原生类有继承关系可循,原生类一致时,类型参数一致,则两个泛型一致,存在继承关系;原生类一致时,类型参数不一致,则两个泛型不一致,不存在继承关系
因而:当泛型的原生类型有继承关系,且类型参数一致时,泛型间存在继承关系,否则不存在
// 定义原生类 SuccessResponse 继承自 Response
public class SuccessResponse<T> extends Response<T> {
}
public static void main(String[] args) {
// 在上面的示例中,定义了 Steak extend Food
Food food = new Food();
Steak steak = new Steak();
Response<Food> foodResponse = new Response<Food>();
Response<Steak> steakResponse = new Response<Steak>();
SuccessResponse<Food> foodSuccessResponse = new SuccessResponse<Food>();
SuccessResponse<Steak> steakSuccessResponse = new SuccessResponse<Steak>();
// 由于存在继承关系,可以将 steak 赋值给 food
food = steak;
// 原生类存在继承,类型参数一致,泛型存在继承关系
foodResponse = foodSuccessResponse;
steakResponse = steakSuccessResponse;
// 原生类一致,类型参数不同,因而泛型不一致,不存在继承关系
// foodResponse = steakResponse;
// steakSuccessResponse 转化为父类后是 steakResponse,与 foodResponse不一致,因而不能进行转换
// foodResponse = steakSuccessResponse;
}
统配符类型
泛型的继承关系与 类型参数 的继承关系无关,但是在实际开发中,经常需要限制 类型参数,因而引入了通配符:
<? extends ClassName>
: ClassName 为类型参数的父类,即为继承关系中的上界限
<? super ClassName>
: ClassName 为类型参数的子类,即为继承关系的下界限
public class Meal<T extends Food> {
// 套餐内的食物
private T food;
public T getFood() {
return food;
}
public void setFood(T food) {
this.food = food;
}
}
public static void main(String[] args) {
// 通配符意义:可能为 Meat 的任意子类,包括Meat
Meal<? extends Meat> orderMeal1 = new Meal<>();
// set 方法中,需要传入一个适用于 Meat 所有子类的类(不存在)
// 以下所有的 setFood 方法,都编译不过,因为没有一个可以表示所有子类的
// orderMeal1.setFood(new Food());
// orderMeal1.setFood(new Meat());
// orderMeal1.setFood(new Steak());
// 赋值时,需要赋值一个父类,可以用子类代替
Food food1 = orderMeal1.getFood(); // Food <- Meat 可以
Meat meat1 = orderMeal1.getFood(); // Meat <- Meat 可以
// steak 是 meat 的子类,父类不可以赋值给子类
// Steak steak1 = orderMeal1.getFood(); // Steak <- Meat 不行
// 通配符意义: Meat 所有父类,包括Meat
Meal<? super Meat> orderMeal2 = new Meal<>();
// Food 是 Meat 的父类,但不一定包含所有父类属性,因而无法通过编译
// orderMeal2.setFood(new Food());
// Meat 是 Meat 类型,一定包含所有 Meat 父类的属性
orderMeal2.setFood(new Meat());
// Steak 是 Meat 子型,一定包含所有 Meat 父类的属性
orderMeal2.setFood(new Steak());
// java内万物皆对象,Object 是所有 java 类的父类
// 如果Meal 定义时,public class Meal<T>,getFood,只支持Object
Object object = orderMeal2.getFood();
// Meal 定义时,public class Meal<T extends Food>
// 类型参数的最高父类即为 Food,所以 Food 可以兼容 <? super Meat> 的所有类
Food food2 = orderMeal2.getFood();
// <? super Meat> 可以为Food,而 Food 不一定是 Meat
// Meat meat2 = orderMeal2.getFood();
// 父类无法转换为子类
// Steak steak2 = orderMeal2.getFood();
}
如在下方的 Meal<Food>
中,需要调用所有的 Food
的 tasteComment()
方法,此时则需要用到通配符:
<? extend Food>
—— (继承关系中)向下兼容 类型参数,即指定类型的子类
class Restaurant {
private String getFoodTasteComment(Meal<Food> meal) {
return meal.getFood().tasteComment();
}
private String getFoodTasteComment2(Meal<? extends Food> meal) {
return meal.getFood().tasteComment();
}
}
public static void main(String[] args) {
Restaurant restaurant = new Restaurant();
Meal<Food> foodMeal = new Meal<>();
Meal<Steak> steakMeal = new Meal<>();
restaurant.getFoodTasteComment(foodMeal);
// steakMeal 和 foodMeal 不是继承关系,因而无法在此兼容
// restaurant.getFoodTasteComment(steakMeal);
restaurant.getFoodTasteComment2(foodMeal);
// 通过<? extends Food> 的通配符,兼容了foodMeal 和 steakMeal 的传参
restaurant.getFoodTasteComment2(steakMeal);
}
除了获取 类型参数 的类型属性外,还可能需要设置,例如餐馆周年庆,所有与肉有关的 Meal
都可以免费升级为 Meal<Steak>
,此时,需要用到通配符。
<? super Food>
—— (继承关系中)向上兼容 类型参数,即指定类型的父类
public class Bread extends Food{
@Override
public String tasteComment() {
return "Bread, not bad.";
}
}
class Restaurant {
// 此方法不兼容 Meal<Meat> 类型入参
private void updateMeal(Meal<Food> meal) {
meal.setFood(new Steak());
}
// 入参定义为 Meal<? extends Food> meal 时,无法实现赋值
// 因为 extends Food 的类型可能会有与 Steak 无关的类型,如 Meal<Bread> 之类
// private void updateMeal2(Meal<? extends Food> meal) {
// meal.setFood(new Steak());
// }
private void updateMeal3(Meal<? super Steak> meal) {
meal.setFood(new Steak());
}
}
public static void main(String[] args) {
Restaurant restaurant = new Restaurant();
Meal<Food> foodMeal = new Meal<>();
Meal<Meat> meatMeal = new Meal<>();
Meal<Steak> steakMeal = new Meal<>();
Meal<Bread> breadMeal = new Meal<>();
restaurant.updateMeal(foodMeal);
// 此方法的入参为 Meal<Food>,无法兼容 Meal<Meat> 或 Meal<Bread>
// restaurant.updateMeal(meatMeal);
// restaurant.updateMeal(breadMeal);
restaurant.updateMeal3(foodMeal);
restaurant.updateMeal3(meatMeal);
restaurant.updateMeal3(steakMeal);
// bread 不在 steak 的父类中,因而也不兼容
// restaurant.updateMeal3(breadMeal);
}
那如果,要设置为所有套餐(包括 Bread
套餐)都能升级为 Meal<Steak>
呢?
那就必须修改整个泛型而不是只单单类型参数了,编译器不兼容,即便编译器兼容了,在运行时也可能会出错。例如,Bread
可能自定义了一个 pubilc
方法,而 Steak
没有,在运行时就会出现崩溃
class Restaurant {
private Meal<Steak> updateMeal4(Meal<? extends Food> meal) {
return new Meal<Steak>();
}
}
public static void main(String[] args) {
Restaurant restaurant = new Restaurant();
Meal<? extends Food> orderMeal = new Meal<>();
orderMeal = restaurant.updateMeal4(orderMeal);
}
虚拟机如何实现泛型 总结
在虚拟机中,并没有真正实现泛型,即 Meal<Bread>
和 Meal<Meat>
在虚拟机中,并不是两个明确不同的类。
虚拟机用类型擦除的方式,间接地实现了泛型。即编译完成后,会将参数类型变为所有类的父类,即Object。擦除后,泛型变成: Meal<Object>
;类里面定义的参数类型会转变为 Object
,然后再使用时,编译器自动给参数类型的参数加入了对应的类型强转