为什么方法重载决议是在编译时确定的?
Why is method overload resolution determined at compile time?
我试图理解为什么我们有覆盖方法的多态性/动态绑定,而不是重载方法。我知道有一种间接方法可以让我们索引到 vtable 中,从而允许我们使用 C++ 或 Java 这样的语言来执行此操作。我很好奇为什么解析重载方法的情况不同——我的直觉让我相信我们可以有一个间接级别,允许我们在运行时确定根据运行时类型调用哪个重载方法。
我想知道这个设计决定是出于性能原因还是我忽略了考虑额外的复杂性。
我没有读懂语言设计者的想法,所以不能告诉你。我是这样想的:
- 重写方法是同一方法在 superclass/subclass 层次结构中不同级别的不同实现。 subclass 一般使用相同的方法签名(允许 return 更具体的类型并声明更少的抛出异常,但不能完全重新定义方法头,否则将不再是覆盖)。
- 重载方法实际上只是碰巧具有相同名称的不同方法。然后使用参数类型进行区分。就像编译器总是在编译时决定调用哪个方法一样,重载方法也是如此。
作为观察结果(可能是次要观察结果),通过重载方法的运行时解析,我们无法再对 return 值进行静态类型检查。假设我们有
public boolean foo(Number n);
public String foo(Integer i);
现在我会发现像这样称呼前者 foo()
是非常自然的:
boolean result = foo(myNumber);
现在,如果 myNumber
恰好是 Integer
,则将调用后者 foo()
。它会 return 一个 String
并且我会在运行时发生类型转换错误。我不会感到惊讶。
… why then we can still have runtime polymorphism and it be considered
static typing, but it wouldn't be if we did dynamic resolution of
overloaded methods.
Java 兼有:静态类型和运行时类型。当我将 Integer
存储到声明为 Number
的变量中时,Number
是静态类型而 Integer
是运行时类型(顺便说一句,类型不一样作为 class)。你是对的,当我做 myObject.foo(arg)
时,myObject
的运行时类型决定调用 foo()
的哪个实现。可以想象 arg
的运行时类型也可能参与了决策。它会变得更复杂,我不确定收益。
重载方法是具有相同名称但采用不同参数的方法。您可以简单地看到它采用的参数的类型和顺序(也是它的值的类型returns)作为名称的一部分。
在 C++ 中,这实际上更“公开”一些 - 这种语言在内部调整名称以匹配参数,void h(int, char)
变成类似于 h__Fic
和 void h(int)
类似于 h__Fi
.这些损坏的名称偶尔会出现在有关它们未被解析的错误消息等地方。在Java中,这个暴露较少,但内部可比。这也称为“签名”。
然后你的问题简化为“如果方法有不同的名称,为什么它们在编译时解析而不是 运行 时?”。具有不同签名的两种方法之间的分辨率不会随时间而改变,因此根本没有理由也没有任何好处来延迟它。
我试图理解为什么我们有覆盖方法的多态性/动态绑定,而不是重载方法。我知道有一种间接方法可以让我们索引到 vtable 中,从而允许我们使用 C++ 或 Java 这样的语言来执行此操作。我很好奇为什么解析重载方法的情况不同——我的直觉让我相信我们可以有一个间接级别,允许我们在运行时确定根据运行时类型调用哪个重载方法。
我想知道这个设计决定是出于性能原因还是我忽略了考虑额外的复杂性。
我没有读懂语言设计者的想法,所以不能告诉你。我是这样想的:
- 重写方法是同一方法在 superclass/subclass 层次结构中不同级别的不同实现。 subclass 一般使用相同的方法签名(允许 return 更具体的类型并声明更少的抛出异常,但不能完全重新定义方法头,否则将不再是覆盖)。
- 重载方法实际上只是碰巧具有相同名称的不同方法。然后使用参数类型进行区分。就像编译器总是在编译时决定调用哪个方法一样,重载方法也是如此。
作为观察结果(可能是次要观察结果),通过重载方法的运行时解析,我们无法再对 return 值进行静态类型检查。假设我们有
public boolean foo(Number n);
public String foo(Integer i);
现在我会发现像这样称呼前者 foo()
是非常自然的:
boolean result = foo(myNumber);
现在,如果 myNumber
恰好是 Integer
,则将调用后者 foo()
。它会 return 一个 String
并且我会在运行时发生类型转换错误。我不会感到惊讶。
… why then we can still have runtime polymorphism and it be considered static typing, but it wouldn't be if we did dynamic resolution of overloaded methods.
Java 兼有:静态类型和运行时类型。当我将 Integer
存储到声明为 Number
的变量中时,Number
是静态类型而 Integer
是运行时类型(顺便说一句,类型不一样作为 class)。你是对的,当我做 myObject.foo(arg)
时,myObject
的运行时类型决定调用 foo()
的哪个实现。可以想象 arg
的运行时类型也可能参与了决策。它会变得更复杂,我不确定收益。
重载方法是具有相同名称但采用不同参数的方法。您可以简单地看到它采用的参数的类型和顺序(也是它的值的类型returns)作为名称的一部分。
在 C++ 中,这实际上更“公开”一些 - 这种语言在内部调整名称以匹配参数,void h(int, char)
变成类似于 h__Fic
和 void h(int)
类似于 h__Fi
.这些损坏的名称偶尔会出现在有关它们未被解析的错误消息等地方。在Java中,这个暴露较少,但内部可比。这也称为“签名”。
然后你的问题简化为“如果方法有不同的名称,为什么它们在编译时解析而不是 运行 时?”。具有不同签名的两种方法之间的分辨率不会随时间而改变,因此根本没有理由也没有任何好处来延迟它。