9

Is it possible perform multiple mapping on collection? Following code compilation error:

... in Stream cannot be applied to java.util.function.Function<capture<?>,capture<?>>

private static List<?> multipleMapping(final Collection<?> collection, final List<Function<?, ?>> functions) {
    Stream<?> stream = collection.stream();
    for (Function<?, ?> function : functions) {
        stream = stream.map(function);
    }
    return stream.collect(Collectors.toList());
}

I would like to generic solution.

Tunaki
  • 132,869
  • 46
  • 340
  • 423
Artur
  • 217
  • 1
  • 5
  • 12

2 Answers2

9

The problem comes from the fact that you're using a generic wildcard ?. What you want is to have a parameterized type T, that will represent the type of the Stream element. Assuming the function would return the same type as their input, you could have:

private static <T> List<T> multipleMapping(final Collection<T> collection, final List<Function<T, T>> functions) {
    Stream<T> stream = collection.stream();
    for (Function<T, T> function : functions) {
        stream = stream.map(function);
    }
    return stream.collect(Collectors.toList());
}

This compiles fine: the mapper given to map correcly accepts a T and returns a T. However, if the functions don't return the same type as their input then you won't be able to keep type-safety and will have to resort to using List<Function<Object, Object>>.


Note that we could use a UnaryOperator<T> instead of Function<T, T>.

Also, you could avoid the for loop and reduce all functions into a single one using andThen:

private static <T> List<T> multipleMapping(final Collection<T> collection, final List<Function<T, T>> functions) {
    return collection.stream()
                     .map(functions.stream().reduce(Function.identity(), Function::andThen))
                     .collect(Collectors.toList());
}
Tunaki
  • 132,869
  • 46
  • 340
  • 423
  • Yes, that's fine. But i have different Functions, e.g: A -> B -> C -> D - not T -> T – Artur Mar 18 '16 at 23:32
  • 1
    @akfmb Then you can't have type-safety. The best you can have is a `List>` is you want to be as general as possible. If you want type-safety, you could do is have multiple overloaded method `multipleMapping(List, Function, Function`, etc... – Tunaki Mar 18 '16 at 23:34
  • 1
    You should compose the functions outside the stream, so you don't compose them for each element of the collection – fps Mar 18 '16 at 23:36
  • My list of the functions have sometimes 1 element - sometimes 2,3,4 - the number of elements is not constant. So I think that (as @Tunaki said) the best option for me is to use Object. – Artur Mar 18 '16 at 23:48
4

If you have few functions (i.e. if you can write them down), then I suggest you don't add them to a list. Instead, compose them into a single function, and then apply that single function to each element of the given collection.

Your multipleMapping() method would now receive a single function:

public static <T, R> List<R> multipleMapping(
    Collection<T> collection, Function<T, R> function) {

    return collection.stream()
            .map(function)
            .collect(Collectors.toList());
}

Then, in the calling code, you could create a function composed of many functions (you will have all the functions anyway) and invoke the multipleMapping() method with that function.

For example, suppose we have a list of candidates:

List<String> candidates = Arrays.asList(
        "Hillary", "Donald",
        "Bernie", "Ted", "John");

And four functions:

Function<String, Integer> f1 = String::length;

Function<Integer, Long> f2 = i -> i * 10_000L;

Function<Long, LocalDate> f3 = LocalDate::ofEpochDay;

Function<LocalDate, Integer> f4 = LocalDate::getYear;

These functions can be used to compose a new function, as follows:

Function<String, Integer> function = f1.andThen(f2).andThen(f3).andThen(f4);

Or also this way:

Function<String, Integer> composed = f4.compose(f3).compose(f2).compose(f1);

Now, you can invoke your multipleMapping() method with the list of candidates and the composed function:

List<Integer> scores = multipleMapping(candidates, function);

So we have transformed our list of candidates into a list of scores, by explicitly composing a new function from four different functions and applying this composed function to each candidate.

If you want to know who will win the election, you could check which candidate has the highest score, but I will let that as an exercise for whoever is interested in politics ;)

fps
  • 33,623
  • 8
  • 55
  • 110
  • Composing function looks better and it kept type control. Thanks @Federico. Your solution solves my problem. – Artur Mar 19 '16 at 02:31