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