为什么在执行时不能实现重载?

时间:2012-05-23 04:20:04

标签: java overloading language-design

请参阅以下示例:

interface I {}

class A implements I {}

class B implements I {}

class Foo{
    void f(A a) {}
    void f(B b) {}
    static public void main(String[]args ) {
        I[] elements = new I[] {new A(), new B(), new B(), new A()};
        Foo o = new Foo();
        for (I element:elements)
            o.f(element);//won't compile
    }
}

为什么重载方法不支持upcasting?

如果在运行时实施了重载,它将提供更大的灵活性。例如,访客模式会更简单。是否存在阻止Java执行此操作的任何技术原因?

7 个答案:

答案 0 :(得分:6)

重载解析涉及一些非平凡的规则来确定哪个重载是最合适的,并且在运行时很难有效地执行这些操作。相比之下,覆盖分辨率更容易 - 在困难的情况下,您只需要查找对象类的foo函数,并且在简单的情况下(例如,当只有一个实现,或者只有一个实现时)代码路径),您可以将虚拟方法转换为静态编译,非虚拟,非动态调度的调用(如果您是基于代码路径执行此操作,则必须快速检查以验证对象实际上是你期望的那个。)

事实证明,Java 1.4及更低版本没有运行时覆盖分辨率是一件好事,因为这会使泛型更难以改进。泛型在覆盖分辨率中起作用,但由于擦除,此信息在运行时将不可用。

答案 1 :(得分:4)

没有理论原因导致无法完成。 Common Lisp对象系统支持这种类型的构造 - 称为多个分派 - 尽管它在一个稍微不同的范例中这样做(方法,而不是附加到对象,是泛型的实例(或通用函数),可以在运行时对多个参数的值进行虚拟调度)。我相信Java也有扩展来启用它(想到多Java,虽然这可能是多重继承而不是多次分发)。

然而,可能有Java语言的原因,为什么它不能做,除了语言设计者只是认为不应该这样做,我会让别人去推理。但它确实为继承引入了复杂性。考虑:

interface A {}
interface B {}
class C implements A {}

class Foo {
    public void invoke(A a) {}
    public void invoke(B b) {}
}

class Bar extends Foo {
    public void invoke(C c) {}
}

class Baz extends Bar {
    public void invoke(A a) {}
}

Baz obj = new Baz();
obj.invoke(new C);

调用哪个invokeBazBar?什么是super.invoke?有可能提出确定性语义,但至少在某些情况下它们可能会引起混淆和惊讶。鉴于Java旨在成为一种简单的语言,我认为引入这种混淆的特征可能不会被视为符合其目标。

答案 2 :(得分:3)

我认为除语言设计者之外的任何人都不可能回答这个问题。我几乎不是这方面的专家,但我会提供我的意见。

通过阅读关于方法调用表达式的JLS 15.12,很明显选择正确的执行方法是一个已经很复杂的编译时过程;最重要的是在引入仿制药后。

现在想象一下,将所有这些移动到运行时只是为了支持mutimethods的单个功能。对我而言,它听起来像一个小功能,为语言增加了太多的复杂性,并且可能是一个具有一定性能影响的功能,因为所有这些决策都需要在运行时一遍又一遍地进行,而不仅仅是一次,就像今天一样,在编译时。

对于所有这些,我们可以添加一个事实:由于type erasure,不可能确定某些泛型类型的实际类型。在我看来,放弃静态类型检查的安全性并不符合Java的最佳利益。

无论如何,有一些有效的替代方案可以解决多个调度问题,也许这些替代方案几乎可以证明为什么它没有在语言中实现。因此,您可以使用经典visitor pattern,也可以使用一定量的反射。

有一个过时的MultiJava Project在Java中实现了多个调度支持,还有一些其他项目使用反射来支持Java中的多方法:Java MultimethodsJava Multimethods Framework。也许还有更多。

您还可以考虑使用其他支持多种方法的基于Java的语言,例如ClojureGroovy

