泛型从入门到精通

Lou.Chen
大约 8 分钟

java泛型详解

一、概述:

​ 泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。

​ 泛型的本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。

二、 特性

public static void main(String[] args) {
        List<String> stringArrayList = new ArrayList<String>();
        List<Integer> integerArrayList = new ArrayList<Integer>();

        //class java.util.ArrayList
        Class classStringArrayList = stringArrayList.getClass();
        //class java.util.ArrayList
        Class classIntegerArrayList = integerArrayList.getClass();
        if(classStringArrayList.equals(classIntegerArrayList)){
            System.out.println("类型相同");
        }
}

我们可以通过以上结果发现类型相同。在编译之后程序会采取去泛型化的措施。也就是说Java中的泛型,只在编译阶段有效。在编译过程中,正确检验泛型结果后,会将泛型的相关信息擦出,并且在对象进入和离开方法的边界处添加类型检查和类型转换的方法。也就是说,泛型信息不会进入到运行时阶段。

对此总结成一句话:泛型类型在逻辑上看以看成是多个不同的类型,实际上都是相同的基本类型。

三、泛型的使用

3.1泛型类

声明形式:

Class 类名称<T,E,V...>{
    
}
/**
 * 声明泛型参数类型必须在类后面声明。
 * @param <T> 泛型参数T的类型
 */
public class Generics<T> {
    
    private T key;

    public Generics(T key) {
        this.key=key;
    }

    public T getKey() {
        return key;
    }
}

使用:

/**
 * 声明泛型参数类型必须在类后面声明。
 * @param <T> 泛型参数T的类型
 */
public class Generics<T> {

    private T key;

    public Generics(T key) {
        this.key=key;
    }

    public T getKey() {
        return key;
    }

    public static void main(String[] args) {
        Generics g1 = new Generics(2);
        Generics g2 = new Generics("hello");
        Generics g3 = new Generics(false);
        System.out.println(g1.getKey()); //2
        System.out.println(g2.getKey()); //hello
        System.out.println(g3.getKey()); //false
    }
}

如果不传入泛型类型实参的话,在泛型类中使用泛型的方法或成员变量定义的类型可以为任何的类型。

注意:
  • 泛型的类型参数只能是类类型,不能是简单类型。

  • 不能对确切的泛型类型使用instanceof操作。如下面的操作是非法的,编译时会出错。

    if(ex_num instanceof Generic<Number>){   
    } 
    
3.2泛型接口

泛型接口与泛型类的定义及使用基本相同。泛型接口常被用在各种类的生产器中

public interface Generator<T> {
    T next();
}
①实现泛型接口,未传入泛型实参:

必须同时在实现类和接口类上同时声明T

public class FruitGenerator<T> implements Generator<T> {
    @Override
    public T next() {
        return null;
    }
}
②实现泛型接口,传入泛型实参

接口类上声明实参类型String

public class FruitGenerator implements Generator<String> {
    @Override
    public String next() {
        return null;
    }
}
3.3泛型通配符/泛型上下界
①泛型通配符<?>

反例:

public class Generics<T> {

    private T key;

    public Generics(T key) {
        this.key=key;
    }

    public T getKey() {
        return key;
    }
}
public class Test {
    public static void main(String[] args) {
        Generics<Integer> i = new Generics<Integer>(10);
        showKeyValue(i); //报错
    }

    static void showKeyValue(Generics<Number> obj) {
        System.out.println("泛型值:"+obj.getKey());
    }
}

尽管Integer继承Number,但是在泛型中同一种泛型T可以对应多个版本(因为参数类型是不确定的),不同版本的泛型类实例是不兼容的

要使能够实现Number下的子类的泛型也能传入:Generics<? extends Number> obj

public class Test {
    public static void main(String[] args) {
        Generics<Integer> i = new Generics<>(10);
        showKeyValue(i);
    }

    static void showKeyValue(Generics<? extends Number> obj) {
        System.out.println("泛型值:"+obj.getKey());
    }
}

类型通配符一般是使用 ?代替具体的类型实参,注意了,此处 ? 是类型实参,而不是类型形参

实际参数String,Double,Integer...

形式参数T,E,V...

当操作类型时,不需要使用类型的具体功能时,只使用Object类中的功能。那么可以用 ? 通配符来表未知类型。

②泛型上下界<? extends T>:
//泛型类
public class Generics<T extends Number> {
    void showKeyValue(Generics<T> obj) {
    }
    
    //泛型方法 泛型方法独立于类,所以要重新声明泛型类型 
    public <T extends Number> T showKeyName(Generics<T> container,T t){
        return t;
    }
}
③泛型<? extends T >与 <? super T>区别

参考:https://www.zhihu.com/question/20400700open in new window

**<? extends T>:**不能存操作,只能取操作(T作为返回值) ,这叫get原则

<? super T>: 可以存(存T的子类)操作,取操作只能是用Object类型来接收,这叫put原则

