Is it possible to do this using Predicate interface.
I have a client class that utilizes functions provided by a MathUtility class. Whatever the Mathmatical operation it
Number
to be summed, Since the Number
class has no static sum method. identity
with type of T extends Number
,0
is an concrete type of Integer and does not compatible with type of T
.you can make which actual type of Number
to be summed later, for example:
Integer sumToInt = MathUtility.sum(numbers, condition).as(Integer.class);
Double sumToDouble = MathUtility.sum(numbers, condition).as(Double.class);
OR you can make which actual type of Number
to be summed ahead, when using this style you are free to take type of actual Number
to every sum to be called, one the other hand, you can reuse it without taking any confused parameters and which is exactly what you want,for example:
SumOp<Integer> sumIntOp = SumOp.of(Integer.class);
//sumIntOp is reused twice.
Integer sumToInt1 = sumIntOp.sum(numbers1, condition1);
Integer sumToInt2 = sumIntOp.sum(numbers2, condition2);
class MathUtility {
private static <T extends Number> Sum sum(List<T> numbers,
Predicate<T> condition) {
return sum(numbers.parallelStream().filter(condition));
}
private static <T extends Number> Sum sum(Stream<T> stream) {
return new Sum() {
public <T extends Number> T as(Class<T> type) {
return SumOp.of(type).sum(stream);
}
};
}
interface Sum {
<T extends Number> T as(Class<T> type);
}
}
public class SumOp<T extends Number> {
private static final Map<Class<?>, SumOp<?>> OPERATORS = new HashMap<>();
private final T identity;
private final BinaryOperator<T> plusOp;
private final Function<Number, T> valueExtractor;
static {
register(Integer.class, new SumOp<>(0, Integer::sum, Number::intValue));
register(Double.class, new SumOp<>(0., Double::sum, Number::doubleValue));
//todo: add more SumOp for other Number types
}
public static <T extends Number> void register(Class<T> type,
SumOp<T> sumOp) {
OPERATORS.put(type, sumOp);
}
public static <T extends Number> SumOp<T> of(Class<T> type) {
return (SumOp<T>) OPERATORS.computeIfAbsent(type, it -> {
String message = "No SumOp registered for type:" + type.getName();
throw new IllegalArgumentException(message);
});
}
public SumOp(T identity,
BinaryOperator<T> plusOp,
Function<Number, T> valueExtractor) {
this.identity = identity;
this.valueExtractor = valueExtractor;
this.plusOp = plusOp;
}
public <I extends Number> T sum(List<I> numbers,
Predicate<I> condition) {
return sum(numbers.stream().filter(condition));
}
public T sum(Stream<? extends Number> stream) {
return stream.reduce(identity, this::plus, plusOp);
}
private T plus(Number augend, Number addend) {
return plusOp.apply(valueIn(augend), valueIn(addend));
}
private T valueIn(Number it) {
return valueExtractor.apply(it);
}
}
The answer is yes, that should be possible. The you defined is not known to have the method "sum", therefore the compiler complains. Try to define
public interace SumInterface {
public int sum(int a, int b);
}
(I haven't tried this code in IDE but this should do the trick)
A much simpler approach I tired is this.
The point to be noted is that the addition logic doesn't happen at the invoking side instead only within the MathUtility. The downside here is that you have to create Addition classes for every Number type you want the + operation.
System.out.println(
MathUtility.sum(listOfInts, i->i<4, new MathUtility.IntegerAddition()).get()
);
class MathUtility<T extends Number> {
static class IntegerAddition implements BinaryOperator<Integer> {
@Override
public Integer apply(Integer t, Integer u) {
return t + u;
}
}
public static <T extends Number> Optional<T> sum(List<T> list, Predicate<T> condition, BinaryOperator<T> operation){
//ability to add is only here
return list.parallelStream()
.filter(condition)
.map(i -> i)
.reduce(operation);
}
}
Is there a way to do it without writing a sum function for every possible type of
T
that i'm expecting?
As Aaron Davis stated in a comment above, you can pass the reduction parameters to the method itself.
public static <T> T sumWithCondition(List<T> numbers, Predicate<T> condition, T identity, BinaryOperator<T> accumulator) {
return numbers.parallelStream().filter(condition).reduce(identity, accumulator);
}
An example would be:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
System.out.println(sumWithCondition(list, i -> i > 1, 0, (a, b) -> a + b));
>> 14
List<BigInteger> list2 = Arrays.asList(BigInteger.ONE, BigInteger.ONE);
System.out.println(sumWithCondition(list2, i -> true, BigInteger.ZERO, (a, b) -> a.add(b)));
>> 2