Java 类型擦除:为什么以下代码片段无法编译?
Java type erasure: why won't the following snippet compile?
我目前正在准备我的 Java SE 11 开发人员证书,我似乎无法理解类型擦除的概念。我有以下 类:
public class BaseClass {
public List<? extends CharSequence> transform(Set<? extends CharSequence> set) {
return null;
}
}
public class SubClass extends BaseClass {
@Override
public List<String> transform(Set<String> set) {
return null;
}
}
据我了解,类型擦除会将方法签名变成以下内容:
public List<? extends CharSequence> transform(Set set) {
return null;
}
public List<String> transform(Set set) {
return null;
}
这对我来说似乎是一个有效的覆盖。然而,当我编译程序时,出现以下错误:
name clash: transform(java.util.Set<java.lang.String>) in covariant.car.SubClass and transform(java.util.Set<?
extends java.lang.CharSequence>) in covariant.car.BaseClass have the same erasure, yet neither overrides the other
我在这里错过了什么?
如果您的代码已编译,那么您可以将 SubClass
的实例向上转换为 BaseClass
,然后将 String
以外的其他类型的 CharSequence
传递给 transform
方法。
这显示了它将如何破坏类型安全:
class NotString implements CharSequence {
public char charAt(int index) {
return 'A';
}
public int length() {
return 1;
}
public CharSequence subSequence(int start, int end) {
return this;
}
public String toString() {
return "A";
}
}
BaseClass base = new SubClass();
// Oops, passing Set<NotString> to transform(Set<String> set).
base.transform(Set.of(new NotString()));
类型擦除确保泛型类型在运行时不存在但在编译时存在。
结果如下:
这些方法在运行时是等效的
它们在编译时不相同,编译器会检测到该差异。
由于泛型的目的是导致编译器错误(或未经检查的警告)而不是获得您很可能不想要的行为,它只会给您提供信息性错误:
name clash: transform(java.util.Set<java.lang.String>) in covariant.car.SubClass and transform(java.util.Set<? extends java.lang.CharSequence>) in covariant.car.BaseClass have the same erasure, yet neither overrides the other
它向您展示了两种方法的签名,并告诉您应用 Type Erasure 后结果相同,但(通用)签名不同。
毕竟,BaseClass
的 transform
方法允许调用者传递任何 CharSequence
的 Set
,而 transform
方法SubClass
只允许 Set
个 String
。
我认为是方法参数导致了错误。当你创建一个子类时,你的方法可以更具体 returns (在你的情况下, String
比 <? extends CharSequence>
更具体。但是,方法参数只能更通用。
例如,这段代码应该有效:
List<? extends CharSequence> out = baseClass.transform(
Collections.singleton(new StringBuilder().append("Hi")));
如果 baseClass
恰好是 SubClass
的一个实例,它应该仍然有效,所以该方法不能假设参数是 List<String>
你的
public List<? extends CharSequence> transform(Set<? extends CharSequence> set)
在编译器引擎盖下类似于
public <T1 extends CharSequence, T2 extends CharSequence> List<T1> transform(Set<T2> set)
除了 T1
和 T2
它的名称类似于 capture#1 of ?
和 capture#2 of ?
.
如您所见,这是一个泛型方法,它对调用者提供的参数有限制,但是您在 subclass 中引入的方法不再是泛型 - 它不接收类型参数,它有具体的类型!
现在编译器看到你试图覆盖方法(JVM 中方法的签名对提供给它们的类型中的泛型一无所知,所以在这两种情况下它都只是 (Ljava/util/Set;)Ljava/util/List;
),并将尝试确保,你 class 仍然可以用来代替 superclass,但这是不可能的,因为 super class 可以用作
BaseClass bc = getBaseClassFromSomewhere();
List<CharBuffer> result = bc.<CharBuffer, CharBuffer>transform(someSetOfCharBuffers);
并且如果您的代码曾经尝试从提供的 Set
中读取 String
s,它将在运行时以 ClassCastException
显着失败,同样的情况也会发生在试图读取的消费者身上CharBuffer
来自您返回的列表。
同时,如果您在 class 定义中显式捕获 transform
方法的参数,例如
public static class BaseClass<T1 extends CharSequence, T2 extends CharSequence> {
public List<T1> transform(Set<T2> set) {
return null;
}
}
public static class SubClass extends BaseClass<String, String> {
@Override
public List<String> transform(Set<String> set) {
return null;
}
}
那么它将是一个有效的重载,因为方法不再是通用的,并且 SubClass
的向上转换为 BaseClass<String, String>
,因此您的 class 的实例只能被使用在需要 BaseClass<String, String>
但不需要的地方,例如 BaseClass<CharBuffer String>
.
JVM 方法签名保持不变,但实际类型在 class 定义中捕获,因此编译器可以放心,它不会在运行时导致转换问题。
我目前正在准备我的 Java SE 11 开发人员证书,我似乎无法理解类型擦除的概念。我有以下 类:
public class BaseClass {
public List<? extends CharSequence> transform(Set<? extends CharSequence> set) {
return null;
}
}
public class SubClass extends BaseClass {
@Override
public List<String> transform(Set<String> set) {
return null;
}
}
据我了解,类型擦除会将方法签名变成以下内容:
public List<? extends CharSequence> transform(Set set) {
return null;
}
public List<String> transform(Set set) {
return null;
}
这对我来说似乎是一个有效的覆盖。然而,当我编译程序时,出现以下错误:
name clash: transform(java.util.Set<java.lang.String>) in covariant.car.SubClass and transform(java.util.Set<? extends java.lang.CharSequence>) in covariant.car.BaseClass have the same erasure, yet neither overrides the other
我在这里错过了什么?
如果您的代码已编译,那么您可以将 SubClass
的实例向上转换为 BaseClass
,然后将 String
以外的其他类型的 CharSequence
传递给 transform
方法。
这显示了它将如何破坏类型安全:
class NotString implements CharSequence {
public char charAt(int index) {
return 'A';
}
public int length() {
return 1;
}
public CharSequence subSequence(int start, int end) {
return this;
}
public String toString() {
return "A";
}
}
BaseClass base = new SubClass();
// Oops, passing Set<NotString> to transform(Set<String> set).
base.transform(Set.of(new NotString()));
类型擦除确保泛型类型在运行时不存在但在编译时存在。
结果如下:
这些方法在运行时是等效的
它们在编译时不相同,编译器会检测到该差异。
由于泛型的目的是导致编译器错误(或未经检查的警告)而不是获得您很可能不想要的行为,它只会给您提供信息性错误:
name clash: transform(java.util.Set<java.lang.String>) in covariant.car.SubClass and transform(java.util.Set<? extends java.lang.CharSequence>) in covariant.car.BaseClass have the same erasure, yet neither overrides the other
它向您展示了两种方法的签名,并告诉您应用 Type Erasure 后结果相同,但(通用)签名不同。
毕竟,BaseClass
的 transform
方法允许调用者传递任何 CharSequence
的 Set
,而 transform
方法SubClass
只允许 Set
个 String
。
我认为是方法参数导致了错误。当你创建一个子类时,你的方法可以更具体 returns (在你的情况下, String
比 <? extends CharSequence>
更具体。但是,方法参数只能更通用。
例如,这段代码应该有效:
List<? extends CharSequence> out = baseClass.transform(
Collections.singleton(new StringBuilder().append("Hi")));
如果 baseClass
恰好是 SubClass
的一个实例,它应该仍然有效,所以该方法不能假设参数是 List<String>
你的
public List<? extends CharSequence> transform(Set<? extends CharSequence> set)
在编译器引擎盖下类似于
public <T1 extends CharSequence, T2 extends CharSequence> List<T1> transform(Set<T2> set)
除了 T1
和 T2
它的名称类似于 capture#1 of ?
和 capture#2 of ?
.
如您所见,这是一个泛型方法,它对调用者提供的参数有限制,但是您在 subclass 中引入的方法不再是泛型 - 它不接收类型参数,它有具体的类型!
现在编译器看到你试图覆盖方法(JVM 中方法的签名对提供给它们的类型中的泛型一无所知,所以在这两种情况下它都只是 (Ljava/util/Set;)Ljava/util/List;
),并将尝试确保,你 class 仍然可以用来代替 superclass,但这是不可能的,因为 super class 可以用作
BaseClass bc = getBaseClassFromSomewhere();
List<CharBuffer> result = bc.<CharBuffer, CharBuffer>transform(someSetOfCharBuffers);
并且如果您的代码曾经尝试从提供的 Set
中读取 String
s,它将在运行时以 ClassCastException
显着失败,同样的情况也会发生在试图读取的消费者身上CharBuffer
来自您返回的列表。
同时,如果您在 class 定义中显式捕获 transform
方法的参数,例如
public static class BaseClass<T1 extends CharSequence, T2 extends CharSequence> {
public List<T1> transform(Set<T2> set) {
return null;
}
}
public static class SubClass extends BaseClass<String, String> {
@Override
public List<String> transform(Set<String> set) {
return null;
}
}
那么它将是一个有效的重载,因为方法不再是通用的,并且 SubClass
的向上转换为 BaseClass<String, String>
,因此您的 class 的实例只能被使用在需要 BaseClass<String, String>
但不需要的地方,例如 BaseClass<CharBuffer String>
.
JVM 方法签名保持不变,但实际类型在 class 定义中捕获,因此编译器可以放心,它不会在运行时导致转换问题。