Java方法参考

时间:2012-04-10 08:29:35

标签: java

我有一些使用这些方法的课程:

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)这样的对象(类“type”与方法相关联)

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

我想知道如何向HashMap添加方法引用(替换“问题”)。

P.S。我来自C#,我只是写Dictionary<Type, Action>:)

10 个答案:

答案 0 :(得分:13)

现在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");
        }
    }

}

答案 1 :(得分: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);

答案 2 :(得分:3)

Method method = TestClass.class.getMethod("method name", type)

答案 3 :(得分:3)

使用接口而不是函数指针。因此,定义一个接口,定义您要调用的函数,然后调用接口,如上例所示。要实现该接口,您可以使用匿名内部类。

void DoSomething(IQuestion param) {
    // ...
    param.question();
}

答案 4 :(得分:2)

虽然您可以在地图中存储java.lang.reflect.Method个对象,但我建议不要这样做:您仍然需要在调用时传递用作this引用的对象,并使用原始字符串作为方法名称可能会在重构中出现问题。

执行此操作的一种方法是提取接口(或使用现有接口)并使用匿名类进行存储:

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

我必须承认,这比C#变量更加冗长,但这是Java的常见做法 - 例如何时使用Listeners进行事件处理。但是,构建在JVM上的其他语言通常具有此类处理程序的简写符号。通过使用接口方法,您的代码与Groovy,Jython或JRuby兼容,并且它仍然是类型安全的。

答案 5 :(得分: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

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

还有另一个问题。如果有人创建了一个新的问题类,那么在运行时之前你不知道该类的地图中没有条目,你有编写像if (!map.containsKey(question.getClass())) { // explode }这样的代码来处理这种可能性。

但还有另一种选择......

还有另一种模式 为您提供编译时安全性,并且意味着您不需要编写任何代码来处理&#34;缺少条目&#34;。该模式称为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
    }
}

另请注意两个类如何相互引用 - 它们仅引用接口,因此Tester和Question实现之间完全脱钩(这使得单元测试/嘲笑也更容易。)

答案 6 :(得分:1)

答案 7 :(得分:1)

您在代码注释中提到每个方法都使用某种类型的对象。由于这是一项常见操作,因此Java已经为您提供了一个名为http://pastie.org/10730081functional interface,它可以将某种类型的对象作为输入并执行一些操作在它上面(到目前为止你已经在问题中提到的两个词:“消费”和“行动”)。

因此,地图可以保存键所在的条目,例如MyClass1MyClass2,并且该值是该类对象的使用者:

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

由于Consumer是一个功能接口,即只有一个抽象方法的接口,因此可以使用lambda表达式定义:

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

其中testClassTestClass的实例。

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

Consumer<T> consumer = testClass::methodForTypeT;

然后,如果您将TestClass的方法的签名更改为method1(MyClass1 obj)method2(MyClass2 obj),则可以将这些方法引用添加到地图中:

consumersMap.put(MyClass1.class, testClass::method1);
consumersMap.put(MyClass2.class, testClass::method2);

答案 8 :(得分: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");

    }
}

给出:
啵!
是的,例子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");
    }
}

使用as:

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

答案 9 :(得分:-1)

使用reflaction API

方法methodObj = TestClass.class.getMethod(&#34;方法名称&#34;,类型)