2012-05-23 89 views
8

请看下面的例子:为什么在执行时不能执行重载?

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 
    } 
} 

为什么不重载方法支持向上转型?

如果超载在运行时实现,它也提供了更多的灵活性。例如,访问者模式会更简单。是否有任何技术原因阻止Java这样做?

+0

我试图想象这种方法的语义是如何工作的,而我的大脑只是“ew”。我希望能够改变我正在使用的接口的实现,并让代码的其余部分工作完全一样。 –

+0

因为静态地试图确定一个对象的运行时间类型将类似于只是简单地运行该程序? – Perception

+1

@Perception他问为什么重载调度不能延迟到运行时而不是在编译时解析。这听起来像会给JVM和类加载逻辑增加很大的复杂性。 –

回答

1

你的方法表示将接受AB其派生的I类,它们可以包含更多的细节,然后I

void f(A a) {} 

当您尝试发送超类的A你的情况接口I,编译器想要一个证明,你实际上是在A发送A可用细节可能未I可用,也只能在运行时I实际上指的A可没有这样的信息的一实例在COM因此你必须明确地告诉编译器,I实际上是AB,并且你会这样说。

+0

我不明白这一点,但如果意图是说OP要的东西不能做,它可以。 – EJP

6

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

事实证明,这是一个很好的事情的Java 1.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

3

我不认为任何人,但语言可能能回答这个问题的设计师。我不是这方面的专家,但我只会提供我的看法。

通过阅读JLS 15.12有关方法调用表达式,它是相当清楚的是选择正确的方法来执行是一个已复杂的编译时间过程;首先是泛型的引入。

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

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

无论如何,有有效的替代品来处理多个调度问题,也许这些替代品几乎证明为什么它没有在语言实现。所以,你可以使用经典的visitor pattern或者你可以使用一定数量的反射。

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

你也可以考虑支持多方法的另一种基于Java的语言,如ClojureGroovy。另外,由于C#在其泛型phillosopy中与Java非常相似,因此可以研究更多关于它如何在Java中提供类似特性的内容的思考。如果您认为这是一个值得拥有的Java特性,您甚至可以提交一个JEP,并且可能会在未来版本的Java语言中考虑它。

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); 

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

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 
3

是否有防止从Java这样做的任何技术原因?

代码的正确性:你目前的实例提供了我两个实现和两个相应的方法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); 
} 


} 
相关问题