简体   繁体   English

Java 类型擦除:为什么以下代码段无法编译?

[英]Java type erasure: why won't the following snippet compile?

I'm currently prepping for my Java SE 11 Developer certificate and I can't seem to wrap my head around the concept of type erasure.我目前正在准备我的 Java SE 11 开发人员证书,我似乎无法理解类型擦除的概念。 I have the following classes:我有以下课程:

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;
    }
}

As far as I understand, type erasure will turn the method signatures into the following:据我了解,类型擦除会将方法签名转换为以下内容:

public List<? extends CharSequence> transform(Set set) {
    return null;
}

public List<String> transform(Set set) {
    return null;
}

Which to me seems like a valid override.在我看来,这似乎是一个有效的覆盖。 Yet, when I compile the program, I get the following error:然而,当我编译程序时,我收到以下错误:

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名称冲突:covariant.car.SubClass 中的 transform(java.util.Set<java.lang.String>) 和 covariant.car.BaseClass 中的 transform(java.util.Set<? extends java.lang.CharSequence>) 具有相同的擦除,但两者都不会覆盖另一个

What am I missing here?我在这里想念什么?

If your code compiled, then you could upcast an instance of SubClass to BaseClass and then pass some other kind of CharSequence than String to the transform method.如果您的代码已编译,那么您可以将SubClass的实例向上转换为BaseClass ,然后将String之外的其他类型的CharSequence传递给transform方法。

This shows how it would break type safety:这显示了它将如何破坏类型安全:

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()));

Type erasure makes sure that generic types do not exist at runtime but they exist at compile time.类型擦除确保泛型类型在运行时不存在,但在编译时存在。

This results in the following:这导致以下结果:

  • The methods would be equivalent at runtime这些方法在运行时是等效的

  • They are not the same at compile time and the conpiler detects that difference.它们在编译时并不相同,编译器会检测到这种差异。

As the purpose of generics is to cause a compiler error (or unchecked warning) instead of getting behaviour you very likely do not want, it just gives you that informative error:由于 generics 的目的是导致编译器错误(或未经检查的警告),而不是获得您很可能不想要的行为,它只会给您提供信息性错误:

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名称冲突:covariant.car.SubClass 中的 transform(java.util.Set<java.lang.String>) 和 covariant.car.BaseClass 中的 transform(java.util.Set<? extends java.lang.CharSequence>) 具有相同的擦除,但两者都不会覆盖另一个

It shows you the signatures of both methods and tells you that the result would be the same after applying Type Erasure but the (generic) signature is different.它向您显示了这两种方法的签名,并告诉您应用类型擦除后的结果将是相同的,但(通用)签名是不同的。

After all, the transform method of the BaseClass allows caller to pass a Set of anything that is a CharSequence while the transform method of SubClass only allows a Set of String s.毕竟, BaseClasstransform方法允许调用者传递任何CharSequenceSet ,而SubClasstransform方法只允许StringSet

I believe it's the method parameter that causes the error.我相信这是导致错误的方法参数。 When you make a subclass, your method can be more specific in what it returns (in your case, String is more specific than <? extends CharSequence> . However, method parameters can only be more general.当您创建子类时,您的方法可以更具体地返回它(在您的情况下, String<? extends CharSequence>更具体。但是,方法参数只能更通用。

For example, this snippet should work:例如,这个片段应该可以工作:

List<? extends CharSequence> out = baseClass.transform(
    Collections.singleton(new StringBuilder().append("Hi")));

If baseClass happens to be an instance of SubClass it should still work, so the method can't assume the parameter is List<String>如果baseClass恰好是SubClass的一个实例,它应该仍然可以工作,所以该方法不能假设参数是List<String>

Your您的

public List<? extends CharSequence> transform(Set<? extends CharSequence> set)

under the compiler hood is similar to在编译器引擎盖下类似于

public <T1 extends CharSequence, T2 extends CharSequence> List<T1> transform(Set<T2> set)

except instead of T1 and T2 it has names like capture#1 of?除了T1T2之外,它的名称类似于capture#1 of? and capture#2 of?capture#2 of? . .

As you can see, this is a generic method that has bounds on parameters supplied by the caller, but the method you introduced in the subclass isn't generic anymore -- it doesn't receive type parameters, it has concrete types!正如你所看到的,这是一个泛型方法,它对调用者提供的参数有限制,但是你在子类中引入的方法不再是泛型的——它不接收类型参数,它有具体的类型!

Now compiler sees that you try to override method(signatures of methods in JVM know nothing about generics in types supplied to them, so it would be just (Ljava/util/Set;)Ljava/util/List; in both cases), and will try to ensure, that you class still can be used in place of superclass, but it's not possible, because super class could be used as现在编译器看到您尝试覆盖方法(JVM 中的方法签名对提供给它们的类型中的 generics 一无所知,所以它只是(Ljava/util/Set;)Ljava/util/List;在这两种情况下),和将尝试确保您的 class 仍然可以用来代替超类,但这是不可能的,因为超级 class 可以用作

BaseClass bc = getBaseClassFromSomewhere();
List<CharBuffer> result = bc.<CharBuffer, CharBuffer>transform(someSetOfCharBuffers); 

And if your code ever tried to read String s from the supplied Set it would fail dramatically in runtime with ClassCastException , and same would happen with consumer that tried to read CharBuffer s from the list you returned.如果您的代码曾经尝试从提供的Set中读取String ,它将在运行时使用ClassCastException显着失败,同样的情况也会发生在试图从您返回的列表中读取CharBuffer的消费者身上。

Meanwhile, you could capture type invariants of this code, if you captured parameters of transform method explicitly in the class definition, like同时,如果您在 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;
  }
}

then it would be a valid overload, because methods aren't generic anymore, and upcast of SubClass is to BaseClass<String, String> , so instances of your class could be used only at places where BaseClass<String, String> are required, but not, for example, BaseClass<CharBuffer String> .那么这将是一个有效的重载,因为方法不再是通用的,并且SubClass的向上转换是BaseClass<String, String> ,因此您的 class 的实例只能在BaseClass<String, String>的地方使用,但不是,例如BaseClass<CharBuffer String>

JVM method signature stays the same, but actual types are captured in class definition, therefore compiler is assured, that it won't cause problems with casting at runtime. JVM 方法签名保持不变,但在 class 定义中捕获了实际类型,因此编译器保证它不会在运行时导致转换问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM