我正在编写一个Java程序,它有一个接口I
和实现它的类A
和B
。我还有另一个实现C
的类,它有两个方法和一个参数:一个接受对象A,另一个接受对象B。
interface I {}
static class A implements I {}
static class B implements I {}
interface C {
String map(A a);
String map(B b);
}
public static void main(String[] args) {
// Injected at runtime;
C mapper = getInstance();
I a = new A();
I b = new B();
// This fail at compilation
String result = mapper.map(a);
}
private static C getInstance() {
return new C() {
@Override
public String map(A a) {
return "a";
}
@Override
public String map(B b) {
return "b";
}
};
}
当我尝试用A或B的对象调用C的方法时,我得到以下错误:
java: no suitable method found for map(Main.I)
method Main.C.map(Main.A) is not applicable
(argument mismatch; Main.I cannot be converted to Main.A)
method Main.C.map(Main.B) is not applicable
(argument mismatch; Main.I cannot be converted to Main.B)
即使我们说I
类型的其他示例可以传递给mapper,sealed interface I permits A, B {}
也应该解决这个问题。
为什么Java中没有运行时的方法调度?我的问题是为什么在java中不可能,而不是“为什么它不工作”
4条答案
按热度按时间okxuctiv1#
对于“因为重载选择是在编译时根据参数的静态类型完成的,而不是在运行时根据参数的动态类型完成的”的效果,有几个答案。这些语句是100%正确的;这就是重载的工作原理,也是它的设计工作原理。多分派(multimethods)在Java选择单(基于接收者)分派的时候是众所周知的。
一个合理的下一个问题是:为什么要这样做?这在1995年是一个扣篮的原因有几个:
***可预测性。**如果给定的调用点总是对应于完全相同的(虚拟)方法,则这是一个简单的好处。
***安全性。**有些情况下没有“最佳”过载。在编译时确定这一点,并给出一个明确的错误消息,这比只有在传递了意外的输入组合时才让程序在运行时失败要好。让每个方法分派都潜在地抛出“没有最特定的重载”异常会使语言不那么可靠。
***性能。**重载选择可能代价高昂;最好是在编译时预先执行此操作,而不是加重运行时的负担。
***简单性。**重载和覆盖的交互将比现在复杂得多,因为,例如,我们需要更多的规则来打破“超类中更具体的方法和子类中被覆盖的候选者”之间的联系。这将导致更多令人惊讶的互动。
***实用性。**真正的多次调度很少;采用多参数分派可能会使语言和运行时复杂化,超过其表达价值。
在2023年,编译器和运行时技术有了很大的改进,因此编译时重载选择和运行时分派之间的平衡可能会有所改变,但总体而言,这仍然是一个明智和务实的权衡。
plupiseo2#
由于在java中不可能有这种重载,我找到了一个解决方案,可以解决类似的问题,使用访问者模式。
首先,假设下面的代码表示我们的域对象,我们不会向它添加任何外部依赖项或实现细节(如Map细节)
正如我们在域层中看到的,没有外部依赖关系或Map逻辑的实现细节。
在下面的代码中,我们将表示Web层,我们希望将域对象Map到JSON表示中,并将其打印到PrintStream。
现在让我们结束吧!以下是我们的主要应用:
现在我们可以将不同领域对象的列表Map到同一个流中的不同表示。
PS:PrintStream实际上可以是
ResponseWriter
或List
或任何可变的。uidvcgyl3#
在java中,方法的“身份”是而不仅仅是它的名称。它实际上是它的名称,* 和 * 是它所在的类型,* 和 * 是参数名的类型(在类文件级别,甚至是返回类型)。
因此,给定
map(I i)
和map(A a)
,这些方法 * 完全不同 *。这是不可能得到动态分派去出于同样的原因,它是不可能的,使调用.foo(x)
以某种方式导致调用.bar(x)
。当然,Java确实有动态分派。重写(进行动态分派)和重载(不进行动态分派)之间存在差异。
这就是java * 做 * 动态分派的地方:
Child中的
foo()
是 override。你可以把@Override
粘在上面,javac会毫无怨言地编译它。(@Override
不会使方法成为覆盖;它仅仅是编译器检查文档:如果没有覆盖任何内容,javac将拒绝编译带有该注解的方法。这就是它的作用这是“错误或无操作”)。然而,
bar(Integer)
* 不是一个覆盖 * -如果你坚持注解,它会导致编译错误。假设bar(Integer)
是一个完全不同的方法,p.bar(expr)
,其中expr
的类型是Number
(即使该表达式解析为对Integer类型对象的引用,这是不相关的,编译器无法知道,这是运行时的事情)。如果你想要这个,它..看起来有点丑会是这样的:
注意,java 20引入了一些方便的特性,比如
i instanceof A a
(它声明了一个名为a
的A
类型的变量),以及pattern-switch,它允许你为每个类型编写一个case
语句(也可以为你创建一个“预转换”的变量)。5lhxktic4#
对象
a
的类型在编译时未知,编译器无法确定调用哪个版本的map方法,因此它是不明确的为了解决这个问题
选项1:将
a
的类型更改为更具体选项2:修改C接口的map方法以接受实现I接口的任何对象