4

Coming across an pre-Java 8 example from jparsec, I wondered if I could leverage lambdas for a more expressive syntax.

Original example:

enum BinaryOperator implements Map2<Double,Double,Double> {
  PLUS {
    public Double map(Double a, Double b) {
      return a + b;
    }
  },
  MINUS {
    public Double map(Double a, Double b) {
      return a - b;
    }
  },
  MUL {
    public Double map(Double a, Double b) {
      return a * b;
    }
  },
  DIV {
    public Double map(Double a, Double b) {
      return a / b;
    }
  }
}

where

public interface Map2<A, B, T> {

  /** Maps {@code a} and {@code b} to the target object. */
  T map(A a, B b);
}

A plain Map2 instance is easily created with e.g. (a,b)->a+b, but is there a concise way to do the same for enums implementing a single function interface?

My current solution looks like this, but it is still verbose:

enum BinaryOperator implements Map2<Double,Double,Double> {
    PLUS((a, b) -> a + b), MINUS((a, b) -> a - b), MUL((a, b) -> a * b), DIV((a, b) -> a / b);

    private final BiFunction<Double, Double, Double> f;

    private BinaryOperator(BiFunction<Double, Double, Double> f) {
        this.f = f;
    }

    @Override
    public Double map(Double a, Double b) {
        return f.apply(a, b);
    }
}
Stefan K.
  • 7,701
  • 6
  • 52
  • 64
  • I don't think you can get much better than your current solution. Differences between enum constants can only be expressed in two ways: implementing a method or passing a constructor argument. The first one is the one we want to move away from, so constructor argument is our only choice. The constructor argument must be placed in a field. The enum must also implement `map()`, and we've arrived to your solution. – biziclop Mar 02 '16 at 15:09
  • 1
    I see eight lines of constant boilerplate code and the amount of code per constant as short as, e.g. `PLUS((a, b) -> a + b),`. How much more concise do you want to get? Related: [“Lambdas in the classical Operation enum example”](http://stackoverflow.com/q/23361418/2711488) – Holger Mar 02 '16 at 15:28

2 Answers2

7

The appropriate interface would be DoubleBinaryOperator here. This interface is suited when you need to operate on two double values and return a double. It works on primitive double instead of the wrapper class, thereby it doesn't have boxing overhead.

Your current solution is still the way to go. The code is clearly factored and there's no duplication. With the above interface, you can have:

enum BinaryOperator implements DoubleBinaryOperator {
    PLUS((a, b) -> a + b),
    MINUS((a, b) -> a - b),
    MUL((a, b) -> a * b),
    DIV((a, b) -> a / b);

    private final DoubleBinaryOperator f;

    private BinaryOperator(DoubleBinaryOperator f) {
        this.f = f;
    }

    @Override
    public double applyAsDouble(double a, double b) {
        return f.applyAsDouble(a, b);
    }

}

Implementing the DoubleBinaryOperator interface can prove to be useful: this way you can use the enum values directly as DoubleBinaryOperator, like:

DoubleStream.of(1, 2, 3).reduce(0, BinaryOperator.PLUS);

(This is just an example; there are better ways to do this specific instance.)

Tunaki
  • 132,869
  • 46
  • 340
  • 423
1

IMHO, much more concise would be to use an EnumMap that maps every single Operation value to its corresponding operation:

enum Operator { PLUS, MINUS, MUL, DIV; }

Then, you would need to initialize the EnumMap:

EnumMap<Operator, DoubleBinaryOperator> operations = new EnumMap<>(Operator.class);
operations.put(Operator.PLUS, (a, b) -> a + b);
operations.put(Operator.MINUS, (a, b) -> a - b);
operations.put(Operator.MUL, (a, b) -> a * b);
operations.put(Operator.DIV, (a, b) -> a / b);

Usage example:

double result = operations.get(Operator.MUL).applyAsDouble(2.0, 3.0); // 6.0

EDIT: As @Holger suggests in the comments, it would be nice to check that you've added all possible enum values to the map. That could be accomplished with an assert:

assert operations.keySet().equals(EnumSet.allOf(Operator.class));
fps
  • 33,623
  • 8
  • 55
  • 110
  • 2
    The problem is, you don’t notice if you forget to put a particular key into the map. Ok, adding a statement like `assert operations.keySet().equals(EnumSet.allOf(Operator.class));` after the initialization can solve this. – Holger Mar 02 '16 at 19:45
  • @Holger I'll add your recommendation to my answer. Thanks! – fps Mar 02 '16 at 20:33