Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Shorthand notation for Enum implementing lambda possible interface

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);
    }
}
like image 319
Stefan K. Avatar asked Feb 07 '23 14:02

Stefan K.


2 Answers

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

like image 66
Tunaki Avatar answered Feb 14 '23 00:02

Tunaki


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));
like image 30
fps Avatar answered Feb 13 '23 22:02

fps