《Java 核心技术卷一》 泛型这儿,书上是不是写错了 ?

两个问题:

  1. 第一个圆圈圈那儿 : “变量pair已经声明为类型 Pair<LocalDate> , 并且这个类型只有一个名为 setSecond 的方法 , 即 setSecond(Object)” 。

为啥是 setSecond(Object) ?? 就算是 setSecond(Object)也应该是 “类型擦除后” 才是 setSecond(Object) 吧 , 但是并没有说是 类型擦除后啊 。

  1. 第二个圆圈圈那儿 : “这个对象是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后就不能在添加这个方法了,会提示下面错误


回答:

在目前主流的编程语言中,编译器主要有以下两种处理泛型的方法:

  1. Code specialization
  2. 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

回到顶部