如何调用存储在HashMap中的方法?(Java)

72

我有一组命令(i、h、t等),用户将在命令行/终端Java程序中输入。我想存储一个命令/方法对的哈希表:

'h', showHelp()
't', teleport()

为了让我能够编写类似以下代码的内容:

所以我可以拥有类似这样的代码:

HashMap cmdList = new HashMap();

cmdList.put('h', showHelp());
if(!cmdList.containsKey('h'))
    System.out.print("No such command.")
else
   cmdList.getValue('h')   // This should run showHelp().

这是可能的吗?如果不行,有什么简单的方法可以实现这个功能呢?

只是好奇,使用哈希映射代替平庸的开关有什么优势? - Juliet
3
因为没有人想要记住87是“吃”的命令。而且,我可以简单地将命令、帮助文本等相互关联。 - cwhiii
3个回答

143

使用Java 8+和Lambda表达式

使用Lambda表达式(在Java 8+中可用),我们可以按以下方式完成:

class Test {

    public static void main(String[] args) throws Exception {
        Map<Character, Runnable> commands = new HashMap<>();

        // Populate commands map
        commands.put('h', () -> System.out.println("Help"));
        commands.put('t', () -> System.out.println("Teleport"));

        // Invoke some command
        char cmd = 't';
        commands.get(cmd).run();   // Prints "Teleport"
    }
}

在这种情况下,我有点懒,重用了Runnable接口,但是可以使用我在Java 7版本答案中发明的Command接口。

此外,还有() -> { ... }语法的替代方案。您也可以为helpteleport设置成员函数,并分别使用YourClass::helpYourClass::teleport


Java 7及以下版本

您真正想要做的是创建一个接口,例如命名为Command(或重用例如Runnable),并让您的映射类型为Map<Character, Command>。像这样:

import java.util.*;

interface Command {
    void runCommand();
}

public class Test {

    public static void main(String[] args) throws Exception {
        Map<Character, Command> methodMap = new HashMap<Character, Command>();

        methodMap.put('h', new Command() {
            public void runCommand() { System.out.println("help"); };
        });

        methodMap.put('t', new Command() {
            public void runCommand() { System.out.println("teleport"); };
        });

        char cmd = 'h';
        methodMap.get(cmd).runCommand();  // prints "Help"

        cmd = 't';
        methodMap.get(cmd).runCommand();  // prints "teleport"

    }
}

反射“黑科技”

话虽如此,您确实可以使用反射和Method类来实现您所要求的功能。

import java.lang.reflect.*;
import java.util.*;

public class Test {

    public static void main(String[] args) throws Exception {
        Map<Character, Method> methodMap = new HashMap<Character, Method>();

        methodMap.put('h', Test.class.getMethod("showHelp"));
        methodMap.put('t', Test.class.getMethod("teleport"));

        char cmd = 'h';
        methodMap.get(cmd).invoke(null);  // prints "Help"

        cmd = 't';
        methodMap.get(cmd).invoke(null);  // prints "teleport"

    }

    public static void showHelp() {
        System.out.println("Help");
    }

    public static void teleport() {
        System.out.println("teleport");
    }
}

10
不支持一等函数真是个麻烦 :) - 9000
我认为它是线程安全的,但我不确定。你同意它是线程安全的吗? - fatih tekin
我会怀疑“获取”操作是线程安全的。但为了确保,你可能想使用“Collections.synchronizedMap”。 - aioobe
1
@9000,答案已更新! :-) - aioobe
1
反射“黑科技”非常好用,如果有人需要传递参数: methodMap.put('h', Test.class.getMethod(“showHelp”,String.class));和methodMap.get(cmd)。invoke(null,“myParam”); - Eduardo Teixeira
显示剩余4条评论

7
虽然你可以通过反射来存储方法,但通常的做法是使用包装函数的匿名对象,即:
  interface IFooBar {
    void callMe();
  }


 'h', new IFooBar(){ void callMe() { showHelp(); } }
 't', new IFooBar(){ void callMe() { teleport(); } }

 HashTable<IFooBar> myHashTable;
 ...
 myHashTable.get('h').callMe();

http://download.oracle.com/javase/6/docs/api/java/lang/reflect/Method.html - Noel M

4

如果您正在使用JDK 7,现在可以像.NET一样使用lambda表达式来调用方法。

如果不是的话,最好的方法是创建一个Function对象:

public interface Action { void performAction(); }

Hashmap<string,Action> cmdList;

if (!cmdList.containsKey('h')) {
    System.out.print("No such command.")
} else {  
    cmdList.getValue('h').performAction();
}

4
事实上,Lambda表达式已延迟至JDK 8。 - Stephen C
hashMap函数map的主要部分是我们希望稍后使用参数调用它,任何人都可以提供包含参数的示例,例如int func(string p1,int 2),当我构建hashMap<event,func>时,我不知道值,当事件发生时,我调用hashmap.get(event).func(“now”,-1)。 - Steven

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接