18

私はこれらのメソッドを持ついくつかのクラスを持っています:

public class TestClass
{

    public void method1()
    {
        // this method will be used for consuming MyClass1
    }

    public void method2()
    {
        // this method will be used for consuming MyClass2
    }
}

およびクラス:

public class MyClass1
{
}

public class MyClass2
{
}

そしてHashMap<Class<?>, "question">、このような (key: class, value: method) ペアを格納する場所が必要です( class "type" は method に関連付けられています)

hashmp.add(Myclass1.class, "question");

メソッド参照を HashMap に追加する方法を知りたい (「質問」を置き換えます)。

ps私は単に書くだけのC#から来ましたDictionary<Type, Action>:)

4

9 に答える 9

15

Java 8 が出たので、Java 8 でこれを行う方法でこの質問を更新すると思いました。

package com.sandbox;

import java.util.HashMap;
import java.util.Map;

public class Sandbox {
    public static void main(String[] args) {
        Map<Class, Runnable> dict = new HashMap<>();

        MyClass1 myClass1 = new MyClass1();
        dict.put(MyClass1.class, myClass1::sideEffects);

        MyClass2 myClass2 = new MyClass2();
        dict.put(MyClass2.class, myClass2::sideEffects);

        for (Map.Entry<Class, Runnable> classRunnableEntry : dict.entrySet()) {
            System.out.println("Running a method from " + classRunnableEntry.getKey().getName());
            classRunnableEntry.getValue().run();
        }
    }

    public static class MyClass1 {
        public void sideEffects() {
            System.out.println("MyClass1");
        }
    }

    public static class MyClass2 {
        public void sideEffects() {
            System.out.println("MyClass2");
        }
    }

}
于 2014-04-14T19:19:33.573 に答える
11

これは Java 8 になる可能性が高い機能です。今のところ、これを行う最も簡単な方法はリフレクションを使用することです。

public class TestClass {
    public void method(MyClass1 o) {
        // this method will be used for consuming MyClass1
    }

    public void method(MyClass2 o) {
        // this method will be used for consuming MyClass2
    }
}

を使用して呼び出します

Method m = TestClass.class.getMethod("method", type);
于 2012-04-10T08:34:06.757 に答える
3
Method method = TestClass.class.getMethod("method name", type)
于 2012-04-10T08:34:55.707 に答える
3

関数ポインターの代わりにインターフェイスを使用します。したがって、呼び出したい関数を定義するインターフェイスを定義してから、上記の例のようにインターフェイスを呼び出します。インターフェイスを実装するには、匿名の内部クラスを使用できます。

void DoSomething(IQuestion param) {
    // ...
    param.question();
}
于 2012-04-10T08:43:55.147 に答える
3

コードのコメントで、各メソッドが特定のタイプのオブジェクトを消費することに言及しました。これは一般的な操作であるため、Javaは、特定のタイプのオブジェクトを入力として受け取り、それに対して何らかのアクションを実行する方法として機能するという機能インターフェースをすでに提供しています(これまでのところ、質問ですでに言及した2つの単語: 「消費」と「行動」)。Consumer

したがって、マップは、キーが や などのクラスであり、値がそのクラスのオブジェクトのコンシューマであるエントリを保持できMyClass1ますMyClass2

Map<Class<T>, Consumer<T>> consumersMap = new HashMap<>();

aConsumerは関数型インターフェース、つまり抽象メソッドを 1 つだけ持つインターフェースであるため、ラムダ式を使用して定義できます。

Consumer<T> consumer = t -> testClass.methodForTypeT(t);

testClassインスタンスですTestClass

このラムダは既存のメソッドを呼び出すだけなので、メソッド参照を直接methodForTypeT使用できます。

Consumer<T> consumer = testClass::methodForTypeT;

次に、 のメソッドのシグネチャTestClassを bemethod1(MyClass1 obj)およびmethod2(MyClass2 obj)に変更すると、これらのメソッド参照をマップに追加できます。

consumersMap.put(MyClass1.class, testClass::method1);
consumersMap.put(MyClass2.class, testClass::method2);
于 2016-02-20T11:01:37.210 に答える
2

マップにオブジェクトを格納することはできますがjava.lang.reflect.Method、これはお勧めできません。呼び出し時に参照として使用されるオブジェクトを渡す必要がありthis、メソッド名に生の文字列を使用すると、リファクタリングで問題が発生する可能性があります。

