编译器认为以下代码无效:
class Foo {
void foo(String foo) { ... }
}
class Bar extends Foo {
@Override
void foo(Object foo) { ... }
}
我认为这在JLS 8.4.8.1中有描述:“m1的签名是m2签名的子签名(§8.4.2)。”在8.4.2中:“相应类型变量的边界相同”。
我的问题是:为什么子类型(Bar)中的参数不能成为超类型(Foo)中参数的超类型。在示例中,Object是String的超类型。据我所知,允许这样做不会违反Liskov Substitution Principle。
是否存在允许这样会破坏代码的情况,还是当前JLS的限制?
答案 0 :(得分:11)
假设你可以那样做。现在你的超类看起来像这样:
class Foo {
void foo(String foo) { ... }
void foo(Number foo) { ... }
}
现在你的子类:
class Bar extends Foo {
@Override
void foo(Object foo) { ... }
}
该语言可能可以允许这样的事情(并且只将Foo.foo(String)和Foo.foo(Number)分配给Bar.foo(Object)),但显然是设计决策对于Java来说,一种方法只能覆盖其他一种方法。
<强> [编辑] 强>
正如dasblinkenlight在他的回答中所说,没有@Override可以有一个foo(Object),但这只会重载foo函数,并且不会覆盖它们。在调用时,java会选择最具体的方法,因此foo(“Hello World”)将始终被分派到foo(String)方法。
答案 1 :(得分:6)
(重写,从不同角度......我的原始答案包含错误。:()
为什么子类型(Bar)中的参数不能成为超类型(Foo)中参数的超类型。
我认为技术上它可以,并且它不会违反类型替代后的祖先合同(Liskov Substitution Principle)。
根据我的分析,我推测/猜测不允许您的方案的理由:
方法覆盖的编译器要求 - JLS 7
编制者需要根据您的经验采取行动。的 8.4 Method Declarations 强>:
子类中的方法可以覆盖祖先类iff:
中的方法返回类型是祖先类中返回类型的type-substitutable,即相同类型或更窄( 8.4.8.3 )
注意:subsignature 不意味着重写方法使用重写方法的子类型。当覆盖方法具有完全相同类型签名时,覆盖方法被称为具有重写方法的子签名,除了泛型类型和相应的原始类型被认为是等效的。
编译器v方法匹配的运行时处理&amp;调用强>
通过多态类型匹配,性能匹配方法签名。通过将覆盖方法签名限制为祖先的精确匹配,JLS将大部分处理移动到编译时。 15.12 Method Invocation Expressions - 摘要:
确定要搜索的类或接口(编译时确定)
确定方法签名(编译时确定)
检查:选择的方法是否合适? (编译时确定)
方法调用评估(运行时确定)
效果
根据JLS中的文字细分:
第1步:5% 第2步:60% 第3步:5% 第4步:30%
第2步不仅在文本中体积庞大,而且非常复杂。它具有复杂的条件和许多昂贵的测试条件/搜索。在此处最大化编译器执行更复杂和更慢处理是有利的。如果这是在运行时完成的,那么性能就会受到拖累,因为每次方法调用都会发生这种情况。
第4步仍然有重要的处理,但尽可能简化。通过 15.12.4 读取,它不包含可以移动到编译时的处理步骤,而不强制运行时类型与编译时类型完全匹配。不仅如此,它还对方法签名进行了简单的精确匹配,而不是复杂的&#34;祖先类型匹配&#34;
答案 2 :(得分: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) { ... }
}
答案 3 :(得分:2)
答案很简单,在Java中,对于方法覆盖,您必须具有超类型的确切签名。但是,如果删除 @Override注释,您的方法将会重载,您的代码也不会中断。这是一个Java实现,可以确保您的方法实现应该覆盖超类型的实现。
方法覆盖按以下方式工作。
class Foo{ //Super Class
void foo(String string){
// Your implementation here
}
}
class Bar extends Foo{
@Override
void foo(String string){
super(); //This method is implied when not explicitly stated in the method but the @Override annotation is present.
// Your implementation here
}
// An overloaded method
void foo(Object object){
// Your implementation here
}
}
上面显示的方法都是正确的,它们的实现可能会有所不同。
我希望这会对你有所帮助。
答案 4 :(得分:1)
回答帖子What is the reasoning behind not allowing supertypes on Java method overrides?
标题中的问题:
Java的设计者想要一种简单的面向对象的语言,他们特别拒绝C ++的功能,他们认为这些功能的复杂性/缺陷不值得获益。您所描述的内容可能属于设计师选择设计/指定功能的类别。
答案 5 :(得分:0)
您的代码存在的问题是,您告诉编译器,foo
类中的Bar
方法被从父类Bar(即Foo)覆盖。从overridden methods must have same signature
开始,但在您的情况下,根据语法,它是一个重载方法,因为您更改了Bar类的foo方法中的参数。
答案 6 :(得分:0)
如果你能用超类覆盖,为什么不用子类?
请考虑以下事项:
class Foo {
void foo(String foo) { ... }
}
class Bar extends Foo {
@Override
void foo(Object foo) { ... }
}
class Another extends Bar {
@Override
void foo(Number foo) { ... }
}
现在,您已成功覆盖原始参数为String
的方法,以接受Number
。不建议至少说......
相反,可以使用重载以及以下更明确的代码来复制预期结果:
class Foo {
void foo(String foo) { ... }
}
class Bar extends Foo {
@Override
private void foo(String foo) { ... }
void foo(Object foo) { ... }
}
class Another extends Bar {
@Override
private void foo(Object foo) { ... }
void foo(Number foo) { ... }
}