Java 语言高级特性--泛型

一  为什么我们需要泛型?

通过两段代码我们就可以知道为何我们需要泛型
public class NonGeneric {

    public int addInt(int x, int y) {
        return x + y;
    }

    public float addFloat(float x, float y) {
        return x + y;
    }

    //double

    public static void main(String[] args) {
        NonGeneric nonGeneric = new NonGeneric();
        System.out.println(nonGeneric.addInt(1, 2));
        System.out.println(nonGeneric.addFloat(1f, 2f));
    }

}
实际开发中,经常有数值类型求和的需求,例如实现 int 类型的加法 , 有时候还需要实现 long 类型的求和 , 如果还需要 double 类型的求和,需要重新在重载一个输入是 double 类型的 add 方法
public class NonGeneric2 {
    public static void main(String[] args) {
        List<String> list = new ArrayList();
        list.add("chric");
        list.add("OK");
        list.add(100);

        for (int i = 0; i < list.size(); i++) {
            String name = list.get(i); // 1
            System.out.println("name:" + name);
        }
    }
}

// 
java: 对于add(int), 找不到合适的方法
    方法 java.util.Collection.add(java.lang.String)不适用
      (参数不匹配; int无法转换为java.lang.String)
    方法 java.util.List.add(java.lang.String)不适用
      (参数不匹配; int无法转换为java.lang.String)

 

定义了一个 List 类型的集合,先向其中加入了两个字符串类型的值,随后加
入一个 Integer 类型的值。这是完全允许的,因为此时 list 默认的类型为 Object
类型。在之后的循环中,由于忘记了之前在 list 中也加入了 Integer 类型的值或
其他编码原因,很容易出现类似于 //1 中的错误。因为编译阶段正常,而运行时
会出现 “java.lang.ClassCastException” 异常。因此,导致此类错误编码过程中不易
发现。
在如上的编码过程中,我们发现主要存在两个问题:
1. 当我们将一个对象放入集合中,集合不会记住此对象的类型,当再次从集
合中取出此对象时,改对象的编译类型变成了 Object 类型,但其运行时类型任
然为其本身类型。
2. 因此, //1 处取出集合元素时需要人为的强制类型转化到具体的目标类型,
且很容易出现 “java.lang.ClassCastException” 异常。
所以泛型的好处就是:
  • 适用于多种数据类型执行相同的代码
  • 泛型中的类型在使用时指定,不需要强制类型转换

1.1  泛型简介

之前关于类的学习中我们知道,一个类中可以定义它的属性以及方法,在那里我们定义类的属性时不同的属性我们采用的是不同的数据类型,这就要求我们对每一个数据的类型进行声明操作。但是我们想到这样一个问题,如果这个类有无数个属性的时候我们怎么对这个类的属性进行定义呢?显然,将这个类的所有属性进行枚举型的定义是不现实的,因此这就要求我们找到属性定义的共同特性。这时Object出现在了我们面前,用这个关键字可以对不同类型的属性进行定义接收,但是这样我们也不知道具体的属性的类型,在涉及类型转换时也很容易出现错误,并且这钟错误一般出现在运行阶段。为了解决这个难题,让代码拥有更高的可读性与更高的安全性,java中提出了泛型的概念。

        泛型的本质是数据类型的参数化,这么说显得比较抽象。其实我们可以这样理解,泛型就是用一个指定的标识符来代表具体的数据类型,将具体的数据类型转换为参数,在创建类、接口或者方法时,我们都用指定的标识符来代替具体的数据类型,这样就免去同一个类、接口或者方法对不同的数据类型进行操作时需要重新编写程序的问题。在使用由泛型创建的方法、接口或者类时,只要我们进行一定程度的说明,系统就会自动判断数据的类型,这样也免去了类型转换时可能出现的错误,提高了代码的安全性。

        所以其实上,泛型与我们之前学的带参方法有着异曲同工之妙。在带参方法中,我们用形式参数来代表传入方法的具体数据,但是在方法创建时我们并不知道传入的实际参数是什么,只有在使用这个方法的时候才知道要传入一个什么样的数据。泛型也是这样,我们用一个标识符来代表在类、方法或者接口中可能出现的数据类型,但具体会出现什么样的数据类型只有在使用一个方法、实例化一个对象或者实现一个接口的时候才知道。

        除了上面谈到的关于泛型的理解的问题,我们还要注意到,虽然程序员在编写程序的时候用了泛型参数来定义类型,但是在编译的时候泛型会被去掉,编译结束后类型参数会被替换成Object。涉及到类型转换时,任然是普通的强制类型转换。我们把这种类型参数在编译后消失的现象叫做类型擦除。虽然目前通过反编译手段任然能够看到编译前的类型参数,但是着并不代表编译后生成的class字节码文件中存在着泛型中的类型信息。

