JavaSE高级程序设计总结、训练——泛型程序设计2

JavaSE高级程序设计总结、训练——泛型程序设计2

四、泛型的实现原理

4.1、概述原理

由于泛型是在JDK1.5才产生的,在此之前Java泛型都是用Object 对象来承接所有类型的对象。但是JVM必须保持向后兼容,也就是说,低版本的JDK必须能够在高版本的JDK上运行!由于这种兼容性,我们的泛型就不得已要在编译后变成和JDK1.5之前的字节码一样的class文件了~
所以,泛型的原理和Object还是有关滴~~

4.2、来看示例

示例1:普通泛型类与普通泛型函数


/**
 * @author jiangzl
 */
public class MyGeneric7 {
    
    

    public static <T> void getTemplateValue(MyNumber<T> myNumber){
    
    
        System.out.println(myNumber.getValue());
    }

    public static void main(String[] args) {
    
    
        MyNumber<Double> doubleMyNumber = new MyNumber<>(3.14);
        getTemplateValue(doubleMyNumber);
        MyNumber<Integer> integerMyNumber = new MyNumber<>(520);
        getTemplateValue(integerMyNumber);
    }
}

class MyNumber<T>{
    
    
    private T value;

    public MyNumber(T value){
    
    
        this.value = value;
    }

    public T getValue(){
    
    
        return value;
    }
}
看看示例的输出结果:

在这里插入图片描述

再看看反汇编的结果:
第一:MyNumber.class的结果

在这里插入图片描述

重点请看:Field value 的值

它是 java/lang/Object; 的!!!这说明,经过反汇编后,这个值是变成了Object的对象。然后构造方法也是Object的,这个invokespecial简单说一下,就是具体函数的意思,不是虚函数~~

第二:MyGeneric7.class的结果

在这里插入图片描述

看到泛型方法的参数,经过反汇编,把getValue()和Field部分,也就是参数部分,都变成了Object对象~~

那么到时候具体调用的时候,是如何把泛型实例化的呢???请看下面:~
在这里插入图片描述
这里进行了泛型的实例化,也就是main方法的部分,这里我们可以看到:

#8 和 #9 的后面都注释了,调用静态方法的时候,对Object对象进行了Double.valueOf()和Integer.valueOf()的操作!~于是就完成了实例化,并且调用泛型方法。

示例2:带有限制的泛型

import java.security.Security;
import java.util.Comparator;
import java.util.Scanner;

/**
 * 泛型类型的限定
 * @author jiangzl
 */
public class MyGeneric3 {
    
    

    public static <T extends Comparable> T getMax(T... e){
    
    
        if(null == e || e.length <= 0){
    
    
            return null;
        }
        T ret = e[0];
        for(T var : e){
    
    
            if(ret.compareTo(var) < 0){
    
    
                ret = var;
            }
        }
        return ret;
    }

    public static void main(String[] args) {
    
    
        int n = 5;
        Student[] s = new Student[n];
        Scanner sc = new Scanner(System.in);
        for(int i = 0;i < n;++i){
    
    
            String name = sc.next();
            int score = sc.nextInt();
            s[i] = new Student(name, score);
        }
        sc.close();
        System.out.println(getMax(s));

        String Str[] = {
    
    "b", "a", "c", "d", "e"};
        System.out.println(getMax(Str));
    }
}

class Student implements Comparable<Student> {
    
    
    private String name;
    private int score;

    public Student(String name, int score){
    
    
        this.name = name;
        this.score = score;
    }

    @Override
    public String toString(){
    
    
        return new String("name = " + name + ", score = " + score);
    }

    @Override
    public int compareTo(Student s) {
    
    
        if(score != s.score){
    
    
            return score - s.score;
        }
        else{
    
    
            return s.name.compareTo(name);
        }
    }
}

这里我们要求,能够实例化泛型方法的类,必须是实现了Comparable接口的类!!!

来看看反汇编的结果吧:(内容太多,只节选)

在这里插入图片描述
这里我们发现,泛型方法(public static <T extends Comparable> T getMax(T... e))反汇编后,参数变成了Comparable的类型,这里我们有如下总结:

第一:如果extends的类与接口里,有类,则类必须写在前面,并且反汇编后就是这个类的类型!
第二:如果extends的没有类,全是接口,那就无所谓的,会以第一个(离extends最近的)类作为这个泛型的类型!

五、协变与逆变

5.1、什么是协变与逆变,什么是关系?

如果我们说 A <= B 代表 A 是 B 的子类,设 f(x) 表示给某个类 x 封装成某种数据结构。比如数组、集合等等。那么在封装后,如果 f(A) 和 f(B) 有继承上的关系,则称有关系~

关系有如下:

第一:

若 A <= B , 有 f(A) <= f(B) 则称协变,意思就是A是B的子类,封装后,A还是B的子类。典型的有数组,比如A是B的子类,则数组 A[] 还是 B[] 的子类~

第二:

若 A <= B , 有 f(A) >= f(B) 则称逆变,意思就是A是B的子类,封装后,B却是A的子类。典型的有在通配符限定下的,下界限定就是的~

第三:

若 A <= B , 有 f(A) <> f(B) 则称不变,意思就是A是B的子类,封装后,A和B没有任何继承关系。典型的有泛型,不加限定的泛型,经过泛型后的集合,比如List< A > 和 List< B >是没有任何关系的~

