Java 8 - 将lambda表达式存储在列表中

16

我想知道是否可以将lambda表达式存储在某种容器中,例如ArrayList或HashMap。 我想更改那段代码:

public enum OPCODE implements BinaryOperator<Integer> {
    MOV((x, y) -> y),
    INC((x, y) -> ++x),
    DEC((x, y) -> --x),
    ADD((x, y) -> x + y),
    SUB((x, y) -> x - y);

    private final BinaryOperator<Integer> binaryOperator;

    OPCODE(BinaryOperator<Integer> binaryOperator) {
        this.binaryOperator = binaryOperator;
    }  

    @Override
    public Integer apply(Integer integer, Integer integer2) {
        return binaryOperator.apply(integer, integer2);
    }
}

转化为类似这样的内容:

List<BinaryOperator<Integer>> opcodes = new ArrayList<BinaryOperator<Integer>>(){
    ((x, y) -> y),
    ((x, y) -> ++x)
};

等等。

然后像这样使用它:

opcodes[0].apply(a, b);

这真的可能吗?


4
顺便提一下,你的操作INCDEC可能没有达到期望的效果,因为Java是按值传递的,所以修改参数并不会改变调用方的任何值,因此(x, y) -> ++x是表述(x, y) -> x+1的一种误导方式,同样地,(x, y) -> --x实际上是(x, y) -> x-1 - Holger
所以,这个问题实际上只是在问(1)如何使用List和(2)如何使用给定元素实例化ArrayList... - MCMastery
@Holger 即使 OP 使用 IDE,它也应该在那里触发警告。 - Eugene
6个回答

12

您可以创建这样的列表:

List<BinaryOperator<Integer>> opcodes = Arrays.asList((x, y) -> y, (x, y) -> ++x);

// sample
int a=14,b=16;
System.out.println(opcodes.get(0).apply(a, b)); // prints 16
System.out.println(opcodes.get(1).apply(a, b)); // prints 15

或者更正你尝试初始化列表的方式。

List<BinaryOperator<Integer>> opcodes = new ArrayList<BinaryOperator<Integer>>() {{
    add((x, y) -> y);
    add((x, y) -> ++x);
    add((x, y) -> --x);
    add((x, y) -> x + y);
    add((x, y) -> x - y);
}};

12
你的子类写操作码列表的方式每次使用该方法都会创建另一个子类,这样效率真的很低。 - Ferrybig
1
谢谢,我完全忘记了 Arrays.asList()。有人建议将助记符更改为清晰的数字,因为这样更加实际。好吧,助记符就移动到了注释中 :> - Jump3r
5
不,所谓的双括号初始化实际上创建了一个 ArrayList 的匿名子类,然后向其中添加了实例初始化器。这是一种非常低效的向列表中添加元素的方式。使用 Arrays.asList 的效率要高得多。 - Boris the Spider
@Eugene 在这种情况下是可以的。Java仅使用基于对象标识的相等性来处理lambda表达式。实际上,将lambda表达式视为相等的概念本身就很奇怪——除了标识以外,不确定还有什么可以提供有用的比较方法。 - Boris the Spider
1
@Eugene同意。那么enum更符合惯用语。只是你的问题措辞有点令人困惑。 - Boris the Spider
显示剩余4条评论

9
除了 @nullpointer 给出的很好的答案之外,您还可以考虑使用 Map 键来保留函数的原始 OPCODE 意图,它将是数组中的 lst,例如使用 Enum 作为键:
public enum OPCODES {
    MOV, ADD, XOR
}

可以进行引导:

Map<OPCODES, BinaryOperator<Integer>> opcodeMap = 
  new EnumMap<OPCODES, BinaryOperator<Integer>>(OPCODES.class);
opcodeMap.put(OPCODES.ADD, (x, y)-> x + y);
opcodeMap.put(OPCODES.MOV, (x, y) -> y);
opcodeMap.put(OPCODES.XOR, (x, y) -> x ^ y);

并使用:

System.out.println(opcodeMap.get(OPCODES.ADD).apply(1, 2));
System.out.println(opcodeMap.get(OPCODES.MOV).apply(1, 2));
System.out.println(opcodeMap.get(OPCODES.XOR).apply(1, 2));

如果不为MOV、INC等的序数索引0、1、2定义常量,数组将无法理解运算符的意图为“Lost”。 - StuartLC

3

你可以将lambda表达式存储在容器中,但真正的问题是为什么要这样做?将它们存储在List中很容易,但例如在Set/Map中则不同 - 你无法覆盖lambda表达式的equals/hashcode方法,因此你无法确定会发生什么。

既然你已经有一个Enum,为什么不使用更简单的方法:

Set<OPCODE> set = EnumSet.allOf(OPCODE.class);

2
因此,一旦您定义了运算符,就可以像这样执行某些操作:
List<BinaryOperator<Integer>> opcodes = new ArrayList<BinaryOperator<Integer>>() {{
    add(OPCODE.ADD);
    add(OPCODE.DEC);
}};

在你的主方法中测试:

opcodes.forEach(elm -> System.out.println(elm.apply(1,2)));

我相信想法是要改变它而不是重复使用它。但我认为这仍然是有效的。不过,通过这种方法,现在任何一个数据结构都没有太多用处了。 - Naman
你可以用reduce代替forEeach来实现那个结果 @nullpointer - Benjamin Gruenbaum

0

是的,您可以将lambda表达式放入列表或映射的值中。请记住,lambda表达式只是编写匿名类的一种花哨方式,而匿名类又只是new运算符的一种特殊情况。换句话说,operators.add((x, y) -> x + y)只是缩写形式。

final BinaryOperator<Integer> ADD = new BinaryOperator<Integer>() {
    @Override
    public Integer apply(final Integer x, final Integer y) {
        return x + y;
    }
};
operators.add(ADD);

按照同样的逻辑,operatorMap.put("add", (x, y) -> x + y);也会做你期望的事情。

然而,将lambda表达式放入集合中(包括将它们用作映射键)可能不会做你所期望的事情。通常,集合的行为取决于其元素类型的equalshashCode的定义,语言对这些方法没有超出Object定义规定的任何保证。因此,以下断言可能失败:

final Function<Object, String> func1 = Object::toString;
final Function<Object, String> func2 = Object::toString;
assert func1.equals(func2);

同样地,以下内容:
final Function<Object, String> func = Object::toString;
final Set<Object> set = new HashSet<>();
set.add(func);
assert set.contains(Object::toString);

所以,当把lambda表达式放入基于Set的容器中时,包括用作Map键时,请小心处理。但是,它们可以放入List并像正常地使用Map值一样使用。


0

对@naomimyselfandi的答案进行改进。

您可以使用带有某些柯里化魔法的常规Function

List<Function<Integer, Function<Integer, Integer>>> opcodes = Arrays.asList(
    (x -> y -> y),
    (x -> y -> ++x),
    (x -> y -> --x),
    (x -> y -> x + y),
    (x -> y -> x - y)
);

请参考以下内容以获取更多详细信息。

@Holger 评论: Java Lambda 可以有多个参数吗?


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