1.2  泛型的定义

从理论上来说,泛型可以用任何一个符合规则的标志符来定义,但是那样会有很大概率造成不小的麻烦,因此一般来说我们会用java中指定的几个标识符来定义泛型。这些标识符分别是:E、T、K、V、N、?。

       这些标识符每一个都代表了不同的含义。

        E——Element,在容器中使用,代表的是容器中的元素。

        T——Type,表示的是普通的java类。

        K——Key,表示键,例如Map中的Key键。

        V——Value,表示值。

        N——Number,表示数值类型。

        ?——表示不确定的java类型。

不难发现,N代表的是Number类,而在包装类中我们提到过,Number类是六种数值类型对应的包装类的父类,也就是说定义泛型时只能用引用类型,不能使用基本数据类型。

二  泛型类和泛型接口

        泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参, 然后调用此方法时传递实参。那么参数化类型怎么理解呢? 顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参 数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时
传入具体的类型(类型实参)。 泛型的本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定 的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中,操作的
数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被 称为泛型类、泛型接口、泛型方法。
引入一个类型变量 T (其他大写字母都可以,不过常用的就是 T E K V
等等),并且用 <> 括起来,并放在类名的后面。泛型类是允许有多个类型变量的。

2.1   泛型类

public class NormalGeneric<K> {
    private K data;

    public NormalGeneric() {
    }

    public NormalGeneric(K data) {
        this.data = data;
    }

    public K getData() {
        return data;
    }

    public void setData(K data) {
        this.data = data;
    }

    public static void main(String[] args) {
        NormalGeneric<String> normalGeneric = new NormalGeneric<>();
        normalGeneric.setData("OK");
        //normalGeneric.setData(1);
        System.out.println(normalGeneric.getData());
        NormalGeneric normalGeneric1 = new NormalGeneric();
        normalGeneric1.setData(1);
        normalGeneric1.setData("dsf");
    }
}
public class NormalGeneric2<T,K> {
    private T data;
    private K result;

    public NormalGeneric2() {
    }

    public NormalGeneric2(T data) {
        this();
        this.data = data;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public NormalGeneric2(T data, K result) {
        this.data = data;
        this.result = result;
    }

    public K getResult() {
        return result;
    }

    public void setResult(K result) {
        this.result = result;
    }

    public static void main(String[] args) {
        NormalGeneric2<String,Integer> normalGeneric2 = new NormalGeneric2<>();
        normalGeneric2.setData("OK");
        System.out.println(normalGeneric2.getData());
        normalGeneric2.setResult(23);
        System.out.println(normalGeneric2.getResult());

    }
}

2.2  泛型接口

        泛型接口与泛型类的定义基本相同。 对于泛型接口,它的声明方式为: public interface 接口名 <泛型>{泛型定义的抽象方法}。这个声明方式和泛型类的声明方式基本一致。在定义了泛型接口后,在实现这个泛型接口的时候,要求我们指定泛型所代表的具体类型。比如在下面的这个演示代码中,定义了泛型抽象接口Igeneric,用泛型T来代表数据类型,因此在抽象方法中就可以使用泛型T来代表方法返回的的类型以及方法中需要传入的参数的类型。这里要注意泛型接口定义时的泛型与抽象泛型方法中使用的泛型的关系。

