什么是泛型?

Java泛型(generic)是JDK5中引入的一个新特性,泛型提供了编译时类型安全监测机制。该机制允许程序在编译时检测非法的类型。泛型的本质是参数类型,即所操作的数据类型被指定为一个参数,注意泛型不存在与JVM虚拟机。

为什么使用泛型?

1.泛型具有更强的类型检查,可以增强编译时错误检测,减少因为类型问题引发的运行时异常。

2.泛型可以避免类型转换。

3.通过使用泛型,可以实现泛型算法,这些算法可以处理不同类型的集合,可以自定义,并且类型安全且易于阅读,可以增加代码复用性。

泛型的使用

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

泛型类

泛型类型用于类的定义中,就称之为泛型类,泛型类的基本写法如下:

public class Generic<T> {}
public class Generic<T1,T2,...,Tn> {}

在类名之后,类型参数部分由尖括号<>分隔。它指定了类型参数(也称为类型变量)T,当然了也可以定义多个参数。

代码如下:

/**
 * 在实例化泛型类时,必须指定T的具体类型
 * @param <T> 此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
 */
public class Generic<T> {
    //key这个成员变量的类型为T,T的类型由外部指定
    private T key;

    //泛型构造方法形参key的类型也为T,T的类型由外部指定
    //这里需要注意的是构造器名是Generic ,而不是Generic<T>。
    public Generic(T key) {
        this.key = key;
    }
    
    //泛型方法getKey的返回值类型为T,T的类型由外部指定
    public T getKey() {
        return key;
    }
}

泛型接口

泛型接口和泛型类的定义使用基本上是一样的。

代码如下:

//定义一个泛型接口
public interface Generic<T> {
    public T add(T t);
}

当实现泛型接口的类,未传入泛型实参时,与泛型类的定义相同,在声明类的时候,需将泛型的声明也一起加到类中。

public class MyGeneric<T> implements Generic<T> {
    @Override
    public T add(T t) {
        return t;
    }

    public static void main(String[] args) {
        Generic<String> list = new MyGeneric<>();
        list.add("aaaa");
        Generic<Integer> list1 = new MyGeneric<>();
        list1.add(0);
    }
}

在实现类实现泛型接口时,如已将泛型类型传入实参类型,则所有使用泛型的地方都要替换成传入的实参类型。

public class MyGeneric implements Generic<String> {
    @Override
    public String add(String s) {
        return s;
    }
}

泛型方法

泛型方法,是在调用方法的时候指明泛型的具体类型。基本写法如下:

/**
 * @param <T>  这个<T>非常重要,可以理解为声明此方法为泛型方法。
 *           只有声明了<T>的方法才是泛型方法。泛型类中的使用了泛型的成员方法并不是泛型方法。
 */
public <T> void Generic(T t) {}

泛型通配符

称为通配符的问号(?)表示未知类型。通配符可以在多种情况下使用:作为参数,字段或局部变量的类型;有时作为返回类。通配符从不用作泛型方法调用,泛型类实例创建或超类型的类型参数。

List<?> list = new ArrayList<String>();
    //我们可以正常获取list中的元素,不会引起编译错误
    Object object = list.get(0);
    //调用add方法会引起编译错误
    list.add("字符串");

这种带通配符的 List仅表示它是各种泛型List的父类,并不能把元素添加到其中,否则会引起编译错误。因为程序无法确定list集合中的元素类型,所以不能向其中添加对象,而用get()方法来返回List<?>集合指定索引处的元素,其返回值是一个未知类型,但肯定是一个Object,因此返回值赋给一个Object类型的变量是可以的。

泛型中的上界(extend)和下界(super)

1. 泛型中上界和下界的定义

上界 <? extend Class>

下界 <? super Class>

2.上界和下界的特点

上界的list只能get,不能add(确切地说不能add出除null之外的对象,包括Object)

下界的list只能add,不能get

3.这些特点的原因

上界 <? extend Class> ,表示所有继承Class的子类,但是具体是哪个子类,无法确定,所以调用add的时候,要add什么类型,谁也不知道。但是get的时候,不管是什么子类,不管追溯多少辈,肯定有个父类是Class,所以,我都可以用最大的父类Class接着,也就是把所有的子类向上转型为Class。

下界 <? super Class>,表示Class的所有父类,包括Class,一直可以追溯到老祖宗Object 。那么当我add的时候,我不能add Class的父类,因为不能确定List里面存放的到底是哪个父类。但是我可以add Class及其子类。因为不管我的子类是什么类型,它都可以向上转型为Class及其所有的父类甚至转型为Object 。但是当我get的时候,Class的父类这么多,我用什么接着呢,除了Object,其他的都接不住。

所以,归根结底可以用一句话表示,那就是编译器可以支持向上转型,但不支持向下转型。具体来讲,我可以把Class子类对象赋值给Class的引用,但是如果把Class对象赋值给Class子类的引用就必须得用cast进行类型强转。

泛型限制

public class Generic<T> {

    public static void main(String[] args) {
        //不能用基本类型实例化类型参数
        //Generic<double> mDoubleGeneric=new Generic<>();
        Generic<Double> mDoubleGeneric = new Generic<>();

        //运行时类型查询只适用于原始类型
        //java编译后会进行泛型擦除操作,只会留下原始类型Generic
        //if (mDoubleGeneric instanceof Generic<Double>)

        //泛型类的静态上下文中类型变量失效
        //静态域或者方法不能引用类型变量
        //private static T instance
        //静态方法本身就是泛型方法就行
        //不能在静态域或方法中引用类型变量。
        //因为泛型是要在对象创建的时候才知道是什么类型的,
        //而对象创建的代码执行先后顺序是static的部分,
        //然后才是构造函数等等。所以在对象初始化之前static的部分已经执行了,
        //如果你在静态部分引用的泛型,那么毫无疑问虚拟机根本不知道是什么东西,
        //因为这个时候类还没有初始化。
        //private static <T> T getInstance(){}

        //不能创建参数化类型的数组,但可以声明
        //Generic<Double>[] generic=new Generic<Double>[10];
        Generic<Double>[] generic;

        //不能实例化类型变量
        //Generic<T> t = new Generic<T>();

        //不能捕获泛型类的实例
        /*泛型类不能extends Exception/Throwable*/
        //private class Problem<T> extends Exception;

        /*不能捕获泛型类对象*/
        //    public <T extends Throwable> void doWork(T x){
        //        try{
        //
        //        }catch(T x){
        //            //do sth;
        //        }
        //    }
    }

    public <T extends Throwable> void doWorkSuccess(T x) throws T {
        try {

        } catch (Throwable e) {
            throw x;
        }
    }

}
  • 无法实例化具有基本类型的泛型类型
  • 无法创建类型参数的实例
  • 无法声明类型为类型参数的静态字段
  • 无法创建参数化类型的数组
  • 无法创建捕获或抛出参数化类型的对象
  • 无法重载每个重载的形式参数类型都擦除为相同原始(raw)类型的方法。
  • 无法将Castsinstanceof与参数化类型一起使用