此外,由于C#在一般的phillosopy中是一种非常类似于Java的语言,因此有必要更多地研究它是如何supports multimethods并思考在Java中提供类似功能的含义。如果您认为这是Java中值得拥有的功能,您甚至可以提交JEP,并且可能会考虑将来的Java语言版本。

答案 3 :(得分:3)

  

是否存在阻止Java执行此操作的任何技术原因?

代码正确性:您当前的示例提供了I的两个实现和两个相应的方法f。然而,没有什么能阻止实现I的其他类的存在 - 将解析移动到运行时也会将编译错误替换为可能隐藏的运行时错误。

性能:正如其他人提到的方法重载涉及相当复杂的规则,在编译时这样做肯定比在运行时对每个方法调用都这样做更快。

向后兼容性:当前重载的方法使用传递的参数的编译时类型而不是它们的运行时类型来解析,更改使用运行时信息的行为会破坏很多现有的应用程序。

如何解决这个问题

使用访客模式,我不明白有人会认为它很难。

interface I{
      void accept(IVisitor v);
}
interface IVisitor{
      void f(A a);
      void f(B b);
}

class A implements I{
    void accept(IVisitor v){v.f(this);}
}
class B implements I{
    void accept(IVisitor v){v.f(this);}
}
class Foo implements IVisitor{
 void f(A a) {}
 void f(B b) {}
 static public void main(String[]args ) {
    I[] elements = new I[] {new A(), new B(), new B(), new A()};
    Foo o = new Foo();
    for (I element:elements)
        element.accept(o);
 }


}

答案 4 :(得分:2)

不是Java的答案。此功能存在于C#4中:

using System;

public class MainClass {     
    public static void Main() {
        IAsset[] xx = { 
             new Asset(), new House(), new Asset(), new House(), new Car() 
        };     
        foreach(IAsset x in xx) {
            Foo((dynamic)x);
        }
    }


    public static void Foo(Asset a) {
        Console.WriteLine("Asset");
    }

    public static void Foo(House h) {
        Console.WriteLine("House");
    }

    public static void Foo(Car c) {
        Console.WriteLine("Car");
    }     
}

public interface IAsset { }      

public class Asset : IAsset { }

public class House : Asset { }

public class Car : Asset { }

输出:

Asset
House
Asset
House
Car

如果您使用的是C#3及以下版本,则必须使用反射,我在我的博客上发表了一篇关于它的帖子 C#中的多个调度http://www.ienablemuch.com/2012/04/multiple-dispatch-in-c.html

如果你想在Java中进行多次调度,你可能会采用反射路径。

这是Java的另一个解决方案:http://blog.efftinge.de/2010/03/multiple-dispatch-and-poor-mens-patter.html

答案 5 :(得分:2)

猜猜你只需要反思:

import java.lang.reflect.*;

interface I {}    
class A implements I {}    
class B implements I {}

public class Foo {

    public void f(A a) { System.out.println("from A"); }
    public void f(B b) { System.out.println("from B"); }

    static public void main(String[]args ) throws InvocationTargetException
        , NoSuchMethodException, IllegalAccessException 
    {
        I[] elements = new I[] {new A(), new B(), new B(), new A()};
        Foo o = new Foo();
        for (I element : elements) {
            o.multiDispatch(element);    
        }
    }

    void multiDispatch(I x) throws NoSuchMethodException
        , InvocationTargetException, IllegalAccessException 
    {    
        Class cls = this.getClass();

        Class[] parameterTypes = { x.getClass() };
        Object[] arguments = { x };

        Method fMethod = cls.getMethod("f", parameterTypes);
        fMethod.invoke(this,arguments);
    }       
}

输出:

from A
from B
from B
from A

答案 6 :(得分:1)

您的方法表示它接受AB作为I的派生类,它们可以包含更多详细信息,然后I

void f(A a) {}

当您尝试在案例界面A中发送超级I时,编译器需要确认您实际发送A A中可用的详细信息在I中不可用,也只是在运行时I实际上会引用A的实例,在编译时没有这样的信息,所以你必须明确地告诉编译器{ {1}}实际上是IA,您可以进行演员表示。