        在泛型接口Igeneric的实现接口IgenericIme中,我们给定了泛型T的具体类型为String,这时如果通过实现类IgenericIme来创建对象,那么创建的对象中的数据类型就是String类。在这里就必须说到实例化对象的不同方式——通过接口实例化对象以及通过实现类实例化对象。在通过泛型接口的的实现类来实例化对象的时候,它的实例化方式与普通类的实例化没有区别,比如在下面演示代码中的igenericIme。但是当我们用泛型接口来实例化对象的时候就要特别注意,这种实例化方式要指定泛型所代之的类型并且还要指定特定的实现类,它的语法格式为:泛型接口名称<泛型指代的类型> 对象名称 = new 泛型接口的实现类名称();。就像演示代码中的对象igeneric以及对象igeneric1一样,并且要注意,尖括号中的泛型指代的类型要和接口的实现类中的保持一致,如果将对象igeneric中的泛型指代的名称输入为Integer,那么因为和后面的实现类中的泛型不同,所以程序无法通过编译。

public interface Generator<T> {
    public T next();
}
而实现泛型接口的类,有两种实现方法:
1 、未传入泛型实参时:
public class ImplGenerator<T> implements Generator<T> {
    private T data;

    @Override
    public T next() {
        return data;
    }
}
new 出类的实例时,需要指定具体类型:
class Test {
    public static void main(String[] args) {
        ImplGenerator<String> stringImplGenerator = new ImplGenerator<>();
    }
}
2 、传入泛型实参
public class ImplGenerator2 implements Generator<String> {
    @Override
    public String next() {
        return "OK";
    }
}
new 出类的实例时,和普通的类没区别
class TestGenerator {
    public static void main(String[] args) {
        ImplGenerator2 implGenerator2 = new ImplGenerator2();
    }
}

2.2  泛型方法

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

 静态泛型方法与非静态泛型方法
        在之前说的泛型类以及泛型接口的部分能够发现,我们定义在类和接口上的泛型在它们的方法中仍然可以使用,但是有时我们需要的仅仅是在一个指定的方法上使用泛型而不需要在整个类和接口上使用泛型,因此泛型方法的概念就被提出了。泛型方法指的是将方法中的参数类型定义为泛型,以便在调用时接收不同的参数类型的现象。泛型方法可以分为非静态泛型方法和静态泛型方法。在定义泛型方法时,参数类型一般放到返回值前面,并且需要说明的是如果返回值类型不是void的情况下,返回值类型仍然定义为泛型。此外,调用泛型方法时不需要像泛型类和泛型接口那样指定泛型的类型,系统能够自动判断出泛型的类型,因此泛型方法的调用和普通方法没有区别。

  •   非静态泛型方法的语法结构:

        public <泛型表示符号> void 方法名(泛型表示符号 形式参数){}

        public <泛型表示符号> 泛型表示符号 方法名(泛型表示符号 形式参数){}

  •   静态泛型方法的语法够:

        public static <泛型表示符号> void 方法名(泛型表示符号 形式参数){}

        public static <泛型表示符号> 泛型表示符号 方法名(泛型表示符号 形式参数){}

        对于静态泛型方法和非静态泛型方法,除了在定义的格式上存在的细微区别外,还要特别注意,静态泛型方法时无法访问类上定义的泛型的,也就是说如果我们定义了一个泛型类,在这个类中定义静态泛型方法时必须在static后面定义泛型,而不能直接使用类上定义的泛型。

泛型方法与可变参数
        在泛型方法中和普通方法一样,传入的参数也可以是任意类型,包括像数组这种可变参数依旧可以传入方法,不过在泛型方法中用泛型定义可变参数的类型时要采用特殊的语法结构进行说明,它的具体语法格式为:

        public  (static) 泛型表示符号 泛型表示符号(void) 方法名 (泛型表示符号. . . 可变参数名称){}

在这个语法结构中要特别注意泛型表示符号与可变参数名称之间的符号. . .  ,它是用来区分普通参数和可变参数的标志。 

public class GenericMethod {

    public <T> T genericMethod(T...a){
        return a[a.length/2];
    }

    public void test(int x,int y){
        System.out.println(x+y);
    }

    public static void main(String[] args) {
        GenericMethod genericMethod = new GenericMethod();
        genericMethod.test(23,343);
        System.out.println(genericMethod.<String>genericMethod("mark","av","lance"));
        System.out.println(genericMethod.genericMethod(12,34));
    }
}
public class GenericMethod2 {
    //这个类是个泛型类,在上面已经介绍过
    public class Generic<T>{
        private T key;

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

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

        /**
         * 这个方法显然是有问题的,在编译器会给我们提示这样的错误信息"cannot reslove symbol E"
         * 因为在类的声明中并未声明泛型E,所以在使用E做形参和返回值类型时,编译器会无法识别。
         */
//        public E setKey(E key){
//            this.key = key;
//        }
    }

