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 Function
s 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 Function
s. 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?
Variable-length arguments, abbreviated as varargs, are defined as arguments that can also accept an unlimited amount of data as input. The developer doesn't have to wrap the data in a list or any other sequence while using them. Let's understand the concept with the help of an example.
With Python, we can use the *args or **kwargs syntax to capture a variable number of arguments in our functions. Using *args , we can process an indefinite number of arguments in a function's position.
In mathematics and in computer programming, a variadic function is a function of indefinite arity, i.e., one which accepts a variable number of arguments.
A variable-length argument is a feature that allows a function to receive any number of arguments. There are situations where a function handles a variable number of arguments according to requirements, such as: Sum of given numbers. Minimum of given numbers and many more.
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);
}
}
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".
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With