16

I wonder if it's possible to store lambdas in some container, for ex. ArrayList or HashMap. I want to change that code:

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

To something like:

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

etc.

and use it like so:

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

It is even possible?

Jump3r
  • 1,028
  • 13
  • 29
  • 4
    As a side note, your operations `INC` and `DEC` may have not the desired effect, as Java is call by value, so modifying the parameters doesn't change any value of the caller side, so `(x, y) -> ++x` is a misleading way to formulate `(x, y) -> x+1` and likewise `(x, y) -> --x` does actually `(x, y) -> x-1`. – Holger Oct 28 '17 at 18:01
  • so really this question is just asking (1) how to use a `List` and (2) how to instantiate an `ArrayList` with given elements... – MCMastery Oct 28 '17 at 19:38
  • @Holger even the IDE should trigger a warning there (if OP is using one) – Eugene Oct 28 '17 at 20:27

6 Answers6

12

You can certainly create such a list as:

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

Or redressing the way you were trying to initializing the list

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);
}};
Naman
  • 27,789
  • 26
  • 218
  • 353
  • 12
    You subclass way of writing the opcodes list makes another subclass every time you use that method, this is really inefficient – Ferrybig Oct 28 '17 at 13:37
  • @Ferrybig Do you mean the `Arrays.asList` initialization? Didn't get you there. – Naman Oct 28 '17 at 14:05
  • 1
    Thanks, completely forgotten about Arrays.asList(). I've got suggestion to change mnemonic to clear numbers, because that way is more realistic. Well, mnemonics moved to comment then :> – Jump3r Oct 28 '17 at 16:32
  • 5
    No, so called double brace initialisation actually creates an anonymous subclass of `ArrayList` and then adds an instance initialiser to it. This is a hugely inefficient way of adding items to a `List`. `Arrays.asList` is significantly better. – Boris the Spider Oct 28 '17 at 17:37
  • @BoristheSpider True. Even I would have suggested to use the former approach for that reason. – Naman Oct 28 '17 at 17:40
  • @nullpointer I wonder what would happen when you would put these in a `Set`, since there is no way to override equals/hashcode for those lambdas ;) – Eugene Oct 28 '17 at 20:21
  • @Eugene in this case it would be fine. Java simply uses object identity based equality for lambdas. The idea of equality of lambdas is very odd anyway - not sure what besides identity would provide a useful method of comparing them. – Boris the Spider Oct 29 '17 at 07:36
  • @BoristheSpider this was exactly my point here, the best way to handle this would be via the `enum` - which he already has in place. And while we are there, we could have done just `EnumSet.allOf` to get those into a container if needed (as I have provided in an answer) – Eugene Oct 29 '17 at 07:45
  • 1
    @Eugene agree. Then `enum` is much more idiomatic. Just the phrasing of your query was a little confusing. – Boris the Spider Oct 29 '17 at 07:58
9

In additional @nullpointer's great answer, you can also consider using a Map key to retain the original OPCODE intention of the functions, which would be lst in the array, e.g. using an Enum as a key:

public enum OPCODES {
    MOV, ADD, XOR
}

Which can be bootstrapped:

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

And used:

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));
StuartLC
  • 104,537
  • 17
  • 209
  • 285
  • I mean the intent of the operators would be 'Lost' by the array unless constants were defined for the ordinal indexes 0,1,2 for MOV, INC etc – StuartLC Oct 28 '17 at 17:03
3

You could store lambdas inside a container, but the real question is why would you want to do that? Storing them in a List is easy, what about a Set/Map for example - you can't override equals/hashcode for lambdas - so you can't tell what would happen.

Since you already have an Enum there, why not use the simpler method:

Set<OPCODE> set = EnumSet.allOf(OPCODE.class);
Eugene
  • 117,005
  • 15
  • 201
  • 306
2

Hence you have defined your operator once you can do some thing like this:

List<BinaryOperator<Integer>> opcodes = new ArrayList<BinaryOperator<Integer>>() {{
    add(OPCODE.ADD);
    add(OPCODE.DEC);
}};

to test that in your main method:

opcodes.forEach(elm -> System.out.println(elm.apply(1,2)));
Maytham Fahmi
  • 31,138
  • 14
  • 118
  • 137
0

Yes, you can put lambdas in a list or values of a map just fine. Remember that lambdas are just a fancy way of writing anonymous classes, which in turn are just a special case of the new operator. Put another way, operators.add((x, y) -> x + y) is just shorthand for

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

By the same logic, operatorMap.put("add", (x, y) -> x + y); would also do exactly what you expect.

However, putting lambdas into a set - which includes using them as map keys - might not do what you expect. Generally, the behavior of a set depends on the definition of equals and hashCode by its element type, and the language doesn't make any guarantees for those methods beyond what's mandated by the definition of Object. Therefore, the following assert can fail:

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

Likewise the following:

final Function<Object, String> func = Object::toString;
final Set<Object> set = new HashSet<>();
set.add(func);
assert set.contains(Object::toString);

So, be careful putting lambdas into Set-based containers, including use as Map keys, but they can be put into Lists and used as Map values just fine.

naomimyselfandi
  • 315
  • 2
  • 6
0

Refining on @naomimyselfandi's answer.

You can use regular Function with some currying magic:

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

Please refer below for more details.

@Holger comment on: Can a java lambda have more than 1 parameter?

sankar
  • 189
  • 1
  • 10
Mahmoud
  • 9,729
  • 1
  • 36
  • 47