I have the following enum.
public enum AggregationType {
MIN,
MAX,
AVERAGE
}
Let's assume that I have a function where I pass the enum value like:
public Float someFunction(AggregationType e) {
return (float) provides.stream()
.mapToDouble(this::someFunc)
.average()
.orElse(-1);
}
I want to apply this .average()
.min()
.max()
methods on a stream based on enum value.
How I can achieve this? I don't want to use simply switch function inside someFunction
but rather in this return statement.
So I want to have something like:
public Float someFunction(final AggregationType e) {
return (float) provides.stream()
.mapToDouble(this::someFunc)
.decideWhichMethodShouldBeUsed()
.orElse(-1);
}
where decideWhichMethodShouldBeUsed()
decides which function to use based on the enum.
When you can change the enum
type to
public enum AggregationType {
MIN(DoubleStream::min),
MAX(DoubleStream::max),
AVERAGE(DoubleStream::average);
public final Function<DoubleStream, OptionalDouble> operation;
AggregationType(Function<DoubleStream, OptionalDouble> f) {
operation = f;
}
}
you can implement the method like
public Float someFunction(final AggregationType aType) {
return (float)aType.operation.apply(provides.stream().mapToDouble(this::someFunc))
.orElse(-1);
}
If changing the enum type is not an option, you have to handle the mapping to the actual operation at the place where you want to implement someFunction
, e.g.
private static final Map<AggregationType,Function<DoubleStream, OptionalDouble>> OPS;
static {
EnumMap<AggregationType,Function<DoubleStream, OptionalDouble>>
m = new EnumMap<>(AggregationType.class);
m.put(AggregationType.MIN, DoubleStream::min);
m.put(AggregationType.MAX, DoubleStream::max);
m.put(AggregationType.AVERAGE, DoubleStream::average);
OPS = Collections.unmodifiableMap(m);
}
public Float someFunction(final AggregationType aType) {
return (float)OPS.get(aType).apply(provides.stream().mapToDouble(this::someFunc))
.orElse(-1);
}
You can also use a switch
statement, but it’s rather clunky
public Float someFunction(final AggregationType aType) {
DoubleStream ds = provides.stream().mapToDouble(this::someFunc);
OptionalDouble d;
switch(aType) {
case MAX: d = ds.max(); break;
case MIN: d = ds.min(); break;
case AVERAGE: d = ds.average(); break;
default: throw new AssertionError();
}
return (float)d.orElse(-1);
}
Things get better when you’re using a recent Java version, as then, you can use a switch
expression:
public Float someFunction(final AggregationType aType) {
DoubleStream ds = provides.stream().mapToDouble(this::someFunc);
return (float)(switch(aType) {
case MAX -> ds.max();
case MIN -> ds.min();
case AVERAGE -> ds.average();
}).orElse(-1);
}
This is only accepted by the compiler when all enum
constants are handled. Then, it will generate an equivalent to default -> throw new AssertionError();
behind the scenes which will never be taken at runtime, as long as no-one changes the enum
type after this code has been compiled.
Generally, only the first variant forces developers who consider adding new constants to AggregationType
to also also consider handling the associated operation
.