Java范型generics,是JDK1.5引入的新特性,是一种编译时类型安全检测机制,可以在编译时检测到非法的类型。范型的本质是将类型参数化,将类型指定成一个参数。java中的集合就有使用,并且对外提供的三方库和SDK中使用也极为常见。
特点是运行效率高,在定义时不设限在使用时再写入具体类型。 定义泛型参数时还可以使用继承,例如 <T extends User> ,后面都可以使用User类或它的子类接收在必要的时再强转指定类型。
泛型只在编译期间生效,运行期泛型会被擦除,比如List<String> 和 List<Integer>的两个空集合,equals判断为true。
范型的参数只能是引用类型,不能是基本数据类型。
三种使用范围:范型类、范型接口、范型方法。
1、范型类: 用在构造方法在实例化的时候检测类型
范型的参数可以是系统定义的类也可以是自定义类;构造方法传入的实参类型需要与范型的参数类型相同,<T> 类型由调用方决定。
1.1 定义一个泛型类
public class GenericUser<T> {
private T name;
private T age;
public GenericUser(T nameOrAge) {
//这里为了学习总结,实际不会这样乱赋值
this.name = nameOrAge;
this.age = nameOrAge;
}
public GenericUser() {
}
public T getName(){
return name;
}
public T getAge(){
return age;
}
//注意:静态方法使用范型:必须将静态方法也定义成范型方法,否则报错。即使类已经定义了也会报错,必须在修饰符和返回值之间static之后加上这个范型
private static <T> T getNameS(T t){
return t;
}
}
1.2 使用泛型类
GenericUser<String> genericUser1 = new GenericUser<>("Tom");
Log.e(TAG, "onClick: name= "+genericUser1.getName() );
GenericUser<Integer> genericUser2 = new GenericUser<>(8);
Log.e(TAG, "onClick: age="+genericUser2.getAge() );
//todo 不是使用定义了范型的类就一定要传入范型,如果传了什么就用什么,如果不传就可以是任意类型。需要做类型判断
GenericUser genericUser3 = new GenericUser<>("随便写");
Log.e(TAG, "onClick: name="+genericUser3.getName()+" age="+genericUser3.getAge());
结果打印:
//2024-02-18 18:33:34.884 22735-22735/com.example.testdemo3 E/FanXingActivity: onClick: name= Tom
//2024-02-18 18:33:34.884 22735-22735/com.example.testdemo3 E/FanXingActivity: onClick: age=8
//2024-02-18 18:33:34.885 22735-22735/com.example.testdemo3 E/FanXingActivity: onClick: name=随便写 age=随便写
2、泛型方法
在方法调用时检测类型,在返回值类型之前的<T>是范型方法的象征,此处表示声明了一个范型T,范型数量也可以多个比如<T,E>;
下面getNanme1和getNanme2是泛型方法,但getNanme3方法不是范型方法它只是在形参中使用了范型而已。
private <T> T getName1(GenericUser<T> user){
return user.getName();
}
//也有这种返回但不传入
private <T> T getName1(T str){
GenericUser<T> genericUser = new GenericUser<>(str);
Log.e(TAG, "getName1: name= "+genericUser.getAge() );
return genericUser.getAge();
}
private <T,E> T getName2(GenericUser<T> user){
return user.getName();
}
//不是泛型方法
private String getName3(GenericUser<String> user){
return user.getName();
}
//范型方法也可以普通方法一样使用可变参数
private <T> void getName4(T...ts){
for (T t : ts) {
Log.e(TAG, "getName4: "+t );
}
}
3、泛型接口:一般和范型类或方法搭配使用且与范型类用法类似
interface GenericInter<T> {
T getNumber();
}
3.1 泛型类使用泛型接口
当实现范型接口的类不传范型实参:比如下面的也需要在类名增加GenericInterUser1<T>和接口一样的范型。
public class GenericInterUser1<T> implements GenericInter<T>{
@Override
public T getNumber() {
return null;
}
}
如果当实现范型接口的类传入自己范型参数: 如GenericInterUser1不需要在类名加范型参数
public class GenericInterUser2 implements GenericInter<String>{
private String number = "000001";
@Override
public String getNumber() {
return number;
}
}
泛型的子类限制
public class GenericUserSun<T extends Number> extends GenericUser{
private T age;
public GenericUserSun(T age){
super(age);
this.age = age;
}
public T getAge() {
return age;
}
}
//上面案例GenericUserSun可以传入的泛型就有了限制,不是所有类型都可以,而必须是Number的子类,比如Integer、Byte、Short、Float等等。
注意:如果我们想写一个接口实现类做泛型参数也要使用<T extends 接口>和类一样 而不是<T implements 接口>,报错如下。