Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use of Java 8 Lambdas with Generics

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 should happen only within the MathUtility class.

    //in client 
    MathUtility.sum(listOfInts, (Integer i)->{return (i<3);});

   //in utility
    class MathUtility<T extends Number> {
        public static <T extends Number> T sumWithCondition(List<T> numbers, Predicate<T> condition) {
            return numbers.parallelStream()
                    .filter(condition)
                    .map(i -> i)
                    .reduce(0, T::sum); //compile time error
        }
        public static <T extends Number> T avgWithCondition(List<T> numbers, Predicate<T> condition) {
            //another function
        }
        //lot many functions go here
    }

Right now it fails with this error - The method reduce(T, BinaryOperator<T>) in the type Stream<T> is not applicable for the arguments (int, T::sum)

Note: I do not want to write sum functions for different Number types

EDIT: Detailed discussion on this topic covered in this Github Notebook

like image 339
John Avatar asked Mar 10 '23 08:03

John


2 Answers

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
like image 57
Jacob G. Avatar answered Mar 16 '23 03:03

Jacob G.


  1. you must point out which actual type of Number to be summed, Since the Number class has no static sum method.
  2. you must assign identity with type of T extends Number,0 is an concrete type of Integer and does not compatible with type of T.

Possible Solution

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

MathUtility

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

SumOp

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);
    }
}
like image 31
holi-java Avatar answered Mar 16 '23 03:03

holi-java