Java中的泛型基础

126 阅读4分钟

1、为什么要使用泛型?

  • 适用于多种数据类型执行相同的代码,利于封装(如,封装Base类、统一的适用于RecyclerViewAdapter等)
  • 泛型中的类型在使用的时候,直接指定即可,不需要进行强制转换。数据转换是很容易带来数据异常的,尤其是当服务端传过来的数据与我们接收的数据类型不同的时候。

2、泛型类、泛型接口、泛型方法

2.1、泛型方法

引入一个类型变量T(其他大写字母都可以,不过常用的就是T,E,K,V等等),并且用<>括起来,并放在类名的后面。泛型类是允许有多个类型变量的。例如:

一个类型变量

publie class NormalGeneric<T> {
    private T data;
    
    public NormalGeneric() {
    }
    
    public NormalGeneric(T data) {
        this();
        this.data = data;
    }
}

两个类型变量

public class NormalGeneric2<T,K>{
    private T data;
    private K result;
    
    public NormalGeneric2(){
    }
}

2.2、泛型接口/泛型类

public interface Generator<T>{
    public T next();
}

实现泛型接口的类有两种实现方法:

(1)未传入泛型参数时,那么该类也属于泛型类,那么在new出该实例对象时,需要指定具体类型。

// 定义类
public class ImpIGenerator<T> implement Generator<T>{
    private T data;
}
​
// 实现  注意<String> STring就是具体类型
ImpIGenerator<String> impIGenerator = new ImpIGenerator<>();

(2)在实现泛型接口时,传入泛型参数,那么在new出该类的实例时,与普通的类就没区别了。

// 类定义
public class ImpIGenerator2 implement Generator<String>{
    
    @Override
    public String next(){
        return "OK";
    }
}
​
// 实现
ImpIGenerator2 impIGenerator2 = new ImpIGenerator2();

2.3、泛型方法

public class GenericMethod {
    // public:代表修饰符 <T>:表示泛型方法  T:表示返回值
    public <T> T genericMethod(T...a){
        return a[a.length/2];
    }
    
    public void test(int x, int y){
        System.out.println(x+y);
    }
}

泛型方法是在调用方法的时候指明泛型的具体参数类型,泛型方法可以在任何场景中使用,包括普通类和泛型类。但是在泛型类中定义普通方法和泛型方法是有区别的:

普通方法:

public class Generic<T>{
    private T key;
    
    public Generic(T key){
        this.key = key;
    }
    
    // 虽然在该方法中使用了泛型,但是这并不是一个泛型方法。
    // 这只是类中一个普通的成员方法,只不过他的返回值是在声明泛型类已经声明过的泛型。
    // 所以在这个方法中才可以继续使用 T 这个泛型。
    public T getKey(){
        return key;
    }
}
​

泛型方法:

// 这才是一个真正的泛型方法。
// 首先在public与返回值之间的<T>必不可少,这表明这是一个泛型方法,并且声名了一个泛型T
// 这个T可以出现在这个泛型方法的任意位置
// 泛型的数量也可以是任意多个
// 如:public <T, K> ...
public <T> T showKeyName(Generic<T> generic){
    System.out.println("generic key:" + generic.getKey());
    T test = generic.getKey();
    return test;
}

关键点:<T>

3、限定类型变量

有时候我们需要对变量类型进行约束。比如我们定义一个泛型方法:

public static <T> T min(T a, T b){
    if(a.comapareTo(b) > 0){
        return a;
    } else {
        return b;
    }
}

如何才能够保证传进来的a、b两个参数一定有compareTo方法呢?那么我们就要对T进行约束,保证传进来的T参数的类型实现了Comparable类

public static <T extends Comparable> T min(T a, T b){
    if(a.comapareTo(b) > 0){
        return a;
    } else {
        return b;
    }
}

T extends Comparable 表示,传进来的实例对象必须实现了Comparable类,否则将发生编译错误

extends左右都允许有多个,如**T,V extends Comparable & Serializable**

注意限定类型的时候,只允许有一个类,允许多个接口,如果所限定的类型包含类和接口,那么类要放在最前面,也就是紧跟extends之后的第一个要放类。

类型限定既可以放在泛型方法上,也可以用在泛型类上

T super XXXextends用法一样,只不过super表示传入的类型T只能是XXX或XXX的父类,最高为object

4、泛型中的约束和局限性

现在有一个Restrict泛型类 public class Restrict<T>{}

4.1、不能用基本类型实例化

// Restrict<double> 这种不允许 要使用 Double
Restrict<Double> restrict = new Restrict<>();

4.2、运行时,类型查询只适用于原始类型

因为类型擦除,Java虚拟机中的对象并没有泛型类这一说,instanceofgetClass() 只能查询到原始类型, 具体的泛型类型时无从得知的。

// if(restrict instanceof Restrict<Double>){}  这种不允许
// if(restrict instanceof Restrict<T>){}  这种不允许
Restrict<String> restrictString = new Restrict<>();
System.out.println(restrict.getclass == restrictString.getclass());
System.out.println(restrict.getclass.getName);

4.3、泛型类的静态上下文中类型变量失效

不能在静态域或静态方法中使用泛型。因为泛型是需要我们在创建对象的时候才知道是什么类型,而对象创建过程中,代码的执行先后顺序是最先执行static部分,然后才是构造函数等。

因此,在对象初始化之前static部分的代码已经执行,而此时的static块并不知道到底是什么类型,因为这个时候我们的对象还没初始化。

// 静态域或者方法里不能引用类型变量
// private static T instance;// 静态方法本身是泛型方法就可以
private static <T> T getInstance(){}

4.4、不能创建参数化类型的数组

例如 Restrict<Double> array = new Restrict<Double>[10] 是错误的,

我们只能声明 Restrict<Double>[] array;

4.5、不能实例化类型变量

// 不能实例化类型变量
//public Restrict(){
//    this.data = new T();
//}

4.6、不能捕获泛型类的实例

// 泛型类不能 extends Exception/Throwable
//private class Problem<T> extends Exception{}//public <T extends Throwable> void doWork(T) t{
//    try{
//        ...
//    } catch (T e){
//        ...
//    }
//}

但可以这样

public <T extends Throwable> void doWork(T t) throws T{
    try{
        ...
    } catch (Throwable e){
        ...
    }
} 

5、泛型类型的继承规则

现在我们有一个类、子类和一个泛型类

public class Employee{}
​
public class Worker extends Employee{}
​
public class Pair<T>{}

Pair<Employee>Pair<Worker>之间没有任何关系

Employee employee = new Worker();
//Pair<Employee> employeePair2 = new Pair<Worker>(); 这行报错

但是泛型类之间可以继承和扩展其他泛型类(看2.2、泛型接口/泛型类),例如:ListArrayList

Pair<Employee> pair = new ExtendPair<>();
​
private static class ExtendPair<T> extends Pair<T>{}