    /**
     * 这才是一个真正的泛型方法。
     * 首先在public与返回值之间的<T>必不可少,这表明这是一个泛型方法,并且声明了一个泛型T
     * 这个T可以出现在这个泛型方法的任意位置.
     * 泛型的数量也可以为任意多个
     *    如:public <T,K> K showKeyName(Generic<T> container){
     *        ...
     *        }
     */


    //这也不是一个泛型方法,这就是一个普通的方法,
    // 只是使用了Generic<Number>这个泛型类做形参而已。
    public void show(Generic<Number> obj){

    }

    /**
     * 这个方法是有问题的,编译器会为我们提示错误信息:"UnKnown class 'E' "
     * 虽然我们声明了<T>,也表明了这是一个可以处理泛型的类型的泛型方法。
     * 但是只声明了泛型类型T,并未声明泛型类型E,因此编译器并不知道该如何处理E这个类型。
     */
//    public <T,E> T show(E ab){
//        //
//    }

    /**
     * 这个方法也是有问题的,编译器会为我们提示错误信息:"UnKnown class 'T' "
     * 对于编译器来说T这个类型并未项目中声明过,因此编译也不知道该如何编译这个类。
     * 所以这也不是一个正确的泛型方法声明。
     }
     */
//    public void show(T obj){
//
//    }

    public static void main(String[] args) {


    }
}
public class GenericMethod3 {
    static class Fruit{
        @Override
        public String toString() {
            return "fruit";
        }
    }

    static class Apple extends Fruit{
        @Override
        public String toString() {
            return "apple";
        }
    }

    static class Person{
        @Override
        public String toString() {
            return "Person";
        }
    }

    static class GenerateTest<T>{
        //普通方法
        public void show_1(T t){
            System.out.println(t.toString());
        }

        //在泛型类中声明了一个泛型方法,使用泛型E,这种泛型E可以为任意类型。
        // 可以类型与T相同,也可以不同。
        //由于泛型方法在声明的时候会声明泛型<E>,因此即使在泛型类中并未声明泛型,
        // 编译器也能够正确识别泛型方法中识别的泛型。
        public <E> void show_3(E t){
            System.out.println(t.toString());
        }

        //在泛型类中声明了一个泛型方法,使用泛型T,
        // 注意这个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();

        GenerateTest<Fruit> generateTest = new GenerateTest<>();
        generateTest.show_1(apple);
        //generateTest.show_1(person);

        generateTest.show_2(apple);
        generateTest.show_2(person);

        generateTest.show_3(apple);
        generateTest.show_3(person);
    }
}

三  限定类型变量

有时候,我们需要对类型变量加以约束,比如计算两个变量的最小,最大值。
    public static <T> T min(T a, T b) {
        if (a.comapareTo(b) > 0) {
            return a;
        } else {
            return b;
        }
    }
请问,如果确保传入的两个变量一定有 compareTo 方法?那么解决这个问
题的方案就是将 T 限制为实现了接口 Comparable 的类
    public static <T extends Comparable> T min(T a, T b) {
        if (a.compareTo(b) > 0) {
            return a;
        } else {
            return b;
        }
    }
T extends Comparable
T 表示应该绑定类型的子类型, Comparable 表示绑定类型,子类型和绑定类
型可以是类也可以是接口。
如果这个时候,我们试图传入一个没有实现接口 Comparable 的类的实例,
将会发生编译错误。
    public static <T extends ArrayList & Comparable> T min(T a, T b) {
        if (a.compareTo(b) > 0) {
            return a;
        } else {
            return b;
        }
    }

    public static <T extends Serializable & Comparable> T min(T a, T b) {
        if (a.compareTo(b) > 0) {
            return a;
        } else {
            return b;
        }
    }
同时 extends 左右都允许有多个,如 T,V extends Comparable & Serializable
注意限定类型中,只允许有一个类,而且如果有类,这个类必须是限定列表
的第一个。
这种类的限定既可以用在泛型方法上也可以用在泛型类上。

四  泛型中的约束和局限性

public class Restrict<T> {
    private T data;

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

