多态性歧义区分是如何工作的?

4

假设我有一个带有两个构造函数的类:

public class TestClass {
    ObjectOne o1;
    ObjectTwo o2;

    public TestClass(ObjectOne o1) {
        // ..
    }

    public TestClass(ObjectTwo o2) {
        // ..
    }
}

请假设ObjectOne是一个interface类型,而ObjectTwo 实现 ObjectOne。如果我调用以下内容会发生什么:
new TestClass(null);

如何确定正确的调用方法?由谁确定? Java与其他面向对象编程语言有什么区别吗?


好的,我看到通过抽象化我扭曲了问题: 请假设,“ObjectOne”是一个接口,“ObjectTwo”实现了ObjectOne接口。 在具体情况下,我有一个可序列化对象和一个ArrayList。现在这个区别如何工作?当回答这个附加问题时,我将很高兴给出投票。到目前为止,我仍然想知道为什么会有这么多重复的答案^^ - Peter Wippermann
ArrayListSerializable更具体,因此将选择ArrayList的重载。有关详细信息,请查看我的答案。 - polygenelubricants
6个回答

16
这个问题实际上是关于解决重载歧义,而不是关于运行时多态性(因为选择调用哪个重载方法是在编译时完成的)。
方法解析是一件复杂的事情,在这种情况下,代码是否能够编译取决于涉及的类型。在许多情况下,代码将会被编译。请参见下面的代码(请注意,String 实现了 CharSequence):
public class MyClass {
    MyClass(CharSequence charSeq) {
        System.out.println("CharSequence");
    }
    MyClass(String s) {
        System.out.println("String");
    }   
    public static void main(String[] args) {
        new MyClass(null); // prints "String"
        new MyClass(""); // prints "String"
        new MyClass((CharSequence) null); // prints "CharSequence"
        new MyClass((CharSequence) "");   // prints "CharSequence"
    }
}

请注意,没有强制转换时,将选择String重载。这正如JLS中所指定的那样: JLS 15.12.2.5 选择最具体的方法 如果有多个成员方法既可访问又适用于方法调用,则需要选择一个来为运行时方法分派提供描述符。Java编程语言使用的规则是选择最具体的方法。 StringCharSequence,但并非所有CharSequence都是String。因此,StringCharSequence更具体,这就是为什么在上面的示例中选择了String重载的原因。
值得注意的是,这个问题(实质上)在精彩的Java Puzzlers(强烈推荐)中出现过,具体来说,在第46个谜题“混淆的构造函数案例”中。
Java的重载解析过程分为两个阶段。第一阶段选择所有可访问和适用的方法或构造函数。第二阶段选择第一阶段选择的方法或构造函数中最具体的方法或构造函数。如果一个方法或构造函数可以接受传递给另一个的任何参数,则一个方法或构造函数比另一个方法或构造函数不够具体。
理解这个谜题的关键在于测试哪个方法或构造函数最具体的方法不使用实际参数:出现在调用中的参数。它们只用于确定哪些重载适用。一旦编译器确定哪些重载适用和可访问,它就选择最具体的重载,仅使用形式参数:出现在声明中的参数。

我将以《Effective Java第二版》中的一句话作为结尾,第41条:谨慎使用重载

决定哪个重载方法被选中的规则非常复杂。它们在语言规范中占据了33页,很少有程序员能够理解所有微妙之处。

不用说,这本书也是强烈推荐的。

另请参阅


1
非常感谢您提供这么全面和详细的答案。我真的很感激您花费的时间 :-) - Peter Wippermann

5

没有什么魔法。你需要将null参数转换为ObjectOne或ObjectTwo中的一个。

因此:

 new TestClass((ObjectOne) null);

或者

 new TestClass((ObjectTwo) null);

实际上,有一些“魔法”。请参见我的答案(附带对JLS的引用)。 - polygenelubricants

1

编译器会失败,因为您正在对构造函数进行模糊调用。C#编译器具有相同的行为。

为了使其在任一语言中工作,您必须将null转换为两种类型之一,以消除对构造函数的歧义调用。


1

显然它无法编译。


实际上,有很多情况下它会编译通过。请见我的答案以了解详情。 - polygenelubricants
下次如果有人已经回答了同样的问题,考虑为他的答案投票而不是再次回答相同的内容。这听起来可能不算什么,但是在 SO 上投票而不是重复回答部分是使其成为金矿的原因之一。重复回答只会增加混乱并稀释更好的答案。这是我的两分钱。 - Newtopian

1

编译错误:构造函数TestClass存在歧义


下次如果有人已经回答了相同的问题,请考虑投票支持他的回答,而不是再次回答。这听起来可能不算什么,但投票而不是重复回答,正是 Stack Overflow 成为知识宝库的部分原因之一。重复回答只会增加混乱,削弱更好的答案。以上纯属个人意见。 - Newtopian
你应该真正检查一下答案的时间。每个人都在同一时间写作。我不知道还有其他人在写相同的答案。 - Michał Mech

0

我不知道转换是否有效。你可以尝试这样做:

ObjectOne o1 = null;
new TestClass(o1);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接