编译器认为以下代码无效:
class Foo {
void foo(String foo) { ... }
}
class Bar extends Foo {
@Override
void foo(Object foo) { ... }
}
Run Code Online (Sandbox Code Playgroud)
我认为这在JLS 8.4.8.1中有描述:"m1的签名是m2签名的子签名(§8.4.2)." 在8.4.2中:"相应类型变量的边界是相同的".
我的问题是:为什么子类型(Bar)中的参数不能成为超类型(Foo)中参数的超类型.在示例中,Object是String的超类型.据我所知,这不会违反Liskov替代原则.
是否有允许这会破坏代码的情况或者它是当前JLS的限制?
kut*_*kem 11
假设你可以那样做.现在你的超类看起来像这样:
class Foo {
void foo(String foo) { ... }
void foo(Number foo) { ... }
}
Run Code Online (Sandbox Code Playgroud)
和你的子类现在:
class Bar extends Foo {
@Override
void foo(Object foo) { ... }
}
Run Code Online (Sandbox Code Playgroud)
语言可能会允许这样的事情(和公正派遣两个Foo.foo(字符串)和Foo.foo(号码),以Bar.foo(对象)),但显然Java的设计决定在这里是一个方法只能覆盖正是另一种方法.
[编辑]
正如dasblinkenlight在他的回答中所说,没有@Override可以有一个foo(Object),但这只会重载foo函数,并且不会覆盖它们.在调用时,java选择最具体的方法,因此总是将foo("Hello World")分派到foo(String)方法.
(重写,从不同的角度......我的原始答案包含错误.:()
为什么子类型(Bar)中的参数不能成为超类型(Foo)中参数的超类型.
我认为技术上它可以,并且它不会破坏类型替代后的祖先合同(利斯科夫替代原则).
根据我在下面的分析,我推测/猜测不允许你的场景的理由:
方法覆盖的编译器要求 - JLS 7
编译器需要根据您的经验采取行动. 8.4方法声明:
子类中的方法可以覆盖祖先类iff中的方法:
返回类型是祖先类中返回类型的类型可替换,即相同类型或更窄(8.4.8.3)
注:子签名并不能意味着压倒一切的方法使用重载方法的亚型.覆盖方法据说有一子签名重写方法的覆盖方法具有当完全一样,除了一般类型的类型签名和相应的原始类型被认为是等效的.
编译器v方法匹配和调用的运行时处理
通过多态类型匹配,性能匹配方法签名.通过将覆盖方法签名限制为祖先的精确匹配,JLS将大部分处理移动到编译时. 15.12方法调用表达式 - 总结:
确定要搜索的类或接口(编译时确定)
确定方法签名(编译时确定)
检查:选择的方法是否合适?(编译时确定)
方法调用评估(运行时确定)
表现命中
根据JLS中的文字细分:
步骤1:5%步骤2:60%步骤3:5%步骤4:30%
第2步不仅在文本中体积庞大,而且令人惊讶地复杂.它具有复杂的条件和许多昂贵的测试条件/搜索.在此处最大化编译器执行更复杂和更慢处理是有利的.如果这是在运行时完成的,那么性能就会受到拖累,因为每次方法调用都会发生这种情况.
第4步仍然有重要的处理,但它尽可能简化.阅读15.12.4,它不包含可以移动到编译时的处理步骤,而不强制运行时类型与编译时类型完全匹配.不仅如此,它还对方法签名进行了简单的精确匹配,而不是复杂的"祖先类型匹配"
Java 注释不能改变编译器从您的类生成字节码的方式。您使用注解来告诉编译器您认为您的程序应该如何被解释,并在编译器的解释与您的意图不符时报告错误。但是,您不能使用注释来强制编译器生成具有不同语义的代码。
当您的子类声明了一个与其超类具有相同名称和参数数量的方法时,Java 必须在两种可能性之间做出决定:
如果 Java 允许foo(Object)覆盖foo(String)该语言,则必须引入替代语法来指示重载方法的意图。例如,他们可以用类似于 C# 的方式new和override方法声明来完成它。然而,无论出于何种原因,设计人员决定反对这种新语法,让语言使用 JLS 8.4.8.1 中指定的规则。
请注意,当前设计允许您通过转发来自重载函数的调用来实现覆盖的功能。在你的情况下,这意味着Bar.foo(Object)从Foo.foo(String)这样调用:
class Foo {
public void foo(String foo) { ... }
}
class Bar extends Foo {
@Override
public void foo(String foo) { this.foo((Object)foo); }
public void foo(Object foo) { ... }
}
Run Code Online (Sandbox Code Playgroud)