多态歧义区分如何工作?

Posted

技术标签:

【中文标题】多态歧义区分如何工作?【英文标题】:How does polymorph ambiguity distinction work? 【发布时间】:2011-02-09 05:03:57 【问题描述】:

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

public class TestClass 
    ObjectOne o1;
    ObjectTwo o2;

    public TestClass(ObjectOne o1) 
        // ..
    

    public TestClass(ObjectTwo o2) 
        // ..
    

请假设ObjectOneinterface 类型,而ObjectTwo implements ObjectOne。如果我打电话会发生什么:

new TestClass(null);

如何确定正确的调用方法?谁来决定? Java 和其他 OOP 语言有区别吗?

【问题讨论】:

好的,我看到通过抽象我扭曲了问题:请假设“ObjectOne”是一个接口,而“ObjectTwo”实现了 ObjectOne-Interface。在具体情况下,我有一个 Serializable 和一个 ArrayList。区别现在如何运作?当这个额外的问题得到回答时,我很乐意投票。到现在我还在纳闷,为什么会有这么多重复的答案^^ ArrayListSerializable 更具体,因此将选择 ArrayList 重载。有关详细信息,请参阅我的答案。 【参考方案1】:

这个问题实际上是关于解决模棱两可的重载,而不是真正关于运行时多态性(因为选择调用哪个重载方法是在编译时完成的)。

方法解析是一件复杂的事情,在这种情况下,代码是否编译完全取决于所涉及的类型是什么。 代码可以编译的情况很多。见下面的代码(注意String implements 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 Choosing the Most Specific Method

如果多个成员方法既可访问又适用于方法调用,则有必要选择一个为运行时方法分派提供描述符。 Java 编程语言使用选择最具体方法的规则。

String is-a CharSequence,但不是所有 CharSequence is-a String。因此,StringCharSequence 更具体,因此在上面的示例中选择了String 重载。


值得注意的是这个确切的问题(本质上)出现在精彩的Java Puzzlers(强烈推荐)中,特别是Puzzle 46: The Case of the Confusing Constructor

Java 的重载解决过程分为两个阶段。第一阶段选择所有可访问和适用的方法或构造函数。第二阶段选择在第一阶段选择的方法或构造函数中最具体的。如果一个方法或构造函数可以接受传递给另一个的任何参数,则它的具体性低于另一个

理解这个难题的关键是最具体的方法或构造函数的测试不使用实际参数:出现在调用中的参数。它们仅用于确定哪些重载适用。一旦编译器确定了哪些重载是适用和可访问的,它就会选择最具体的重载,只使用形式参数:出现在声明中的参数。


我将引用 Effective Java 2nd Edition第 41 条:明智地使用重载

决定选择哪个重载的规则极其复杂。它们在语言规范中占据了 33 页 页,很少有程序员能理解它们的所有细微之处。

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

另见

Polymorphism vs Overriding vs Overloading Method Overloading. Can you overuse it?

【讨论】:

非常感谢您提供全面而详细的回答。我非常感谢您为此付出的时间:-)【参考方案2】:

没有魔法。您必须将 null 参数强制转换为 ObjectOne 或 ObjectTwo。

所以:

 new TestClass((ObjectOne) null);

 new TestClass((ObjectTwo) null);

【讨论】:

其实有一些“魔力”。请参阅我的答案(参考 JLS)。【参考方案3】:

编译器将失败,因为您对构造函数进行了模棱两可的调用。 C# 编译器具有相同的行为。

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

【讨论】:

【参考方案4】:

显然它不会编译。

【讨论】:

实际上,有很多种场景可以编译。详情见我的回答。 下次不要和别人回答同样的问题,考虑投票给他的答案。听起来不多,但投票而不是重复答案部分是使 SO 成为金矿的部分原因。重复答案只会增加混乱并稀释更好的答案。我的 2 美分【参考方案5】:

编译错误:构造函数TestClass不明确

【讨论】:

下次不要和别人回答同样的问题,考虑投票给他的答案。听起来不多,但投票而不是重复答案部分是使 SO 成为金矿的部分原因。重复答案只会增加混乱并稀释更好的答案。我的 2 美分 你真的应该检查答案时间。每个人都在同一时间写作。我不知道其他人也在写同样的答案。【参考方案6】:

我不知道演员阵容是否有效。你可以试试这个:

ObjectOne o1 = null;
new TestClass(o1);

【讨论】:

以上是关于多态歧义区分如何工作?的主要内容,如果未能解决你的问题,请参考以下文章

阿花宝宝 Java基础笔记 之 多态区分

javascript的对象和变量之间的区分

"多态"的数据库连接池实现

区分Protobuf 3中缺失值和默认值

Excel VBA - 如何检测用户在工作表上选择整行并区分是不是选择了多行?

词义消歧