Java 方法在两个泛型类的层次结构中不明确,而方法实际给定了相同的类型参数

Java 方法在两个泛型类的层次结构中不明确,而方法实际给定了相同的类型参数,java,generics,Java,Generics,为什么方法f()在派生中不明确 class Base<T> { void f(T arg) { } } class Derived<S extends CharSequence> extends Base<String> { void f(S arg) { } // compiles OK so far !!! } Derived<String> obj = new Derived<>(); obj

为什么方法f()在派生中不明确

class Base<T> {
    void f(T arg) { }
}

class Derived<S extends CharSequence> extends Base<String> {
    void f(S arg) { }  // compiles OK so far !!!
}

    Derived<String> obj = new Derived<>();
    obj.f("hi"); // c.ERR: The method f(String) is ambiguous for the type Derived<String>
类基{
void f(T arg){}
}
类派生的扩展基{
void f(S arg){}//编译到目前为止还可以!!!
}
派生对象=新派生();
对象f(“hi”);//c、 错误:方法f(字符串)对于派生的类型不明确
但是:

Derived obj=new-Derived();
对象f(“hi”);//好啊重载方法解决了吗?
obj.f(新的StringBuffer());//好啊重载方法解决了吗?
void f(String)
继承自Base(
void f(Object)在运行时
),相同的
void f(String)
在派生中生成(
void f(Object)
在运行时)

请帮助我确定冲突发生的时间和地点

到目前为止,我倾向于认为只在编译时从所有可能的重载方法中选择合适的方法来调用(!),而派生扩展Base对编译器来说意味着在编译时派生有两个具有相同签名的方法:Base.f(String)和Derived.f(String),这使得f(“hi”)调用不明确(两个重载版本都适用)

在这种情况下,重写是不可能的(@Override in-Derived确实会产生编译错误),因为无法保证相同的签名(相同的参数),而且这只是一个幸运的巧合,因为…extensed Base可以在其中重写,但JLS将标记编译错误

方法的实际运行时签名(如编译后JVM所见)是
void Base.f(Object)
void f(CharSequence)
这一事实与重载解析完全无关,因为这种解析只发生在编译时

Java语言规范:

调用方法时(§15.12),实际参数的数量(和 任何显式类型参数)和 参数在编译时用于确定 将被调用的方法(§15.12.2)。如果要使用的方法是 调用的是实例方法,要调用的实际方法将是 使用动态方法查找(§15.12.4)在运行时确定


注意:Base.f()和Derived.f()是否总是重载?那怎么办呢?作为运行时的
void f(Object)
vs
void f(CharSequence)
?如果它们在新派生的情况下被重写,那么它们是否重载或重写取决于实例化期间派生的类型参数?这可能吗?

Base
的定义意味着方法
f()
可以接受任何类型的参数。它不仅可以是String,还可以是Integer、Long,实际上是任何类的实例。什么类,取决于创建类
Base
实例时使用的泛型参数


Derived
的定义允许传递
CharSequence
的任何子类的实例

非常重要的是,
Derived
的定义并不意味着它的泛型参数
S
以某种方式与父类的泛型参数
T
相关。这样的定义意味着
Derived
中的方法
f()
不会覆盖父类中的方法
f()
。因此,编译器可以看到两种方法

你能做什么?这取决于你的目标是什么。但一种解决方案可能是:

class Base<T> {
    void f(T arg) { }
}

class Derived<S extends CharSequence> extends Base<S> {
    void f(S arg) { }
}

Derived<String> obj = new Derived<>();
obj.f("hi");
类基{
void f(T arg){}
}
类派生的扩展基{
空f(S arg){}
}
派生对象=新派生();
obj.f(“hi”);

对于
基本类的给定情况

class Base<T> {
  void f(T arg) { }
}
class Derived<S extends CharSequence> extends Base<String> {
  void f(S arg) { } 
}
不会重写
f()
方法,而是因为类型擦除而重载
f()
方法

因此,
派生的
类包含两个重载
f()

  • 一个来自父级
    Base
    class:
    void f(对象arg)
  • 另一个来自它自己的类:
    void f(CharSequence arg)
由于在java泛型中只提供编译时间类型安全性,编译器充分利用这一点,根据传递的参数推断参数化
派生的
f()
的正确重载

这就是发生在这起案件中的情况

Derived<StringBuffer> obj = new Derived<>();
obj.f("hi"); // OK! Overloaded methods resolved?
obj.f(new StringBuffer()); // OK! Overloaded methods resolved?
Derived<String> obj = new Derived<>();
obj.f("hi");
Derived obj=new-Derived();
对象f(“hi”);//好啊重载方法解决了吗?
obj.f(新的StringBuffer());//好啊重载方法解决了吗?
但是,在

Derived<StringBuffer> obj = new Derived<>();
obj.f("hi"); // OK! Overloaded methods resolved?
obj.f(new StringBuffer()); // OK! Overloaded methods resolved?
Derived<String> obj = new Derived<>();
obj.f("hi");
Derived obj=new-Derived();
obj.f(“hi”);
编译器无法推断一个重载超过另一个重载,因此存在歧义

要回答您的问题:

  • Base.f()和Derived.f()是否总是重载?:这取决于这些声明版本的
    f()
    的类型擦除结果是否产生不同的方法签名
  • 在某些情况下,编译器本身会在派生的.f()方法(也称为桥接方法)中生成类型erasured
    Base.f()
    的新重写。但你的情况并不是这样

  • 不能保证
    s
    String