Create a method that accepts variable length of Fu

2020-07-07 11:29发布

问题:

Suppose I have a string: String s = "1,2,3,4,5,6". I would like to create a method combineFunctions() that would take a variable length sequence of Functions as an argument and apply all of the operations in that order.

The functions may have different <T,U> types.

Example uses of such a function would be the following:

Combine<String> c = new Combine<>(s);
List<String> numbers = c.combineFunctions(splitByComma);
Integer max = c.combineFunctions(splitByComma,convertToInt, findMax);

What I have tried (the <U> here is not of much use here):

public <U> void combineFunctions(
        Function<? extends Object, ? extends Object>... functions) {

}

But I am stuck at getting type of last one of the Functions. I was also thinking about a recursive approach but the varargs parameter has to be the last one.

Would it be possible to implement such method in Java?

回答1:

The problem with such a function is that you loose all compile-time type checking and casting is necessary.

This would be an implementation, using andThen to combine functions together. This looks ugly because of all the casting and I'm not sure you can do it more properly. Notice also that this requires the creation of 2 Stream pipelines when only 1 is really necessary.

public static void main(String[] args) throws Exception {
    String str = "1,2,3,4,5,6";
    Function<Object, Object> splitByComma = s -> ((String) s).split(",");
    Function<Object, Object> convertToInt = tokens -> Stream.of((String[]) tokens).map(Integer::valueOf).toArray(Integer[]::new);
    Function<Object, Object> findMax = ints -> Stream.of((Integer[]) ints).max(Integer::compare).get();
    Integer max = (Integer) combineFunctions(splitByComma, convertToInt, findMax).apply(str);
    System.out.println(max);
}

@SafeVarargs
private static Function<Object, Object> combineFunctions(Function<Object, Object>... functions) {
    return Arrays.stream(functions)
                 .reduce(Function::andThen)
                 .orElseThrow(() -> new IllegalArgumentException("No functions to combine"));
}

To match the code in your question, you could wrap this into a class like this:

public class Combiner<R> {

    private Object input;

    public Combiner(Object input) {
        this.input = input;
    }

    @SuppressWarnings("unchecked")
    @SafeVarargs
    public final R combineFunctions(Function<Object, Object>... functions) {
        return (R) Arrays.stream(functions)
                         .reduce(Function::andThen)
                         .orElseThrow(() -> new IllegalArgumentException("No functions to combine"))
                         .apply(input);
    }

}


回答2:

The example in your question is quite easily solved by using the functional-style Streams. The "functional" approach to solving this is by using sequences of map operations, each step transforming the elements to a different type, and then optionally reducing/collecting the result.

For instance

String str = "1,2,3,4,5,6,7";
int max = Arrays.stream(str.split(",")).mapToInt(Integer::parseInt).max().orElse(0)

The same pattern would apply for other types of "function combinations".