关于泛型,你所需要知道的事

Leo 2021年12月01日 578次浏览

思维导图

image.png

概述

泛型即参数化类型,也就是说,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口、方法中,分别被称为泛型类、泛型接口、泛型方法。但是需要注意的是,这种参数化类型之存在于编译阶段。在编译过程中,正确检验泛型结果后,会将泛型的相关信息擦除,并且在对象进入和离开方法的边界处添加类型检查和类型转换的方法,也就是说,泛型信息不会进入到运行期。

基本使用

泛型有三种使用方式:泛型类、泛型接口、泛型方法。

泛型类

泛型类最常用于各种容器类中,如:List、Set、Map。

public class Genericity<T> {
    
    private T t;

    public Genericity(T t) {
        this.t = t;
    }

    public T getT() {
        return t;
    }

    public void setT(T t) {
        this.t = t;
    }
}
public class Test {
    public static void main(String[] args) {
        //传入泛型参数
        Genericity<String> genericity = new Genericity<>("demo");
        System.out.println(genericity.getT());
        Genericity<Integer> genericity1 = new Genericity<>(114514);
        System.out.println(genericity1.getT());

        //不传入泛型参数
        Genericity genericity2 = new Genericity("Demo");
        System.out.println(genericity2.getT());
        Genericity genericity3 = new Genericity(23333);
        System.out.println(genericity3.getT());
    }
}
输出:
demo
114514
Demo
23333

如果传入了泛型参数,那么编译器在传入实参的时候会进行类型检查,此时泛型才会发挥它真正的意义;如果不传入泛型参数,那么就可以传入任意类型。

注意:

  1. 泛型的类型参数只能是类类型,不能是简单类型,如果是基本类型必须使用其包装类型。
  2. 不能对泛型类型使用 instanceof 操作,原因在于泛型擦除。
泛型接口

泛型接口与泛型类的定义及使用基本一致,泛型接口常用在各种类的生产器中,如 AsyncTask 等。

public interface Genericity<T> {
    public T show();
}
public class GenericityImpl<T> implements Genericity<T> {
    @Override
    public T show() {
        return null;
    }
}

如果未传入泛型实参,那么就得把泛型的声明延伸到类中。

如果传入了类型实参,那么就不需要在类上增加类型说明了:

public class GenericityImpl implements Genericity<String> {
    @Override
    public String show() {
        return null;
    }
}
泛型方法

先举一个经典的错误例子:

public class Genericity<T> {
    
    private T t;

    public Genericity(T t) {
        this.t = t;
    }

    //这不是一个泛型方法
    public T getT() {
        return t;
    }

    public void setT(T t) {
        this.t = t;
    }
}

上诉泛型类的 getT 方法并不是一个泛型方法,它的返回值是在声明泛型类已经声明过的泛型。

泛型类,是在实例化类的时候指明泛型的具体类型;

泛型方法,是在调用方法的时候指明泛型的具体类型;

下面是一个简单的泛型方法:

public class Genericity {
    public <T> String show(T t) {
        return t.toString();
    }
}

//使用
public static void main(String[] args) {
    Genericity genericity = new Genericity();
    System.out.println(genericity.show(2333));
    System.out.println(genericity.show("Demo"));
}

只有声明了 <T> 的方法才是泛型方法,所以可以在方法中使用泛型类型 T。

当然,泛型参数也可以指定多个:

public class Genericity {
    public <T, K> String show(T t, K k) {
        return t.toString() + k.toString();
    }
}
//使用:
public static void main(String[] args) {
    Genericity genericity = new Genericity();
    System.out.println(genericity.show(2333, "Demo"));
}

这都是一些简单的泛型方法,来看一下几种比较容易搞错的例子:

  1. 泛型类中包含泛型方法:

    public class Genericity<T> {
        private T t;
    
        public Genericity(T t) {
            this.t = t;
        }
    
        public void show(){
            System.out.println(t.toString());
        }
    
        //泛型方法
        public <T> void show(T t){
            System.out.println(t.toString());
        }
    }
    

    泛型中的 T 会把类的类型参数 T 隐藏,所以调用时其实不是一个对象:

    public static void main(String[] args) {
        Genericity<Integer> genericity = new Genericity<>(2333);
        genericity.show();
        genericity.show("Demo");
    }
    
  2. 泛型与可变长参数

    //泛型方法
    public <T> void show(T... ts) {
        for (T t : ts) {
        	System.out.println(t.toString());
        }
    }
    genericity.show("Demo", 2333, 23.22);
    
  3. 静态泛型方法

    public class Genericity<T> {
    
        //静态泛型方法
        public static <T> void show(T t) {
    
        }
        //以下方法编译器报错:
        public static void show(T t) {
    
        }
    }
    

    静态方法无法访问类上定义的泛型,所以只能把泛型定义在方法上。

通配符

先看一个例子:

public class Genericity<T> {

    private T t;

    public Genericity(T t) {
        this.t = t;
    }

    public T getT() {
        return t;
    }

    public void setT(T t) {
        this.t = t;
    }
}
public class Test {
    public static void main(String[] args) {
        Genericity<Number> numberGenericity = new Genericity<>(2333);

        Genericity<Integer> integerGenericity = new Genericity<>(1222);

        show(numberGenericity);
        //编译报错
        //show(integerGenericity);
    }

    private static void show(Genericity<Number> genericity) {
        System.out.println(genericity.getT());
    }
}

无法给型参为 Genericity<Number> 的方法传入 Genericity<Integer> 实参,即使 Integer 是 Number 的子类。

同一个泛型可以对应多个版本(因为参数类型是不确定的),不同版本的泛型类实例是不兼容的。

这时候可以用 ? 类型通配符来解决:

private static void show(Genericity<?> genericity) {
    System.out.println(genericity.getT());
}

**注意:**这里的 ? 代替具体的类型实参,而不是类型型参!

由此,又进入了上界通配符和下界通配符。

上通配符:
private static void show(Genericity<? extends Number> genericity) {
    System.out.println(genericity.getT());
}

表示传入的实参必须是 Number 或 Number 的子类。

下界通配符:
private static void show(Genericity<? super Integer> genericity) {
    System.out.println(genericity.getT());
}

表示传入的实参必须是 Integer 或者其父类。

但是需要注意的是,泛型的上下边界添加,必须与泛型的声明在一起:

//错误声明    
private <T> T  show(Genericity<T extends Integer> genericity) {
        
}
//正确声明
private <T extends Integer> T  show(Genericity<T> genericity) {
	return genericity.getT();
}

泛型的上下边界添加,必须与泛型的声明在一起.

关于泛型的擦除机制,可以参考这篇博文:
Java泛型类型擦除以及类型擦除带来的问题