    // 泛型类的静态上下文中类型变量失效
    //静态域或者方法里不能引用类型变量
    //private static T instance;
    //静态方法 本身是泛型方法就行
    //private static <T> T getInstance(){}


    public static void main(String[] args) {
//         不能用基本类型实例化类型参数  这种不允许
//        Restrict<double> restrict = new Restrict<>();

        //Restrict<double>
        Restrict<Double> restrict = new Restrict<>();


//          运行时类型查询只适用于原始类型
//        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());
        System.out.println(restrictString.getClass().getName());

        //  不能创建参数化类型的数组
        Restrict<Double>[] restrictArray;  // 可以
        //Restrict<Double>[] restricts = new Restrict<Double>[10]; // 不允许
//        ArrayList<String>[] list1 = new ArrayList<String>[10];   // 不允许
        ArrayList<String>[] list2 = new ArrayList[10];   //可以

    }

}
public class ExceptionRestrict {


    //  不能捕获泛型类的实例
    /*泛型类不能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;
        }
    }
}

4.1 不能用基本类型实例化类型参数

//         不能用基本类型实例化类型参数  Restrict<double>这种不允许
//        Restrict<double> restrict = new Restrict<>();

        //Restrict<double>
        Restrict<Double> restrict = new Restrict<>();

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

//          运行时类型查询只适用于原始类型
//        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());
        System.out.println(restrictString.getClass().getName());

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

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

    // 泛型类的静态上下文中类型变量失效
    //静态域或者方法里不能引用类型变量
    //private static T instance;
    //静态方法 本身是泛型方法就行
    //private static <T> T getInstance(){}

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

        //  不能创建参数化类型的数组
        Restrict<Double>[] restrictArray;  // 可以
        //Restrict<Double>[] restricts = new Restrict<Double>[10]; // 不允许
//        ArrayList<String>[] list1 = new ArrayList<String>[10];   // 不允许
        ArrayList<String>[] list2 = new ArrayList[10];   //可以

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 x){
//        try{
//
//        }catch(T x){
//            //do sth;
//        }
//    }

    //  但是这样可以:
    public <T extends Throwable> void doWorkSuccess(T x) throws T{
        try{

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

五  泛型类型的继承规则

现在我们有一个类和子类
public class Employee {
    private String firstName;
    private String secondName;

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getSecondName() {
        return secondName;
    }

    public void setSecondName(String secondName) {
        this.secondName = secondName;
    }
}

public class Worker extends Employee {
}

有一个泛型类        
public class Pair<T> {

    private T one;
    private T two;

    public T getOne() {
        return one;
    }

    public void setOne(T one) {
        this.one = one;
    }

    public T getTwo() {
        return two;
    }

    public void setTwo(T two) {
        this.two = two;
    }

    private static <T> void set(Pair<Employee> p){
    }

    public static void main(String[] args) {
        //Pair<Employee>和Pair<Worker>没有任何继承关系
        Pair<Employee> employeePair = new Pair<>();
        Pair<Worker> workerPair = new Pair<>();

        Employee employee = new Worker();
        //Pair<Employee> employeePair2 = new Pair<Worker>();


        Pair<Employee> pair = new ExtendPair<>();


        set(employeePair);
        //set(workerPair);
    }

    /*泛型类可以继承或者扩展其他泛型类,比如List和ArrayList*/
    private static class ExtendPair<T> extends Pair<T>{

    }
}
请问 Pair<Employee> Pair<Worker> 是继承关系吗?
答案:不是,他们之间没有什么关系
     //Pair<Employee>和Pair<Worker>没有任何继承关系
        Pair<Employee> employeePair = new Pair<>();
        Pair<Worker> workerPair = new Pair<>();

        Employee employee = new Worker();
        //Pair<Employee> employeePair2 = new Pair<Worker>(); // 不允许
但是泛型类可以继承或者扩展其他泛型类,比如 List ArrayList
Pair<Employee> pair = new ExtendPair<>();
   /*泛型类可以继承或者扩展其他泛型类,比如List和ArrayList*/
    private static class ExtendPair<T> extends Pair<T>{

    }

六  通配符类型

正是因为前面所述的, Pair<Employee> Pair<Worker> 没有任何关系,如果
我们有一个泛型类和一个方法
public class GenericType<T> {
    private T data;

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }
}
public class Food {
}


