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">像这样存储(键:类,值:方法)对的位置(类“类型”与方法相关联)

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 已经为您提供了一个名为的函数式接口Consumer,该接口可作为一种将某种类型的对象作为输入并对其执行某些操作的方式(到目前为止,您在问题中已经提到了两个词: “消费”和“行动”)。

因此,映射可以保存键是类的条目,例如MyClass1and MyClass2,值是该类对象的消费者:

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

由于 aConsumer是一个函数式接口,即只有一个抽象方法的接口,它可以使用 lambda 表达式来定义:

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

testClass的实例在哪里TestClass

由于此 lambda 只调用现有方法methodForTypeT,因此您可以直接使用方法引用

Consumer<T> consumer = testClass::methodForTypeT;

然后,如果您将方法的签名更改TestClassmethod1(MyClass1 obj)and 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

要回答有关使用 a 的直接问题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

这就是为什么您需要在 lambda 中强制转换对象以绑定到正确的方法。

还有另一个问题。如果有人创建了一个新的 Question 类,您直到运行时才知道Map 中没有该类的条目,并且您必须编写类似的代码if (!map.containsKey(question.getClass())) { // explode }来处理这种可能性。

但是有一个替代...


还有另一种模式确实为您提供了编译时安全性,这意味着您无需编写任何代码来处理“丢失的条目”。该模式称为双重调度(它是访问者模式的一部分)。

它看起来像这样:

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 实现者不会忘记实现对 Tester 的回调,自动检查 Testers 是否可以处理新的实现,例如

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

还要注意这两个类如何不相互引用 - 它们只引用interface,因此 Tester 和 Question 实现之间存在完全解耦(这也使得单元测试/模拟更容易)。

于 2016-02-23T05:47:04.360 回答
0

你的问题

给定你的类有一些方法:

public class MyClass1 {
    public void boo() {
        System.err.println("Boo!");
    }
}

public class MyClass2 {
    public void yay(final String param) {
        System.err.println("Yay, "+param);
    }
}

然后您可以通过反射获取方法:

Method method=MyClass1.class.getMethod("boo")

调用方法时,需要传递一个类实例:

final MyClass1 instance1=new MyClass1();
method.invoke(instance1);

把它放在一起:

public class Main {
    public static void main(final String[] args) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
        final Map<Class<?>,Method> methods=new HashMap<Class<?>,Method>();
        methods.put(MyClass1.class,MyClass1.class.getMethod("boo"));
        methods.put(MyClass2.class,MyClass2.class.getMethod("yay",String.class));


        final MyClass1 instance1=new MyClass1();
        methods.get(MyClass1.class).invoke(instance1);

        final MyClass2 instance2=new MyClass2();
        methods.get(MyClass2.class).invoke(instance2,"example param");

    }
}

给:
嘘!
是的,示例参数

请注意以下问题:

  • 将方法名称硬编码为字符串 - 这很难避免
  • 它是反射,因此在运行时访问类的元数据。容易出现很多异常(示例中未处理)
  • 您不仅需要告知方法名称,还需要告知参数类型以访问一种方法。这是因为方法重载是标准的,这是选择正确的重载方法的唯一方法。
  • 调用带参数的方法时要小心:没有编译时参数类型检查。

另一个答案

我猜您正在寻找的是一个简单的侦听器:即一种间接调用另一个类的方法的方法。

public class MyClass1 implements ActionListener {
    @Override
    public void actionPerformed(final ActionEvent e) {
        System.err.println("Boo!");
    }
}

public class MyClass2 implements ActionListener {
    @Override
    public void actionPerformed(final ActionEvent e) {
        System.err.println("Yay");
    }
}

用作:

public class Main {
    public static void main(final String[] args)  {
        final MyClass1 instance1=new MyClass1();
        final MyClass2 instance2=new MyClass2();

        final Map<Class<?>,ActionListener> methods=new HashMap<Class<?>,ActionListener>();

        methods.put(MyClass1.class,instance1);
        methods.put(MyClass2.class,instance2);



        methods.get(MyClass1.class).actionPerformed(null);
        methods.get(MyClass2.class).actionPerformed(null);
    }
}

这称为侦听器模式。我敢于重用 Java Swing 中的 ActionListener,但实际上您可以通过声明一个带有方法的接口来非常轻松地创建自己的侦听器。MyClass1, MyClass2 将实现该方法,然后您可以像调用...方法一样调用它。

没有反射,没有硬编码的字符串,没有混乱。(ActionListener 允许传递一个参数,该参数针对 GUI 应用程序进行了调整。在我的示例中,我只传递 null。)

于 2016-02-23T01:05:24.527 回答