Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reducing a list of UnaryOperators in Java 8

What is the preferred way of reducing a list of UnaryOperators in Java 8 till they represent one UnaryOperator that I can call apply on? For example I have the following

interface MyFilter extends UnaryOperator<MyObject>{};

public MyObject filterIt(List<MyFilter> filters,MyObject obj){
Optional<MyFilter> mf = filters
                           .stream()
                           .reduce( (f1,f2)->(MyFilter)f1.andThen(f2));

return mf.map(f->f.apply(obj)).orElse(obj);

}

But this code throws a ClassCastException at (MyFilter)f1.andThen(f2). I really want the effect of this code in the end:

MyObject o = obj;
for(MyFilter f:filters){
  o = f.apply(o);
}
return o;

But I am also curious of how we can reduce a collection of functions to one function, using compose or andThen.

like image 698
ABT Avatar asked Jun 03 '14 03:06

ABT


People also ask

What is unary operator java8?

Interface UnaryOperator<T>Represents an operation on a single operand that produces a result of the same type as its operand. This is a specialization of Function for the case where the operand and result are of the same type. This is a functional interface whose functional method is Function. apply(Object) .

What is a BiFunction?

The BiFunction Interface is a part of the java.util.function package which has been introduced since Java 8, to implement functional programming in Java. It represents a function which takes in two arguments and produces a result.

What is consumer interface in Java 8?

Interface Consumer<T>Represents an operation that accepts a single input argument and returns no result. Unlike most other functional interfaces, Consumer is expected to operate via side-effects. This is a functional interface whose functional method is accept(Object) . Since: 1.8.

How many operands does unary operators use in Java?

The unary operators require only one operand; they perform various operations such as incrementing/decrementing a value by one, negating an expression, or inverting the value of a boolean. The increment/decrement operators can be applied before (prefix) or after (postfix) the operand.


2 Answers

MyFilter inherits the method andThen from Function and therefore the returned type is Function and cannot be cast to MyFilter. But since it has the desired function signature, you can create the MyFilter instance using a lambda or method reference.

E.g. change (f1,f2)->(MyFilter)f1.andThen(f2) to (f1,f2)-> f1.andThen(f2)::apply.

With this change the entire method looks like:

public static MyObject filterIt(List<MyFilter> filters, MyObject obj) {
    Optional<MyFilter> mf =
      filters.stream().reduce( (f1,f2)-> f1.andThen(f2)::apply);
    return mf.map(f->f.apply(obj)).orElse(obj);
}

But you should rethink your design. There is no need to have the resulting function to be an instance of MyFilter, in fact, even the input can be relaxed to accept more than just List<MyFilter>:

// will accept List<MyFilter> as input
public static MyObject filterIt(
 List<? extends Function<MyObject,MyObject>> filters, MyObject obj) {
   List<Function<MyObject,MyObject>> l=Collections.unmodifiableList(filters);
   Optional<Function<MyObject,MyObject>> mf=l.stream().reduce(Function::andThen);
   return mf.orElse(Function.identity()).apply(obj);
}

or, using Stuart Marks’ hint for getting rid of the Optional:

// will accept List<MyFilter> as input
public static MyObject filterIt(
  List<? extends Function<MyObject,MyObject>> filters,MyObject obj) {
    List<Function<MyObject,MyObject>> l=Collections.unmodifiableList(filters);
    return l.stream().reduce(Function.identity(), Function::andThen).apply(obj);
}

Just for completeness, alternatively you could chain your MyFilters on a stream rather than composing a new function:

public static MyObject filterIt2(List<MyFilter> filters,MyObject obj) {
    Stream<MyObject> s=Stream.of(obj);
    for(MyFilter f: filters) s=s.map(f);
    return s.findAny().get();
}
like image 136
Holger Avatar answered Oct 08 '22 04:10

Holger


You can do a cast to Function<?, ?> before doing the reduction:

interface MyFilter extends UnaryOperator<MyObject>{};

Function<MyObject, MyObject> mf = filters.stream()
    .map(f -> (Function<MyObject, MyObject>) f)
    .reduce(Function.identity(), Function::compose);

This lets you use the Function.identity() and Function::compose methods, avoiding the need to reimplement them in your interface (like Stuart Marks suggested).

The cast is always safe because its up/widening.

like image 43
wilmol Avatar answered Oct 08 '22 03:10

wilmol