これを行う標準的な方法は、インターフェイスを抽出 (または既存のものを使用) し、格納に匿名クラスを使用することです。

map.add(MyClass1.class, new Runnable() {
  public void run() {
    MyClass1.staticMethod();
  }
});

これは C# バリアントよりもはるかに冗長であることを認めなければなりませんが、これは Java の一般的な方法です。たとえば、リスナーでイベント処理を行う場合などです。ただし、JVM に基づいて構築された他の言語には、通常、そのようなハンドラーの簡略表記があります。インターフェイス アプローチを使用することで、コードは Groovy、Jython、または JRuby と互換性があり、タイプ セーフのままです。

于 2012-04-10T08:41:28.717 に答える
2

の使用に関する直接の質問に答えるにはMap、提案されたクラスは次のようになります。

interface Question {} // marker interface, not needed but illustrative

public class MyClass1 implements Question {}

public class MyClass2 implements Question {}

public class TestClass {
    public void method1(MyClass1 obj) {
        System.out.println("You called the method for MyClass1!");
    }

    public void method2(MyClass2 obj) {
        System.out.println("You called the method for MyClass2!");
    }
}

次に、次のMapようになります。

Map<Class<? extends Question>, Consumer<Question>> map = new HashMap<>();

次のように入力されます。

TestClass tester = new TestClass();
map.put(MyClass1.class, o -> tester.method1((MyClass1)o)); // cast needed - see below
map.put(MyClass2.class, o -> tester.method2((MyClass2)o));

次のように使用します。

Question question = new MyClass1();
map.get(question.getClass()).accept(question); // calls method1

上記は正常に動作しますが、問題は、マップのキーの型をそのの型に接続する方法がないことです。つまり、ジェネリックを使用してコンシューマの値を適切に型付けすることができないため、メソッドを使用できません。参照:

map.put(MyClass1.class, tester::method1); // compile error

そのため、ラムダでオブジェクトをキャストして正しいメソッドにバインドする必要があります。

別の問題もあります。誰かが新しい Question クラスを作成した場合、そのクラスの Map にエントリがないことを実行時まで知ることはできず、その不測の事態を処理するようなコードを作成する必要があります。if (!map.containsKey(question.getClass())) { // explode }

しかし、代替手段があります...


コンパイル時の安全性を確保する別のパターンがありますこれは、「不足しているエントリ」を処理するためのコードを記述する必要がないことを意味します。このパターンはDouble Dispatchと呼ばれます ( Visitorパターンの一部です)。

次のようになります。

interface Tester {
    void consume(MyClass1 obj);
    void consume(MyClass2 obj);
}

interface Question {
    void accept(Tester tester);
}

public class TestClass implements Tester {
    public void consume(MyClass1 obj) {
        System.out.println("You called the method for MyClass1!");
    }

    public void consume(MyClass2 obj) {
        System.out.println("You called the method for MyClass2!");
    }
}

public  class MyClass1 implements Question {
    // other fields and methods
    public void accept(Tester tester) {
        tester.consume(this);
    }
}
public  class MyClass2 implements Question {
    // other fields and methods
    public void accept(Tester tester) {
        tester.consume(this);
    }
}

そしてそれを使用するには:

Tester tester = new TestClass();
Question question = new MyClass1();
question.accept(tester);

または多くの質問について:

List<Question> questions = Arrays.asList(new MyClass1(), new MyClass2());
questions.forEach(q -> q.accept(tester));

このパターンは、オブジェクトのそのクラスを処理するための正しいメソッドにバインドできるコールバックをターゲット クラスに配置することによって機能しthisます。

このパターンの利点は、別の Question クラスが作成された場合、accept(Tester)メソッドを実装する必要があるため、Question 実装者はテスターへのコールバックの実装を忘れず、テスターが新しい実装を処理できること自動的にチェックすることです。

public class MyClass3 implements Question {
    public void accept(Tester tester) { // Questions must implement this method
        tester.consume(this); // compile error if Tester can't handle MyClass3 objects
    }
}

また、2 つのクラスが相互に参照しないことにも注意してください。これらはinterfaceのみを参照するため、Tester と Question の実装が完全に切り離されています (これにより、単体テスト/モックも容易になります)。

于 2016-02-23T05:47:04.360 に答える