为什么需要泛型?
先来看一段代码,这段代码是用来计算两个数之和,可以看到每次新增一种数据类型,那么就要新增一个方法,这显然是不好的解决方法。
public class NoGeneric {
public int addInt(int a, int b) {
return a + b;
}
public double addDouble(double a, double b) {
return a + b;
}
public float addFloat(float a, float b) {
return a + b;
}
}
这里就引出为什么要使用泛型的第一原因:
其实计算两个数之和的逻辑是一样的,只是参数类型不一样而已,那么泛型就是可以用于多种数据类型执行相同的代码。
下面再看另一段代码:
public class NoGeneric {
public static void main(String[] args) {
//定义一个集合
ArrayList datas = new ArrayList();
//往集合中添加数据
datas.add("Android");
datas.add(1);
datas.add("Flutter");
//使用集合数据
String rank = datas.get(1)//这段代码会报类型转化异常。
}
}
在 JDK1.5 之前,集合是没有泛型的,添加的数据都是 Object 类型,因此可以往里面添加任意类型的数据,而在取出数据时,是需要判断类型,然后进行强制转化的,不然就会出现 ClassCastException
。这里就是引入泛型的第二个原因:泛型中的类型在使用时指定,不需要强制类型转换
泛型引入的好处:
-
泛型就是可以用于多种数据类型执行相同的代码。
-
泛型中的类型在使用时指定,不需要强制类型转换
泛型在类,接口以及方法的使用
泛型使用 <T> 表示, 中间的可以带多种类型,使用 , 分割,例如<T,E>等。
- 泛型类:
public class GenericType<T> {}
- 泛型接口
泛型接口与泛型类的定义基本相同。
//泛型接口
public interface Callback<T> {
void next(T data);
}
//未传入泛型实参时
public class ResponseCallback<T> implements Callback<T> {
T data;
public void next(T data){
this.data = data;
}
}
//传入泛型实参
public class SuccessCallback implements Callback<String> {
String data ;
public void next(String data){
this.data=data;
}
}
- 泛型方法
泛型方法,是在调用方法的时候指明泛型的具体类型 ,泛型方法可以在任何地方和任何场景中使用,包括普通类和泛型类。
注意泛型类中定义的普通方法和泛型方法的区别。
public class GenericMethod<T> {
private T data;
/**
* 这个不是泛型方法,虽然该方法使用了泛型,但是这个泛型是在 GenericMethod 中定义的,
*/
public T getData() {
return data;
}
/**
* 泛型方法:
*
* 在方法的修饰符后面,返回值前面使用<T,...>,内部定义的泛型可以出现在方法的任意位置。
*/
public <T> T getData(GenericType<T> data) {
return data.getData();
}
}
限定类型变量
有时候,我们需要对类型变量加以约束,比如计算两个变量的最小,最大值。
public <T> T min(T a, T b) {
return a.compareTo(b)>0?a:b;
}
那么直接这样写代码,肯定是会报错的,那么如何确保传入的参数类型 T 的两个变量具备 compareTo
方法。
解决方法就是将 T 限制为实现 Comparable 接口。
改造代码如下:
public <T extends Comparable> T min(T a, T b) {
return a.compareTo(b)>0?a:b;
}
T extends Comparable
T 表示绑定类型的子类型,Comparable 表示绑定类型,子类型和绑定类型可以是类也可以是接口。
注意:extends左右都允许有多个,如 <T,V extends Comparable & Serializable>
注意限定类型中,只允许有一个类(这是单继承的原因),而且如果有类,这个类必须是限定列表的第一个。
这种类的限定既可以用在泛型方法上也可以用在泛型类上。
public class GenericMethodLimit {
/**
* 比较两个参数
*/
public static <T extends Comparable> T min(T a, T b) {
return a.compareTo(b) > 0 ? a : b;
}
public static void main(String[] args) {
//使用 min 方法
min(1, 2);
min("a", "b");
//min(new Test(),new Test());//Test 类没有实现 Comparable 接口。所以这里编译报错
}
class Test{
}
}
泛型中的约束和局限性
不能实例化类型变量
public class RestrictGeneric<T> {
public RestrictGeneric() {
//不能实例化类型变量
//new T();
}
}
不能用基本类型实例化类型参数
//RestrictGeneric<double> restrictGeneric = new RestrictGeneric<>();//报错
不能创建参数化类型的数组
RestrictGeneric<String>[] restrictGenericArr = null;//可以这样定义
//RestrictGeneric<String>[] restrictGenericArr1 = new RestrictGeneric<String>[10];//不允许
泛型类的泛型不能用于静态变量的定义和静态方法上
public class RestrictGeneric<T> {
//泛型类的静态上下文中类型变量失效
//static T instance;
//泛型类定义的泛型也是不能用在静态方法上
//public static T getInstance(){
// return null;
//}
//但是可以这样用-单独定义泛型方法
public static <T> T getInstance(){
return null;
}
}
泛型类不能继承 Exception,Throwable
private class Problem<T> extends Exception{//有问题的
}
不能 catch 泛型类对象
//private class Problem<T extends Exception> {
// public void exception(){
// try{
// //不能 catch 泛型类对象
// }catch (T e){
// }
// }
//}
/**
* 抛出 T 类型的
* 这种操作的是可以的
*/
private <T extends Exception> void throwException(T t) throws T {
try {
} catch (Exception e) {
//do sth
throw t;
}
}
运行时类型查询只适用于原始类型
RestrictGeneric<String> stringRestrictGeneric = new RestrictGeneric<>();
RestrictGeneric<Integer> integerRestrictGeneric = new RestrictGeneric<>();
System.out.println(stringRestrictGeneric.getClass() == integerRestrictGeneric.getClass());//true
System.out.println(stringRestrictGeneric.getClass().getName());//com.example.generic.RestrictGeneric
System.out.println(integerRestrictGeneric.getClass().getName());//com.example.generic.RestrictGeneric
if(stringRestrictGeneric instanceof RestrictGeneric<String>){ }//不允许
泛型类型的继承规则
public class Animal {
@Override
public String toString() {
return Animal.class.getSimpleName();
}
}
public class Cat extends Animal {
@Override
public String toString() {
return Cat.class.getSimpleName();
}
}
public class InheritGeneric<T> {
public static void main(String[] args) {
InheritGeneric<Animal> animalInheritGeneric = new InheritGeneric<Animal>();
InheritGeneric<Cat> catInheritGeneric = new InheritGeneric<Cat>();
//animalInheritGeneric = catInheritGeneric;//泛型不一致
//InheritGeneric<Animal> animalInheritGeneric = new InheritGeneric<Cat>();//这种是会报错的,因为两者的泛型不一样
//为了实现这种方式,需要引入通配符
}
}
观察上面的代码,
来看看 animalInheritGeneric 和 catInheritGeneric 有什么关系?
即使 Animal和 Cat 是继承关系,但是 animalInheritGeneric 和 catInheritGeneric 它们之间没有关系,因为泛型不一致。
通配符类型
? extends X
表示传递给方法的参数,必须是X的子类(包括X本身)
public class GenericType<T> {
private T data;
public GenericType() {
}
public GenericType(T data) {
this.data = data;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
GenericType<? extends Cat> genericType = null;
GenericType<Cat> catGenericType = new GenericType<>();
GenericType<Animal> animalGenericType = new GenericType<Animal>();
//<? extends Cat>接受 Cat 及其 Cat 子类
genericType = catGenericType;//编译通过
//Cat 是 Animal 的子类
genericType = animalGenericType;//编译不通过
//--------setData
//setData() 他知道传入的类型是 Cat 类型,但是具体是什么子类型,还是不清楚的。因此这种方式是不安全的。
genericType.setData(new BlackCat());//编译不通过
genericType.setData(new Cat());//编译不通过
//-------getData
genericType = new GenericType<Cat>(new BlackCat());
Cat data = genericType.getData();//编译通过
这里需要关注两个问题:
-
genericType.setData(new Cat())
不能编译通过的原因
setData() 他知道传入的类型是 Cat 类型,但是具体是什么子类型,还是不清楚的。因此这种方式是不安全的。
- 为什么
Cat data = genericType.getData()
返回的是 Cat 类型的。
genericType.getData
接受的数据,一定会是 Cat
或者 Cat 的子类
,但是不管是哪个,都是可以用 Cat 这个父类去接受,这个可以用多态去解释。
因此 genericType.getData() 返回的是 Cat 类型。
? super X
表示传递给方法的参数,必须是X的超类(包括X本身)
GenericType<? super Cat> genericType2 = new GenericType<Animal>();//right
//GenericType<? super Cat> genericType3 = new GenericType<BlackCat>();//error
//设置数据
//GenericType其中提供了get和set类型参数变量的方法的话,set方法可以被调用的,且能传入的参数只能是泛型X或者泛型X的子类
//用到多态的思想来理解:既然传给 GenericType 的参数必须是 Cat 的超类,那么在调用 setData 时肯定是能接受 Cat 或者 Cat 的子类。
//Cat 或者 Cat 的之类。
genericType2.setData(new Cat());
genericType2.setData(new BlackCat());
//genericType.setData(new Animal());//不允许
//获取数据
// ? super X 表示类型的下界,类型参数是X的超类(包括X本身),
// 那么可以肯定的说,get方法返回的一定是个X的超类,那么到底是哪个超类?
// 不知道,但是可以肯定的说,Object一定是它的超类,所以get方法返回Object。
// 编译器是可以确定知道的。对于set方法来说,编译器不知道它需要的确切类型,
// 但是X和X的子类可以安全的转型为X。
Object data = genericType.getData();
Java 虚拟机是如何实现泛型的?
Java语言中的泛型实现方法称为类型擦除,基于这种方法实现的泛型称为伪泛型。
/**
* 下面这两种声明方法方式是不行的,因为在编译时会进行泛型擦除
* 因此得到的参数都是 ArrayList ,因此这两个方法的签名是一样的
*/
//public static void method(ArrayList<Animal> data){}
//public static void method(ArrayList<Cat> data){}
上面这段代码是不能被编译的,因为参数List<Animal>和List<Cat>编译之后都被擦除了,变成了一样的原生类型List<E>,擦除动作导致这两种方法的特征签名变得一模一样。
下面通过泛型擦除的机制,来往List<Integer>
集合中添加一个 String 字符串。
/**
* 反射机制动态给集合添加其他类型的数据---绕过泛型的限制,泛型擦除
*/
private static void generic3() {
try {
List<Integer> collection = new ArrayList<Integer>();
collection.add(1);
Class<? extends List> collectionClass = collection.getClass();
Method addMethod = collectionClass.getDeclaredMethod("add", Object.class);
addMethod.setAccessible(true);
addMethod.invoke(collection, "我是 String 类型的数据");
for (Object obj : collection) {
System.out.println("元素:" + obj);
}
} catch (Exception e) {
e.printStackTrace();
}
}
记录于2019年4月1日愚人节~
网友评论