(从不同的角度重写......我原来的答案包含一个错误。:()
为什么子类型(Bar)中的参数不能是超类型(Foo)中参数的超类型。
我相信从技术上讲它可以,并且不会破坏类型替换(Liskov Substitution Principle)之后的祖先合同。
- 类型是按值传递的(包括引用类型)。
- 调用者永远不会被迫处理与其传入的参数类型不同的参数类型。
- 方法体可以交换参数类型,但不能将其返回给调用者(没有“输出参数类型”之类的东西)。
- 如果您的提议被允许,并且调用了祖先方法签名,则后代类可以使用更广泛的类型覆盖该方法,但仍然不可能返回比调用者设置的更广泛的类型。
- 覆盖永远不会破坏使用窄祖先方法合同的客户端。
从我下面的分析中,我推测/猜测不允许您的情况的理由:
- 性能相关:允许覆盖更广泛的类型会影响运行时性能,这是一个主要问题
- 功能相关:它只增加了少量的功能。就目前而言,您可以将“更广泛的方法”添加为重载方法而无需覆盖。然后,您还可以使用完全匹配的签名覆盖原始方法。最终结果:您在功能上实现了非常相似的东西。
方法覆盖的编译器要求 - JLS 7
编译器需要根据您的经验行事。 8.4 方法声明:
子类中的方法可以覆盖祖先类中的方法当且仅当:
用于方法匹配和调用的编译器 v 运行时处理
通过多态类型匹配有一个性能匹配方法签名。通过将覆盖方法签名限制为与祖先完全匹配,JLS 将大部分处理转移到编译时间。 15.12 方法调用表达式- 总结:
确定要搜索的类或接口(编译时确定)
- 获取调用方法的基类型 T。
- 这是向编译器声明的引用类型,而不是运行时类型(可以替换子类型)。
确定方法签名(编译时确定)
- 在编译时基类型 T 中搜索与调用一致的名称和参数和返回类型匹配的
适用方法。
- 解析 T 的泛型参数,显式传递或从调用方法参数的类型隐式推断
- 第 1 阶段:通过一致的类型/子类型(“子类型”)适用的方法
- 第 2 阶段:通过自动装箱/拆箱加子类型适用的方法
- 第 3 阶段:通过自动装箱/拆箱加子类型加变量“arity”参数适用的方法
- 确定最具体的匹配方法签名(即可以成功传递给所有其他匹配方法签名的方法签名);如果没有:编译器/歧义错误
检查:选择的方法是否合适?(编译时确定)
方法调用的评估(运行时确定)
- 确定运行时目标引用类型
- 评估论点
- 检查方法的可访问性
- 定位方法 - 与编译时匹配签名的精确签名匹配
- 调用
性能命中
就 JLS 中的文本而言,细分:
第 1 步:5% 第 2 步:60% 第 3 步:5% 第 4 步:30%
第 2 步不仅文本繁多,而且非常复杂。它具有复杂的条件和许多昂贵的测试条件/搜索。在这里最大化编译器执行更复杂和更慢的处理是有利的。如果这是在运行时完成的,则会拖累性能,因为每次方法调用都会发生这种情况。
第 4 步仍有重要处理,但已尽可能简化。通读 15.12.4,它不包含可以移动到编译时的处理步骤,而不强制运行时类型与编译时类型完全匹配。不仅如此,它还对方法签名进行了简单的精确匹配,而不是复杂的“祖先类型匹配”