【问题标题】:Java Generics, Type Inference, Inheritance?Java 泛型、类型推断、继承?
【发布时间】:2015-07-04 04:11:24
【问题描述】:

我正在阅读有关泛型类型推断的信息,并且此代码是作为无法编译的示例提供的。

import java.io.*;
class LastError<T> {
    private T lastError;

    void setError(T t){
        lastError = t;
        System.out.println("LastError: setError");
    }
}

class StrLastError<S extends CharSequence> extends LastError<String>{
    public StrLastError(S s) {
    }
    void setError(S s){
        System.out.println("StrLastError: setError");
    }
}
class Test {
    public static void main(String []args) {
        StrLastError<String> err = new StrLastError<String>("Error");
        err.setError("Last error");
    }
}

而书中给出的解释是:

"(貌似StrLastError中的setError()方法覆盖了LastError类中的setError(),然而事实并非如此。在编译的时候,知道了S类型的知识不可用。因此,编译器将这两个方法的签名记录为父类中的setError(String)和子类中的setError(S_extends_CharSequence)——将它们视为重载方法(未覆盖)。在这种情况下,当对setError()的调用是找到,编译器发现两个重载方法都匹配,导致方法调用不明确错误。”

我真的不明白为什么在编译时无法推断类型SString 在调用类StrLastError 的构造函数时传递, 从 API 文档中,String 确实实现了接口 CharSequence, 所以这是否意味着S&lt;S extends CharSequence&gt; 实际上是String 类型?

我已多次阅读有关泛型主题的 Java 在线教程。我检查了“类型推断”和继承,我只是不知道整个事情是如何工作的。这个问题我真的需要一个解释。

我坚持的观点是:

  1. 如果子类型不能决定S,为什么超类型可以决定T,因为超类没有上限?还是因为子类型首先调用超类型的构造函数,所以推断TString
  2. 我理解如果构造函数被调用为:

    StrLastError<CharSequence> err = newStrLastError<>((CharSequence)"Error");
    

    不会有歧义,因为它是简单的方法覆盖。 (或者我在这里错了吗?)

但是,就像我一开始说的,如果String通过了,为什么不能将S推断为String

【问题讨论】:

  • 为什么说它不会产生编译错误??
  • 嗨,我说它应该在我的帖子的第一行产生编译错误:)
  • 是的,编辑后 ;) 在“代码最终会出现编译错误”之前
  • 我没有说它“不”,我说它“应该”,如果我非常确定它产生编译错误的原因,我不会在这里问这个问题.

标签: java


【解决方案1】:

您必须提醒自己,这些类是一一编译的。 Java 泛型不像其他语言那样是模板。将只有一个已编译的类,而不是与它一起使用的每种类型的一个类。

这样你可以看到StrLastError 类需要以某种方式编译,以便它也可以与将CharSequence 实现为泛型S 的其他类一起使用。

这就是为什么编译器会为您提供两种不同的方法而不是一种被覆盖的方法。现在,在类型建议的情况下,查看子类可能想要覆盖父类中的方法将是一项运行时工作。由于这种行为对于开发人员来说难以理解,并且可能导致编程错误,因此会引发异常。

如果你使用CharSequence作为StrLastError类的泛型类型参数,你会在父类中调用setError方法,因为"Last Error"的类型是String,比@更具体987654327@ 和 Java 总是选择最具体的方法,以防它被重载。 (我希望在这种情况下该方法也没有被覆盖)

【讨论】:

    【解决方案2】:

    如果子类型不能决定S,为什么超类型可以决定T,因为超类没有上限?还是因为子类型先调用超类型的构造函数就推断出T是String?

    超级类型不会决定或推断T 是什么;您通过此声明明确告诉T 是什么:

    class StrLastError<S extends CharSequence> extends LastError<String>
    

    T 现在绑定到String for LastError,这使得父类中对T 的每个引用都成为具体的String

    您的子类现在附加了一个绑定 S extends CharSequence,但这与应用于父类的边界无关。

    现在发生的事情是 Java 将编译您的子类,而您的子类的结果是将创建两个签名匹配 String 的方法。 (此处的重点说明:A String is-a CharSequence

    在您的子类中,setError(Ljava/lang/CharSequence;)V 生成为setError 的签名。由于泛型的工作方式,LastError#setError 将被处理,就好像它具有 setError(Ljava/lang/String;)V 的签名一样。这也是为什么当您实际覆盖该方法时,它会将String 类型作为您的参数而不是其他任何内容。

    所以,我们得到的是two methods that have override-equivalent signatures.

    void setError(CharSequence s)
    void setError(String s)
    

    JLS 8.4.8.4。适用于此。

    一个类可以继承多个方法 覆盖等效签名(第 8.4.2 节)。

    如果类 C 继承了具体方法,则为编译时错误 其签名是继承的另一个具体方法的子签名 由 C. 如果超类是泛型的,并且它有两个 在泛型声明中不同的方法,但具有 在使用的特定调用中使用相同的签名。


    我知道如果构造函数被调用为 StrLastError err = new StrLastError((CharSequence)"Error");不会有歧义,因为它的普通方法覆盖了。(或者我在这里甚至错了)

    不,现在你在搞乱raw types。有趣的是它会工作,主要是因为这两种方法的签名变成了:

    void setError(Object s)
    void setError(String s)
    

    您想使用泛型来避免这种情况;您可能希望在某些时候调用超类方法,但在这种情况下,使用这些绑定很难实现。

    【讨论】:

    • 谢谢,我想我在第二点遗漏了一个 ,将修改帖子,但我仍然不明白为什么在声明中明确使用 main, S 不能在编译时推断出来。
    • 绑定到子级的泛型类型与绑定到父级的类型不同。哎呀,如果您在泛型方法上有一个类型参数,那么这两种类型也不会相同。如果您对这种行为感到好奇(这里稍微超出了范围),请鼓励提出一个新问题。
    • 所以编译器首先编译类“LastError”,然后编译“StrLastError”,在main的主体中,当构造函数被调用时,LastError被明确告知T应该是来自子类型声明的String , 并留下子类类型参数 S 未确定?
    • 没有。 S 绑定到 CharSequence 作为约束的上限。这是 Java 搞混的主要原因。 StringCharSequence,因此在传递 String 时,父类和子类方法均有效。
    • "在编译的​​时候,没有 S 类型的知识。" 这是最让我困惑的地方,所以假设没有超类,并且类 StrLastError 单独存在而不改变其他任何东西,例如它的上界,main 还是一样,S 还是未定?非常感谢,我想我快到了。
    【解决方案3】:

    这是一个棘手的例子,要修复它,您需要更改以下行:

    class StrLastError<S extends CharSequence> extends LastError<S>
    

    【讨论】:

    • 我认为您对修复的看法是正确的,但据说该示例是故意错误的,OP 想了解其错误的原因。
    猜你喜欢
    • 2021-08-22
    • 2020-12-01
    • 1970-01-01
    • 2021-10-11
    • 1970-01-01
    • 1970-01-01
    • 2021-10-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多