《Java 核心技术卷一》 泛型这儿,书上是不是写错了 ?
两个问题:
- 第一个圆圈圈那儿 : “变量pair已经声明为类型 Pair<LocalDate> , 并且这个类型只有一个名为 setSecond 的方法 , 即 setSecond(Object)” 。
为啥是 setSecond(Object) ?? 就算是 setSecond(Object)也应该是 “类型擦除后” 才是 setSecond(Object) 吧 , 但是并没有说是 类型擦除后啊 。
- 第二个圆圈圈那儿 : “这个对象是DateInterval类型,因而将会调用 DateInterval.setSecond( Object ) 方法” 。
按 “这个对象是DateInterval类型” 这个描述 , 应该是要按照 多态 来调用的意思, 那不就应该调用 DateInterval 类的 setSecond( LocalDate ) 方法吗 ??而不是 setSecond( Object ) 方法吧 , 是不是书写错了 ??
回答:
我感觉完成了文字游戏。擦除后就是object,没擦除就是localDate ,这么理解就行了。DateInterval.setSecond( Object )
也可能说的是父方法,setSecond( LocalDate )
说的是子方法,用参数来区分重写关系。很多书籍,确实不够严谨,错误也说不上。
回答:
我觉得 setSecond( Object )
这个说法没啥问题,泛型擦除后就是 Object
作为参数。
而且类 DateInterval
如果不集成 Pair
的话是还可以添加下面的方法的
public void setSecond(Object second) { }
而继承Pair
后就不能在添加这个方法了,会提示下面错误
回答:
在目前主流的编程语言中,编译器主要有以下两种处理泛型的方法:
- Code specialization
- Code sharing
Java是通过类型擦除来实现的。
泛型擦除(类型擦除)是指在编译器处理带泛型定义的类、接口或方法时,会在字节码指令集里抹去全部泛型类型信息,泛型被擦除后在字节码里只保留泛型的原始类型(raw type)。类型擦除的关键在于从泛型类型中清除类型参数的相关信息,然后在必要的时候添加类型检查和类型转换的方法。
原始类型是指抹去泛型信息后的类型,在Java语言中,它必须是一个引用类型(非基本数据类型),一般而言,它对应的是泛型的定义上界。
示例:中的T对应的原始泛型是Object,对应的原始类型就是String。
java使用泛型擦除的方式生成一份只有上界类型的字节码,而且在必要的时候添加类型检查
先明白简单的概念;然后创建这两个类。
public class Pair<T> { public void handler(T t){
System.out.println("parent A");
}
}
public class DataInterval extends Pair<String> { @Override
public void handler(String s) {
System.out.println("DataInterval:"+ s);
}
public static void main(String[] args) {
var sub = new DataInterval();
Pair<String> pair = sub;
pair.handler("DataInterval");
}
}
为了简单这里使用参数形式,而且参数和返回值不同即可确认不同的签名方法;
反编译一下:
Compiled from "DataInterval.java"public class com.example.demo.record.DataInterval extends com.example.demo.record.Pair<java.lang.String> {
public com.example.demo.record.DataInterval();
Code:
0: aload_0
1: invokespecial #1 // Method com/example/demo/record/Pair."<init>":()V
4: return
public void handler(java.lang.String);
Code:
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: aload_1
4: invokedynamic #3, 0 // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;)Ljava/lang/String;
9: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
12: return
public static void main(java.lang.String[]);
Code:
0: new #5 // class com/example/demo/record/DataInterval
3: dup
4: invokespecial #6 // Method "<init>":()V
7: astore_1
8: aload_1
9: astore_2
10: aload_2
11: ldc #7 // String DataInterval
13: invokevirtual #8 // Method com/example/demo/record/Pair.handler:(Ljava/lang/Object;)V
16: return
public void handler(java.lang.Object);
Code:
0: aload_0
1: aload_1
2: checkcast #9 // class java/lang/String
5: invokevirtual #10 // Method handler:(Ljava/lang/String;)V
8: return
}
这里看到有两个handler方法,仔细观察两个方法,执行的方法是不同的,而且handler(Object) 的执行过程显然不是我们定义的。这里使用了
2: checkcast #9 // class java/lang/String
回头看之前的一句话:在必要的时候添加类型检查,因为字节码生成方式的问题,使用了泛型擦除,因为擦除了又需要确定使用的类型,所以这里生成了对应的上界类型的方法并进行了类型检查;并最终调用了// Method handler:(Ljava/lang/String;)V 也就是我们子类中所写的方法,
这是第一个问题的答案和第二个问题的部分答案。
这里注意子类重写了父类的方法。
如果我们不实现这个方法,那么是不是不存在需要做类型检查呢?
public class DataInterval extends Pair<String> { public static void main(String[] args) {
var sub = new DataInterval();
//在main方法中增加 子类类型调用handler方法。
sub.handler("DataInterval");
Pair<String> pair = sub;
pair.handler("DataInterval");
}
}
反编译:
Compiled from "DataInterval.java"
public class com.example.demo.record.DataInterval extends com.example.demo.record.Pair<java.lang.String> {
public com.example.demo.record.DataInterval();
Code:
0: aload_0
1: invokespecial #1 // Method com/example/demo/record/Pair."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: new #2 // class com/example/demo/record/DataInterval
3: dup
4: invokespecial #3 // Method "<init>":()V
7: astore_1
8: aload_1
9: ldc #4 // String DataInterval
11: invokevirtual #5 // Method handler:(Ljava/lang/Object;)V
14: aload_1
15: astore_2
16: aload_2
17: ldc #4 // String DataInterval
19: invokevirtual #6 // Method com/example/demo/record/Pair.handler:(Ljava/lang/Object;)V
22: return
}
这里没有生成带Object的方法。
根据反编译的代码 这两个方法调用都是调用了父类擦除后的方法。
这里确实没有书中所有的 "桥方法"。根据里式替换原则。如果没有重写是不需要做类型检查的。
但是当我们重写了这个方法,这个类型检查就必须存在于某个地方,也就是书中所说的桥方法,具体叫什么我不太清楚;就当是这个称呼。
所以这里,当出现类型擦除又重写的方法上时会生成"桥方法"来进行类型判断并调用具体的签名方法(多态的实现)。
个人拙见。
以上是 《Java 核心技术卷一》 泛型这儿,书上是不是写错了 ? 的全部内容, 来源链接: utcz.com/p/944965.html