public class Fruit extends Food {
    private String color;

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }
}

public class Apple extends Fruit {
}


public class Orange extends Fruit {
}


public class HongFuShi extends Apple {
}
public class WildChar{

    public static void print(GenericType<Fruit> p){
        System.out.println(p.getData().getColor());
    }

    public static void use(){
       GenericType<Fruit> a = new GenericType<>();
        print(a);
       GenericType<Orange> b = new GenericType<>();
        //print(b); // 这样不允许
    }

}
则会出现  //print(b); // 这样不允许
Required type:GenericType<Fruit>

Provided:GenericType<Orange>
为解决这个问题,于是提出了一个通配符类型 ?
有两种使用方式:
extends X 表示类型的上界,类型参数是 X 的子类
super X 表示类型的下界,类型参数是 X 的超类
这两种 方式从名字上来看,特别是 super ,很有迷惑性,下面我们来仔细辨
析这两种方法。

6.1  ? extends X

表示传递给方法的参数,必须是 X 的子类(包括 X 本身)
public class WildChar{

    public static void print2(GenericType<? extends Fruit> p){
        System.out.println(p.getData().getColor());
    }

    public static void use2(){
        GenericType<Fruit> a = new GenericType<>();
        print2(a);
        GenericType<Orange> b = new GenericType<>();
        print2(b);
        //print2(new GenericType<Food>());  // 不允许
        GenericType<? extends Fruit> c =  new GenericType<>();

        Apple apple =  new Apple();
        Fruit fruit = new Fruit();
        //c.setData(apple); // 不允许
        //c.setData(fruit);  // 不允许
        Fruit x = c.getData();
    }

}
但是对泛型类 GenericType 来说,如果其中提供了 get set 类型参数变量
的方法的话, set 方法是不允许被调用的,会出现编译错误
 //  print2(new GenericType<Food>());  // 不允许
Required type:GenericType<? extends Fruit>
Provided:GenericType<Food>
//  c.setData(apple); // 不允许
Required type:capture of ? extends Fruit
Provided:Apple
 //  c.setData(fruit);  // 不允许
Required type:capture of ? extends Fruit
Provided:Fruit
get 方法则没问题,会返回一个 Fruit 类型的值。
 Fruit x = c.getData();
为何?
道理很简单,? extends X 表示类型的上界,类型参数是 X 的子类,那么
可以肯定的说, get 方法返回的一定是个 X (不管是 X 或者 X 的子类)编译器是
可以确定知道的。但是 set 方法只知道传入的是个 X ,至于具体是 X 的那个子类,
不知道。
总结:主要用于安全地访问数据,可以访问 X 及其子类型,并且不能写入非
null 的数据。

6.2   ? super X

表示传递给方法的参数,必须是 X 的超类(包括 X 本身)
public class WildChar{

    public static void printSuper(GenericType<? super Apple> p){
        System.out.println(p.getData());
    }

    public static void useSuper(){
        GenericType<Fruit> fruitGenericType = new GenericType<>();
        GenericType<Apple> appleGenericType = new GenericType<>();
        GenericType<HongFuShi> hongFuShiGenericType = new GenericType<>();
        GenericType<Orange> orangeGenericType = new GenericType<>();
        printSuper(fruitGenericType);
        printSuper(appleGenericType);
//        printSuper(hongFuShiGenericType);
//        printSuper(orangeGenericType);


        //表示GenericType的类型参数的下界是Apple
        GenericType<? super Apple> x = new GenericType<>();
        x.setData(new Apple());
        x.setData(new HongFuShi());
        //x.setData(new Fruit());  // 不允许
        Object data = x.getData();

    }

}
但是对泛型类 GenericType 来说,如果其中提供了 get set 类型参数变量
的方法的话, set 方法可以被调用的,且能传入的参数只能是 X 或者 X 的子类
//x.setData(new Fruit());  // 不允许
Required type:capture of ? super Apple
Provided:Fruit
get 方法只会返回一个 Object 类型的值。
 Object data = x.getData();
super X 表示类型的下界,类型参数是 X 的超类(包括 X 本身),那
么可以肯定的说, get 方法返回的一定是个 X 的超类,那么到底是哪个超类?不
知道,但是可以肯定的说, Object 一定是它的超类,所以 get 方法返回 Object
编译器是可以确定知道的。对于 set 方法来说,编译器不知道它需要的确切类型,
但是 X X 的子类可以安全的转型为 X
总结:主要用于安全地写入数据,可以写入 X 及其子类型。