public class Demo {
    public static void main(String[] args) {
        //假如变量p的实例化对操作者是未知的
        // 即 p=new Plate<>(new Apple());
        //   p=new Plate<>(new Banner());
        //    ...
        //    只要是Fruit的子类或者Fruit都可实例化
        Plate<? extends Fruit> p=new Plate<>(new Banner());
        //设置值的时候,无法确定子类到底是哪个,是apple或者banner,两者并无关系,即不能相互转化类型,即为了防止冲突,Jdk直接不允许设置值
        //拿值的时候,已知所有实例化的对象都是Fruit或者Fruit的子类,所以可以直接用Fruit接收
        Fruit p1 = p.get();

        //假如变量s1的实例化对操作者是未知的
        // 即 s1=new Plate<>(new Food());
        //    s1=new Plate<>(new Fruit());
        //    ...
        //    只要是Apple的基类或者Apple都可实例化
        Plate<? super Apple> s1=new Plate<>(new Food());
        //当需要拿到s1的实例时,不确定,Apple的父类是哪个,为了保证转化不报错,即只能用Object接收,因为Apple是最小的类,如果实例是Apple的基类,则Apple不能强转为基类
        Object object = s1.get();
        //因为要保证所有的实例对象都能被Apple类或者Apple的父类接收,即限定设置值的时候,即理想状态为从最下类Apple开始,即规定必须是 Apple或者Apple的子类设值
        s1.set(new RedApple());
    }
}

class Food {

}
class Fruit extends Food{

}
class Banner extends Fruit{

}
class Apple extends Fruit{

}
class RedApple extends Apple {

}
class Plate<T>{
    private T item;
    public Plate(T t){item=t;}
    public void set(T t){item=t;}
    public T get(){return item;}
}
3.4泛型方法

泛型类:

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

泛型方法:

  • 是在调用方法的时候指明泛型的具体类型 。
①声明泛型方法:
  • 只有在方法中定义的泛型才能算是泛型方法,否则不算。即在方法中有 <T,E....>

  • 泛型方法的类型只需在方法中定义即可

  • 类中声明的泛型和方法中声明的泛型相互独立。无论名称是否相同。互不影响

public class Generics{
    public <T,E> E getkey(E eKey,T tKey) {
        return eKey;
    }
}

例如:

public class Generics<T> {

    private T key;
    public Generics(T key) {
        this.key=key;
    }
    //非泛型方法
    public void showkey(T genericObj){
    }
    //非泛型方法
    public T getKey() {
        return key;
    }
    //非泛型方法
    public void showKeyValue1(Generics<Number> obj){
    }
    //非泛型方法
    public void showKeyValue2(Generics<?> obj){
    }

//    --------------------------------------------

    //泛型方法
//    只要在方法中声明<T,K...>泛型类型即为泛型方法,否则不是。
    public <T,K> K showKeyName(Generics<T> container,K k){
        return k;
    }
}
②类中的泛型方法:
public class Fruit {
    @Override
    public String toString() {
        return "fruit";
    }
}
class Apple extends Fruit{
    @Override
    public String toString() {
        return "apple";
    }
}
class Person{
    @Override
    public String toString() {
        return "Person";
    }
}
public class Generics<T> {
    //非泛型方法
    public void show_1(T t){
        System.out.println(t.toString());
    }

    //泛型方法 泛型E独立于类中声明的泛型
    public <E> void show_3(E t){
        System.out.println(t.toString());
    }

    //泛型方法 泛型T独立于类中声明的泛型T
    public <T> void show_2(T t){
        System.out.println(t.toString());
    }

    public static void main(String[] args) {
        Apple apple=new Apple();
        Person person=new Person();
        Generics<Fruit> fruitGenerics=new Generics<>();
        fruitGenerics.show_1(apple);
        //编译失败  因为在类中声明的泛型为实参确定之后,传入的必须为该类型
//        fruitGenerics.show_1(person);

        //在方法声明的泛型会重新定义,这个泛型T是全新的泛型。尽管泛型名字一样。方法中声明的泛型独立于类中声明的泛型
        fruitGenerics.show_2(apple);
        fruitGenerics.show_2(person);
		//在方法声明的泛型会重新定义,这个泛型T是全新的泛型。尽管泛型名字一样。方法中声明的泛型独立于类中声明的泛型
        fruitGenerics.show_3(apple);
        fruitGenerics.show_3(person);
    }
}
③泛型方法与可变参数
public <T> void printMsg( T... args){
    for(T t : args){
       System.out.println(t);
    }
}

//调用
printMsg("111",222,"aaaa","2323.4",55.55);
④静态方法与泛型

静态方法无法访问类上定义的泛型;如果静态方法操作的引用数据类型不确定的时候,必须要将泛型定义在方法上。

public class Generics<T> {

    /**
     * 静态方法使用泛型必须在方法中定义泛型,在类上定义无效
     * @param t
     * @param <T>
     */
    public static <T> void show(T t){
    }
    
    static <T> void showKeyValue(Generics<T> obj) {
    }
}
⑤ 泛型方法总结

泛型方法能使方法独立于类而产生变化,即方法上定义的泛型独立于类上的泛型

无论何时,如果你能做到,你就该尽量使用泛型方法。也就是说,如果使用泛型方法将整个类泛型化,那么就应该使用泛型方法。另外对于一个static的方法而已,无法访问泛型类型的参数。所以如果static方法要使用泛型能力,就必须使其成为泛型方法。