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

オーバーロードメソッドがアップキャストをサポートしないのはなぜですか?

実行時にオーバーロードが実装された場合、柔軟性が大幅に向上します。たとえば、Visitorパターンはより単純になります。Javaがこれを行うのを妨げる技術的な理由はありますか?

4

7 に答える 7

6

オーバーロードの解決には、どのオーバーロードが最適かを判断するためのいくつかの重要なルールが含まれており、実行時にこれらを効率的に行うのは困難です。対照的に、オーバーライドの解決は簡単です。難しいケースでは、オブジェクトのクラスの関数を検索するだけで済みfoo、簡単なケース (たとえば、実装が 1 つしかない場合、またはこのコード パスに 1 つの実装しかない場合) では、仮想メソッドを、静的にコンパイルされた非仮想の非動的ディスパッチ呼び出しに変えることができます (コード パスに基づいて実行している場合は、オブジェクトが実際にそのオブジェクトであることを確認するために簡単なチェックを行う必要があります)。あなたが期待する)。

結局のところ、Java 1.4 以前にランタイム オーバーライドの解決がなかったのは良いことでした。ジェネリックはオーバーライドの解決に役割を果たしますが、この情報は消去のために実行時に利用できません。

于 2012-05-23T04:33:32.083 に答える
4

それができない理論的な理由はありません。Common Lisp Object System は、このタイプの構築をサポートしています —複数ディスパッチと呼ばれます— ただし、これは多少異なるパラダイムでサポートしています (メソッドは、オブジェクトに付加されるのではなく、ジェネリック(またはジェネリック関数) のインスタンスであり、仮想ディスパッチを行うことができます)。複数のパラメータの値に対するランタイム)。それを可能にする Java の拡張機能もあると思います (Multi-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が呼び出されますか? Baz? Bar? とはsuper.invoke? 決定論的なセマンティクスを考え出すことは可能ですが、少なくとも場合によっては混乱と驚きを伴う可能性があります。Java が単純な言語を目指していることを考えると、このような混乱をもたらす機能が Java の目標に沿っていると見なされる可能性は低いと思います。

于 2012-05-24T15:14:29.093 に答える
3

言語の設計者以外の誰もがこの質問に答えることができるとは思いません。私はこのテーマの専門家ではありませんが、私の意見を述べます。

メソッド呼び出し式に関するJLS15.12を読むと、実行する適切なメソッドの選択がすでに複雑なコンパイル時プロセスであることは明らかです。とりわけジェネリック医薬品の導入後。

ここで、 mutimethodsの単一の機能をサポートするためだけに、これらすべてをランタイムに移動することを想像してください。私には、言語を複雑にしすぎる小さな機能のように聞こえます。おそらく、これらすべての決定を実行時に1回だけでなく、何度も繰り返す必要があるため、パフォーマンスにある程度の影響を与える機能です。今日のように、コンパイル時に。

これらすべてに、型消去のために特定のジェネリック型の実際の型を判別することが不可能であるという事実を追加することができます。静的型チェックの安全性を放棄することは、Javaにとって最善の利益ではないように思われます。

いずれにせよ、多重ディスパッチの問題に対処するための有効な代替案があり、おそらくこれらの代替案は、それが言語で実装されていない理由をほぼ正当化しています。したがって、古典的なビジターパターンを使用することも、ある程度の反射を使用することもできます。

Javaで多重ディスパッチサポートを実装した古いMultiJavaプロジェクトがあり、Javaでマルチメソッドをサポートするためにリフレクションを使用している他のプロジェクトがいくつかあります:Java MultimethodsJavaMultimethodsFramework。おそらくもっとたくさんあります。

また、 ClojureGroovyなどのマルチメソッドをサポートする代替のJavaベースの言語を検討することもできます。

また、C#は一般的な哲学においてJavaに非常に似ている言語であるため、マルチメソッドをサポートする方法についてさらに調査し、Javaで同様の機能を提供することの意味について瞑想することは興味深いかもしれません。これがJavaで持つ価値のある機能だと思う場合は、JEPを送信することもでき、Java言語の将来のリリースで考慮される可能性があります。

于 2012-05-23T06:44:01.103 に答える
3

Javaがこれを行うのを妨げる技術的な理由はありますか?

コードの正確性: 現在の例では、I の 2 つの実装と対応する 2 つのメソッド f が提供されています。ただし、I を実装する他のクラスの存在を妨げるものは何もありません。解像度をランタイムに移動すると、コンパイル エラーが隠れたランタイム エラーに置き換えられる可能性もあります。

パフォーマンス: 他の人が言及したように、メソッドのオーバーロードにはかなり複雑なルールが含まれます。コンパイル時に 1 回行う方が、実行時にすべてのメソッド呼び出しに対して行うよりも確実に高速です。

下位互換性: 現在オーバーロードされているメソッドは、渡された引数の実行時の型ではなくコンパイル時の型を使用して解決されます。実行時の情報を使用するように動作を変更すると、多くの既存のアプリケーションが機能しなくなります。

それを回避する方法

ビジターパターンを使って、難しいと思う人がいるとは思いません。

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


}
于 2012-05-26T21:32:46.783 に答える
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
于 2012-05-26T00:04:08.420 に答える
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 以下を使用している場合は、リフレクションを使用する必要があります。これについては、私のブログMultiple Dispatch in 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

于 2012-05-23T04:47:02.670 に答える
1

あなたのメソッドは、それが受け入れるAB、の派生クラスであると言っていIます。I

void f(A a) {}

Aあなたのケースのインターフェイスでのスーパークラスを送信しようとすると、コンパイラは、で利用可能な詳細がで利用できない可能性があるためI、実際に送信していることを確認する必要があります。 、したがって、実際にあることをコンパイラーに明示的に伝えるか、そうするためにキャストを行う必要があります。AAIIAIAB

于 2012-05-23T04:32:15.797 に答える