6.3 无限定的通配符 ?

表示对类型没有什么限制,可以把?看成所有类型的父类,如 Pair< ?>
比如:
ArrayList<T> al=new ArrayList<T>(); 指定集合元素只能是 T 类型
ArrayList<?> al=new ArrayList<?>(); 集合元素可以是任意类型,这种没有意义,
一般是方法中,只是为了说明用法。
在使用上:
getFirst() : 返回值只能赋给 Object ,;
void setFirst(?) setFirst 方法不能被调用, 甚至不能用 Object 调用;

七  虚拟机是如何实现泛型的?

泛型思想早在 C++ 语言的模板( Template )中就开始生根发芽,在 Java 语言 处于还没有出现泛型的版本时,只能通过 Object 是所有类型的父类和类型强制 转换两个特点的配合来实现类型泛化。,由于 Java 语言里面所有的类型都继承 于 java.lang.Object ,所以 Object 转型成任何对象都是有可能的。但是也因为有无 限的可能性,就只有程序员和运行期的虚拟机才知道这个 Object 到底是个什么 类型的对象。在编译期间,编译器无法检查这个 Object 的强制转型是否成功, 如果仅仅依赖程序员去保障这项操作的正确性,许多 ClassCastException 的风险 就会转嫁到程序运行期之中。
泛型技术在 C# Java 之中的使用方式看似相同,但实现上却有着根本性的 分歧,C# 里面泛型无论在程序源码中、编译后的 IL 中( Intermediate Language , 中间语言,这时候泛型是一个占位符),或是运行期的 CLR 中,都是切实存在的, List< int >与 List String >就是两个不同的类型,它们在系统运行期生成,有自 己的虚方法表和类型数据,这种实现称为类型膨胀,基于这种方法实现的泛型称 为真实泛型。
Java 语言中的泛型则不一样,它只在程序源码中存在,在编译后的字节码文 件中,就已经替换为原来的原生类型(Raw Type ,也称为裸类型)了,并且在相 应的地方插入了强制转型代码,因此,对于运行期的 Java 语言来说, ArrayList <int >与 ArrayList String >就是同一个类,所以泛型技术实际上是 Java 语言的 一颗语法糖,Java 语言中的泛型实现方法称为类型擦除,基于这种方法实现的泛 型称为伪泛型。
将一段 Java 代码编译成 Class 文件,然后再用字节码反编译工具进行反编译 后,将会发现泛型都不见了,程序又变回了 Java 泛型出现之前的写法,泛型类 型都变回了原生类型
public class Conflict {

    public static String method(List<String> stringList){
        System.out.println("List");
        return "OK";
    }

    public static Integer method(List<Integer> stringList){
        System.out.println("List");
        return 1;
    }

    /*
    * Signature (弱记忆)
    *
    * ? super xxxx
    *
    * */

}
上面这段代码是不能被编译的,因为参数 List Integer >和 List String >编 译之后都被擦除了,变成了一样的原生类型 List E >,擦除动作导致这两种方 法的特征签名变得一模一样。
由于 Java 泛型的引入,各种场景(虚拟机解析、反射等)下的方法调用都 可能对原有的基础产生影响和新的需求,如在泛型类中如何获取传入的参数化类 型等。因此,JCP 组织对虚拟机规范做出了相应的修改,引入了诸如 Signature 、 LocalVariableTypeTable 等新的属性用于解决伴随泛型而来的参数类型的识别问 题,Signature 是其中最重要的一项属性,它的作用就是存储一个方法在字节码 层面的特征签名[3] ,这个属性中保存的参数类型并不是原生类型,而是包括了 参数化类型的信息。修改后的虚拟机规范要求所有能识别 49.0 以上版本的 Class 文件的虚拟机都要能正确地识别 Signature 参数。 另外,从 Signature 属性的出现我们还可以得出结论,擦除法所谓的擦除,
仅仅是对方法的 Code 属性中的字节码进行擦除,实际上元数据中还是保留了泛 型信息,这也是我们能通过反射手段取得参数化类型的根本依据。
  • 22
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值