这样可能不太清晰,用一张图来表示吧:
在这里插入图片描述

5.2、函数返回值的协变与里氏替换原则

根据里氏替换原则,传入函数的实参,应该能够被形参接收。所以type(实参) <= type(形参),意思就是实参是形参本身类型或者子类型。而设定的返回值应该能够被调用的返回值接收,也就是type(调用) >= type(返回)。

看个示例:



/**
 * @author jiangzl
 */
public class MyGeneric7 {
    
    
    public static void main(String[] args) {
    
    
        ClassFather classFather = new ClassSon();
        System.out.println(classFather.method(3.14));
    }
}

class ClassFather{
    
    
    public Number method(Number value){
    
    
    	System.out.println(value.getClass());
        System.out.println("father");
        return value;
    }
}

class ClassSon extends ClassFather{
    
    
    @Override
    public Double method(Number value){
    
    
        System.out.println("son");
        return Double.valueOf(value.toString());
    }
}

从JDK1.5开始,允许子类重写父类方法的时候,用协变后的(被重写的父类方法的返回值类型的子类)的类型去做返回值。但是来看看这个代码输出的结果哈:

在这里插入图片描述

得到结论:

在JDK1.5之后,子类重写父类方法允许协变,也就是说,返回值允许是比父类方法更加具体的类型!!

5.3、数组协变

先看看示例代码:

模块1:类的设计
class FirstClass{
    
    
    public FirstClass(){
    
    
        System.out.println("FirstClass is constructed!!");
    }

    public void sayHello(){
    
    
        System.out.println("Helo World!");
    }
}

class SecondClass extends FirstClass{
    
    
    public SecondClass(){
    
    
        System.out.println("SecondClass is constructed!!");
    }
}

class LastClass extends SecondClass{
    
    
    public LastClass(){
    
    
        System.out.println("LastClass is constructed!!");
    }
}
模块2:数组的协变
		FirstClass[] firstClasses = new SecondClass[2];
        firstClasses[0] = new SecondClass();
        try {
    
    
            firstClasses[1] = new FirstClass();
            firstClasses[1].sayHello();
        }
        catch (Exception e){
    
    
            System.out.println("Runtime error!");
        }

        System.out.println();
        SecondClass[] secondClasses = new SecondClass[2];
        firstClasses = secondClasses;
        firstClasses[0] = new LastClass();
        try {
    
    
            firstClasses[1] = new FirstClass();
            firstClasses[1].sayHello();
        }
        catch (Exception e){
    
    
            System.out.println("Runtime error!");
        }

这里我只截取了关于数组协变部分的代码,这部分代码在main方法里。执行的结果是:
在这里插入图片描述
从此处我们可以看到,数组的确是协变的,但是也提出了一个新的问题:

子类实例化出父类的对象,为何要到父类构造函数执行完之后才异常?

此处编译会通过,运行要运行到父类构造函数结束后才抛出异常!!!为何JVM如此后知后觉呢?有知道的小伙伴还请留言或私信我,谢谢!~

5.4、泛型的协变

看看示例:

import java.util.ArrayList;

/**
 * @author jiangzl
 */
public class MyGeneric6 {
    
    
    public static void main(String[] args) {
    
    
        int nA = 3, nB = 4, nC = 5;
        ArrayList<A> asA = new ArrayList<>();
        for(int i = 0;i < nA;++i){
    
    
            asA.add(new A());
        }
        ArrayList<B> asB = new ArrayList<>();
        for(int i = 0;i < nB;++i){
    
    
            asB.add(new B());
        }
        ArrayList<C> asC = new ArrayList<>();
        for(int i = 0;i < nC;++i){
    
    
            asC.add(new C());
        }

        sayName(asA, asB, asC);
    }

    public static void sayName(ArrayList<? extends A>... arrayList){
    
    
        int len = arrayList.length;
        for(int i = 0;i < len;++i){
    
    
            int size = arrayList[i].size();
            for(int j = 0;j < size;++j){
    
    
                arrayList[i].get(j).sayName();
            }
        }
    }
}

class A{
    
    
    public void sayName(){
    
    
        System.out.println("my name is A");
    }
}

class B extends A{
    
    
    @Override
    public void sayName(){
    
    
        System.out.println("my name is B");
    }
}

class C extends A{
    
    
    @Override
    public void sayName(){
    
    
        System.out.println("my name is C");
    }
}

看看输出的结果:

在这里插入图片描述

五、写在后面:

泛型的协变和逆变,其实在上一节就已经提过了,需要注意的就是:

协变,也就是 ? extends Class 的上界限定泛型,只能够 get 而不能 set!

逆变,也就是 ? super Class 的下界限定泛型,就只能 set 而不能 get!

如果对泛型基础不理解的,可以参考我的上一篇文章:

JavaSE高级程序设计总结——泛型基础1

另外,本篇提出了两个疑惑,由于本人能力有限,如果有写错,理解不当的地方,还请各位大佬指出。感谢批评指正!还有那两个疑惑,如果有能够解释的,还请不吝赐教!再次感谢!

猜你喜欢

转载自blog.csdn.net/qq_44274276/article/details/108098107