Java 8: Input a list of functional Interfaces and call them dynamically after .stream()

Viewed 983

I have the following method:


public void caller(){
    List<Class1> data1 = Arrays.asList(new Class1(), new Class1() ...);
    List<Class2> data2 = Arrays.asList(new Class2(), new Class2() ...);

    // The following is what I'm trying to implement:
    List<BiConsumer<Class1, Double>> peeks1 = Arrays.asList(Class1::setOneNum, Class1::setAnotherNum, Class1:: setMoreNum);
    List<BiConsumer<Class2, Double>> peeks2 = Arrays.asList(Class2::setSomeNum1, Class2::setSomeNum2);

    helper(data1, peeks1);
    helper(data2, peeks2);
    ...
}

private <T> List<T> helper(List<T> data, List<BiConsumer<T, Double>> peeks) {

        for(BiConsumer<T, Double> singlePeek: peeks){
            data = data.stream()
                    .peek(a -> singlePeek.accept(a, math.random()))
                    .collect(Collectors.toList());
        }

        return data;
    }

There are other implementation in common for Class1 and Class2, the only difference are the methods called after the .stream() which is why I'm trying to "merge" the functions into one helper.

Where BiConsumer is a setter. I want to call a list of setters after stream(). But I cannot input a list of functional interface into helper() (what I tried was Arrays.asList(Class1::setNum, Class1::setAnotherNum, Class1::setMoreNum) won't work as an input since Array.asList() only accepts Object). So is there any work-around? Thanks!

@user7 Thanks for pointing it out. I was careless but I've fixed the "typo". And added the caller function.

2 Answers

You have to specify the target type, when you call the .asList method:

Arrays.<BiConsumer<Object, Double>>asList(Class1::setOneNum, ...)

Update:

According to the updated code of the question the result of Arrays.asList is not directly handed over to the helper method, so no explicit typing is required.

The only possible reasons left why the code is not working are:

  1. At least one of the methods (setOneNum, setSomeNum1, ...) has wrong parameters types

  2. At least one of the methods is not static

Could I advise you in trying to make it a little bit more functional?

For your code consider the following helper, this one will make use of function as a first class citizen concept and make some High Order Functions:

    private <T, V> Function<Supplier<T>, Supplier<T>> helper(Supplier<V> v, 
                                                             BiConsumer<T, V> bc) {
        return (Supplier<T> r) -> {
            bc.accept(r.get(), v.get());
            return r;
        };
    }

This helper function expects a Supplier of some value kind of value and a BiConsumer that will be your setter function. The returns is a function of Suppliers of the same class you are working with.

With that we can make something like a pipe operator of functional languages. Their premises is that the data should processed in a pipeline operation.

List<Class1> data1 = Arrays.asList(new Class1(), new Class1());
List<Class2> data2 = Arrays.asList(new Class2(), new Class2());
Supplier<Double> random = () -> Math.random();

This will be our data, you have the same array and now a Supplier with the random value you want.

Now lets compose our pipeline with andThem:

        data1.stream()//
            .forEach(data -> {
                helper(random, Class1::setOneNum)//
                            .andThen(helper(random, Class1::setAnotherNum))//
                            .andThen(helper(random, Class1::setMoreNum))//
                            .apply(() -> data);
                    System.out.println(data.toString());
                });

        data2.stream()//
            .forEach(data -> {
                helper(random, Class2::setSomeNum1)//
                        .andThen(helper(random, Class2::setSomeNum2))//
                        .apply(() -> data);
                System.out.println(data.toString());
            });

As you can see the helper function can be chained together with "andThem" method of Function interface. This will make Java execute the helper function and use it's return as the parameter of the next Function.

The data parameter will hole the values of classes and will be changed each chain. As we iterated all objects will

And the result:

  • Class1 [oneNum=0,047, anotherNum=0,482, moreNum=0,339]
  • Class1 [oneNum=0,131, anotherNum=0,889, moreNum=0,411]
  • Class2 [someNum1=0,18, someNum2=0,004]
  • Class2 [someNum1=0,497, someNum2=0,702]

I think it is the same result you want. And as you can see you don't need to pass any generics as the Java will understand it well.

The classes that I made for reference:

class Class1 {
double oneNum;
double anotherNum;
double moreNum;

public double getOneNum() {
    return oneNum;
}

public void setOneNum(double oneNum) {
    this.oneNum = oneNum;
}

public double getAnotherNum() {
    return anotherNum;
}

public void setAnotherNum(double anotherNum) {
    this.anotherNum = anotherNum;
}

public double getMoreNum() {
    return moreNum;
}

public void setMoreNum(double moreNum) {
    this.moreNum = moreNum;
}

@Override
public String toString() {
    return MessageFormat.format("Class1 [oneNum={0}, anotherNum={1}, moreNum={2}]", oneNum, anotherNum, moreNum);
}

}

class Class2 {
double someNum1;
double someNum2;

public double getSomeNum1() {
    return someNum1;
}

public void setSomeNum1(double someNum1) {
    this.someNum1 = someNum1;
}

public double getSomeNum2() {
    return someNum2;
}

public void setSomeNum2(double someNum2) {
    this.someNum2 = someNum2;
}

@Override
public String toString() {
    return MessageFormat.format("Class2 [someNum1={0}, someNum2={1}]", someNum1, someNum2);
}

}

Related