In Java, is it possible to have a lambda accept multiple different types?
I.e:
Single variable works:
Function <Integer, Integer> adder = i -> i + 1;
System.out.println (adder.apply (10));
Varargs also work:
Function <Integer [], Integer> multiAdder = ints -> {
int sum = 0;
for (Integer i : ints) {
sum += i;
}
return sum;
};
//....
System.out.println ((multiAdder.apply (new Integer [] { 1, 2, 3, 4 })));
But I want something that can accept many different types of arguments, e.g:
Function <String, Integer, Double, Person, String> myLambda = a , b, c, d-> {
[DO STUFF]
return "done stuff"
};
The main use is to have small inline functions inside functions for convenience.
I've looked around google and inspected Java's Function Package, but could not find. Is this possible?
It's possible if you define such a functional interface with multiple type parameters. There is no such built in type. (There are a few limited types with multiple parameters.)
#FunctionalInterface
interface Function6<One, Two, Three, Four, Five, Six> {
public Six apply(One one, Two two, Three three, Four four, Five five);
}
public static void main(String[] args) throws Exception {
Function6<String, Integer, Double, Void, List<Float>, Character> func = (a, b, c, d, e) -> 'z';
}
I've called it Function6 here. The name is at your discretion, just try not to clash with existing names in the Java libraries.
There's also no way to define a variable number of type parameters, if that's what you were asking about.
Some languages, like Scala, define a number of built in such types, with 1, 2, 3, 4, 5, 6, etc. type parameters.
For something with 2 parameters, you could use BiFunction. If you need more, you can define your own function interface, like so:
#FunctionalInterface
public interface FourParameterFunction<T, U, V, W, R> {
public R apply(T t, U u, V v, W w);
}
If there is more than one parameter, you need to put parentheses around the argument list, like so:
FourParameterFunction<String, Integer, Double, Person, String> myLambda = (a, b, c, d) -> {
// do something
return "done something";
};
For this case you could use interfaces from default library (java 1.8):
java.util.function.BiConsumer
java.util.function.BiFunction
There is a small (not the best) example of default method in interface:
default BiFunction<File, String, String> getFolderFileReader() {
return (directory, fileName) -> {
try {
return FileUtils.readFile(directory, fileName);
} catch (IOException e) {
LOG.error("Unable to read file {} in {}.", fileName, directory.getAbsolutePath(), e);
}
return "";
};
}}
To make the use of lambda : There are three type of operation:
1. Accept parameter --> Consumer
2. Test parameter return boolean --> Predicate
3. Manipulate parameter and return value --> Function
Java Functional interface upto two parameter:
Single parameter interface
Consumer
Predicate
Function
Two parameter interface
BiConsumer
BiPredicate
BiFunction
For more than two, you have to create functional interface as follow(Consumer type):
#FunctionalInterface
public interface FiveParameterConsumer<T, U, V, W, X> {
public void accept(T t, U u, V v, W w, X x);
}
You could also use jOOL library - https://github.com/jOOQ/jOOL
It has already prepared function interfaces with different number of parameters. For instance, you could use org.jooq.lambda.function.Function3, etc from Function0 up to Function16.
Another alternative, not sure if this applies to your particular problem but to some it may be applicable is to use UnaryOperator in java.util.function library.
where it returns same type you specify, so you put all your variables in one class and is it as a parameter:
public class FunctionsLibraryUse {
public static void main(String[] args){
UnaryOperator<People> personsBirthday = (p) ->{
System.out.println("it's " + p.getName() + " birthday!");
p.setAge(p.getAge() + 1);
return p;
};
People mel = new People();
mel.setName("mel");
mel.setAge(27);
mel = personsBirthday.apply(mel);
System.out.println("he is now : " + mel.getAge());
}
}
class People{
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
So the class you have, in this case Person, can have numerous instance variables and won't have to change the parameter of your lambda expression.
For those interested, I've written notes on how to use java.util.function library: http://sysdotoutdotprint.com/index.php/2017/04/28/java-util-function-library/
Some lambda function :
import org.junit.Test;
import java.awt.event.ActionListener;
import java.util.function.Function;
public class TestLambda {
#Test
public void testLambda() {
System.out.println("test some lambda function");
////////////////////////////////////////////
//1-any input | any output:
//lambda define:
Runnable lambda1 = () -> System.out.println("no parameter");
//lambda execute:
lambda1.run();
////////////////////////////////////////////
//2-one input(as ActionEvent) | any output:
//lambda define:
ActionListener lambda2 = (p) -> System.out.println("One parameter as action");
//lambda execute:
lambda2.actionPerformed(null);
////////////////////////////////////////////
//3-one input | by output(as Integer):
//lambda define:
Function<String, Integer> lambda3 = (p1) -> {
System.out.println("one parameters: " + p1);
return 10;
};
//lambda execute:
lambda3.apply("test");
////////////////////////////////////////////
//4-two input | any output
//lambda define:
TwoParameterFunctionWithoutReturn<String, Integer> lambda4 = (p1, p2) -> {
System.out.println("two parameters: " + p1 + ", " + p2);
};
//lambda execute:
lambda4.apply("param1", 10);
////////////////////////////////////////////
//5-two input | by output(as Integer)
//lambda define:
TwoParameterFunctionByReturn<Integer, Integer> lambda5 = (p1, p2) -> {
System.out.println("two parameters: " + p1 + ", " + p2);
return p1 + p2;
};
//lambda execute:
lambda5.apply(10, 20);
////////////////////////////////////////////
//6-three input(Integer,Integer,String) | by output(as Integer)
//lambda define:
ThreeParameterFunctionByReturn<Integer, Integer, Integer> lambda6 = (p1, p2, p3) -> {
System.out.println("three parameters: " + p1 + ", " + p2 + ", " + p3);
return p1 + p2 + p3;
};
//lambda execute:
lambda6.apply(10, 20, 30);
}
#FunctionalInterface
public interface TwoParameterFunctionWithoutReturn<T, U> {
public void apply(T t, U u);
}
#FunctionalInterface
public interface TwoParameterFunctionByReturn<T, U> {
public T apply(T t, U u);
}
#FunctionalInterface
public interface ThreeParameterFunctionByReturn<M, N, O> {
public Integer apply(M m, N n, O o);
}
}
I think we can pass a map as an argument and pass the different values as elements of the map.
Function <Map <String, Object>, Integer> multiAdder = i -> {
String param1 = (String)i.get("PARAM1");
Integer param2 = (Integer)i.get("PARAM2");
Double param3 = (Double)i.get("PARAM3");
Integer x = callAnotherMethod(param1, param2, param3);
return x;
};
//....
Map<String, Object> paramsMap = new HashMap<>();
paramsMap.put("PARAM1", "String Val");
paramsMap.put("PARAM2", new Integer(12));
paramsMap.put("PARAM3", new Double(45);
System.out.println ((multiAdder.apply (paramsMap )));
Related
Today I encountered a following Java assignment, and I can't figure out how to get past type erasure.
The task is to create a generic InputConverter class, which takes an input of type T and converts it using chain of multiple functions received as a method argument. It has to support a following notation:
Function<String, List<String>> lambda1 = ...;
Function<List<String>, String> lambda2 = ...;
Function<String, Integer> lambda3 = ...;
String input = ...;
List<String> res1 = new InputConverter(input).convertBy(lambda1);
Integer res2 = new InputConverter(input).convertBy(lambda1, lambda2, lambda3);
This is what I came up with:
import java.util.Arrays;
import java.util.function.Function;
public class InputConverter<T> {
private final T input;
public InputConverter(T input) {
this.input = input;
}
public <B> B convertBy(Function<T, ?> first, Function<?, ?>... functions) {
var res = first.apply(input);
Function<?, B> composed = Arrays.stream(functions)
.reduce(Function::andThen)
.orElse(Function.identity());
return composed.apply(res);
}
}
This doesn't work of course, since I can't find a way to determine the return type of the last function.
Notes:
InputConverter should define only one convertBy method, so method overloading is not an option.
This method should return the result of last function in the chain without the need of explicit casting.
Problem
You would need to chain the generics for each number of expected functions and chain the generic parameters as on the snippet below with five functions:
public <D> E convertBy(
Function<T, A> first, Function<A, B> second, Function<B, C> third,
Function<C, D> fourth, Function<D, E> fifth) {
...
}
However, this is not possible for unknown number of parameters (varargs). There is no such thing as "vargenerics" which would dynamically create and chain the generic parameters as above.
Solution
You can instead treat the InputConverter as a builder instead which returns self with each convertBy call and finally packs a result. This recursive behavior allows indefinite number of calls. Try it out:
public static class InputConverter<T> {
private final T data;
public InputConverter(T data) {
this.data = data;
}
public <U> InputConverter<U> convertBy(Function<T, U> function) {
return new InputConverter<>(function.apply(data));
}
public T pack() {
return data;
}
}
Pretty neat, isn't it? Let's see the usage on a minimal sample:
// let lambda1 split String by characters and create a List
Function<String, List<String>> lambda1 = str -> Arrays.stream(str.split(""))
.collect(Collectors.toList());
// let lambda2 get the first item
Function<List<String>, String> lambda2 = list -> list.get(0);
// let lambda3 parse a String into the Integer
Function<String, Integer> lambda3 = Integer::parseInt;
String input = "123"; // out sample numeric input
List<String> res1 = new InputConverter<String>(input) // don't forget the generics
.convertBy(lambda1)
.pack();
Integer res2 = new InputConverter<String>(input)
.convertBy(lambda1)
.convertBy(lambda2)
.convertBy(lambda3)
.pack();
System.out.println(res1); // [1, 2, 3]
System.out.println(res2); // 1
So, according to task's author the correct solution is this:
import java.util.Arrays;
import java.util.function.Function;
public class InputConverter<T> {
private final T input;
public InputConverter(T input) {
this.input = input;
}
public <B> B convertBy(Function<T, ?> first, Function... functions) {
var res = first.apply(input);
Function<Object, B> composed = Arrays.stream(functions)
.reduce(Function::andThen)
.orElse(Function.identity());
return composed.apply(res);
}
}
Which is not satisfying to me at all. It allows for using vararg parameters, but using raw, unparameterized Function makes no sense. Nikolas Charalambidis' answer is a much better solution as we preserve return type information and safety.
In Java, is it possible to have a lambda accept multiple different types?
I.e:
Single variable works:
Function <Integer, Integer> adder = i -> i + 1;
System.out.println (adder.apply (10));
Varargs also work:
Function <Integer [], Integer> multiAdder = ints -> {
int sum = 0;
for (Integer i : ints) {
sum += i;
}
return sum;
};
//....
System.out.println ((multiAdder.apply (new Integer [] { 1, 2, 3, 4 })));
But I want something that can accept many different types of arguments, e.g:
Function <String, Integer, Double, Person, String> myLambda = a , b, c, d-> {
[DO STUFF]
return "done stuff"
};
The main use is to have small inline functions inside functions for convenience.
I've looked around google and inspected Java's Function Package, but could not find. Is this possible?
It's possible if you define such a functional interface with multiple type parameters. There is no such built in type. (There are a few limited types with multiple parameters.)
#FunctionalInterface
interface Function6<One, Two, Three, Four, Five, Six> {
public Six apply(One one, Two two, Three three, Four four, Five five);
}
public static void main(String[] args) throws Exception {
Function6<String, Integer, Double, Void, List<Float>, Character> func = (a, b, c, d, e) -> 'z';
}
I've called it Function6 here. The name is at your discretion, just try not to clash with existing names in the Java libraries.
There's also no way to define a variable number of type parameters, if that's what you were asking about.
Some languages, like Scala, define a number of built in such types, with 1, 2, 3, 4, 5, 6, etc. type parameters.
For something with 2 parameters, you could use BiFunction. If you need more, you can define your own function interface, like so:
#FunctionalInterface
public interface FourParameterFunction<T, U, V, W, R> {
public R apply(T t, U u, V v, W w);
}
If there is more than one parameter, you need to put parentheses around the argument list, like so:
FourParameterFunction<String, Integer, Double, Person, String> myLambda = (a, b, c, d) -> {
// do something
return "done something";
};
For this case you could use interfaces from default library (java 1.8):
java.util.function.BiConsumer
java.util.function.BiFunction
There is a small (not the best) example of default method in interface:
default BiFunction<File, String, String> getFolderFileReader() {
return (directory, fileName) -> {
try {
return FileUtils.readFile(directory, fileName);
} catch (IOException e) {
LOG.error("Unable to read file {} in {}.", fileName, directory.getAbsolutePath(), e);
}
return "";
};
}}
To make the use of lambda : There are three type of operation:
1. Accept parameter --> Consumer
2. Test parameter return boolean --> Predicate
3. Manipulate parameter and return value --> Function
Java Functional interface upto two parameter:
Single parameter interface
Consumer
Predicate
Function
Two parameter interface
BiConsumer
BiPredicate
BiFunction
For more than two, you have to create functional interface as follow(Consumer type):
#FunctionalInterface
public interface FiveParameterConsumer<T, U, V, W, X> {
public void accept(T t, U u, V v, W w, X x);
}
You could also use jOOL library - https://github.com/jOOQ/jOOL
It has already prepared function interfaces with different number of parameters. For instance, you could use org.jooq.lambda.function.Function3, etc from Function0 up to Function16.
Another alternative, not sure if this applies to your particular problem but to some it may be applicable is to use UnaryOperator in java.util.function library.
where it returns same type you specify, so you put all your variables in one class and is it as a parameter:
public class FunctionsLibraryUse {
public static void main(String[] args){
UnaryOperator<People> personsBirthday = (p) ->{
System.out.println("it's " + p.getName() + " birthday!");
p.setAge(p.getAge() + 1);
return p;
};
People mel = new People();
mel.setName("mel");
mel.setAge(27);
mel = personsBirthday.apply(mel);
System.out.println("he is now : " + mel.getAge());
}
}
class People{
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
So the class you have, in this case Person, can have numerous instance variables and won't have to change the parameter of your lambda expression.
For those interested, I've written notes on how to use java.util.function library: http://sysdotoutdotprint.com/index.php/2017/04/28/java-util-function-library/
Some lambda function :
import org.junit.Test;
import java.awt.event.ActionListener;
import java.util.function.Function;
public class TestLambda {
#Test
public void testLambda() {
System.out.println("test some lambda function");
////////////////////////////////////////////
//1-any input | any output:
//lambda define:
Runnable lambda1 = () -> System.out.println("no parameter");
//lambda execute:
lambda1.run();
////////////////////////////////////////////
//2-one input(as ActionEvent) | any output:
//lambda define:
ActionListener lambda2 = (p) -> System.out.println("One parameter as action");
//lambda execute:
lambda2.actionPerformed(null);
////////////////////////////////////////////
//3-one input | by output(as Integer):
//lambda define:
Function<String, Integer> lambda3 = (p1) -> {
System.out.println("one parameters: " + p1);
return 10;
};
//lambda execute:
lambda3.apply("test");
////////////////////////////////////////////
//4-two input | any output
//lambda define:
TwoParameterFunctionWithoutReturn<String, Integer> lambda4 = (p1, p2) -> {
System.out.println("two parameters: " + p1 + ", " + p2);
};
//lambda execute:
lambda4.apply("param1", 10);
////////////////////////////////////////////
//5-two input | by output(as Integer)
//lambda define:
TwoParameterFunctionByReturn<Integer, Integer> lambda5 = (p1, p2) -> {
System.out.println("two parameters: " + p1 + ", " + p2);
return p1 + p2;
};
//lambda execute:
lambda5.apply(10, 20);
////////////////////////////////////////////
//6-three input(Integer,Integer,String) | by output(as Integer)
//lambda define:
ThreeParameterFunctionByReturn<Integer, Integer, Integer> lambda6 = (p1, p2, p3) -> {
System.out.println("three parameters: " + p1 + ", " + p2 + ", " + p3);
return p1 + p2 + p3;
};
//lambda execute:
lambda6.apply(10, 20, 30);
}
#FunctionalInterface
public interface TwoParameterFunctionWithoutReturn<T, U> {
public void apply(T t, U u);
}
#FunctionalInterface
public interface TwoParameterFunctionByReturn<T, U> {
public T apply(T t, U u);
}
#FunctionalInterface
public interface ThreeParameterFunctionByReturn<M, N, O> {
public Integer apply(M m, N n, O o);
}
}
I think we can pass a map as an argument and pass the different values as elements of the map.
Function <Map <String, Object>, Integer> multiAdder = i -> {
String param1 = (String)i.get("PARAM1");
Integer param2 = (Integer)i.get("PARAM2");
Double param3 = (Double)i.get("PARAM3");
Integer x = callAnotherMethod(param1, param2, param3);
return x;
};
//....
Map<String, Object> paramsMap = new HashMap<>();
paramsMap.put("PARAM1", "String Val");
paramsMap.put("PARAM2", new Integer(12));
paramsMap.put("PARAM3", new Double(45);
System.out.println ((multiAdder.apply (paramsMap )));
I'm porting a piece of code from .NET to Java and stumbled upon a scenario where I want to use stream to map & reduce.
class Content
{
private String propA, propB, propC;
Content(String a, String b, String c)
{
propA = a; propB = b; propC = c;
}
public String getA() { return propA; }
public String getB() { return propB; }
public String getC() { return propC; }
}
List<Content> contentList = new ArrayList();
contentList.add(new Content("A1", "B1", "C1"));
contentList.add(new Content("A2", "B2", "C2"));
contentList.add(new Content("A3", "B3", "C3"));
I want to write a function that can stream through the contents of contentlist and return a class with result
content { propA = "A1, A2, A3", propB = "B1, B2, B3", propC = "C1, C2, C3" }
I'm fairly new to Java so you might find some code that resembles more like C# than java
You can use proper lambda for BinaryOperator in reduce function.
Content c = contentList
.stream()
.reduce((t, u) -> new Content(
t.getA() + ',' + u.getA(),
t.getB() + ',' + u.getB(),
t.getC() + ',' + u.getC())
).get();
The most generic way to deal with such tasks would be to combine the result of multiple collectors into a single one.
Using the jOOL library, you could have the following:
Content content =
Seq.seq(contentList)
.collect(
Collectors.mapping(Content::getA, Collectors.joining(", ")),
Collectors.mapping(Content::getB, Collectors.joining(", ")),
Collectors.mapping(Content::getC, Collectors.joining(", "))
).map(Content::new);
This creates a Seq from the input list and combines the 3 given collectors to create a Tuple3, which is simply a holder for 3 values. Those 3 values are then mapped into a Content using the constructor new Content(a, b, c). The collector themselves are simply mapping each Content into its a, b or c value and joining the results together separated with a ", ".
Without third-party help, we could create our own combiner collector like this (this is based of StreamEx pairing collector, which does the same thing for 2 collectors). It takes 3 collectors as arguments and performs a finisher operation on the result of the 3 collected values.
public interface TriFunction<T, U, V, R> {
R apply(T t, U u, V v);
}
public static <T, A1, A2, A3, R1, R2, R3, R> Collector<T, ?, R> combining(Collector<? super T, A1, R1> c1, Collector<? super T, A2, R2> c2, Collector<? super T, A3, R3> c3, TriFunction<? super R1, ? super R2, ? super R3, ? extends R> finisher) {
final class Box<A, B, C> {
A a; B b; C c;
Box(A a, B b, C c) {
this.a = a;
this.b = b;
this.c = c;
}
}
EnumSet<Characteristics> c = EnumSet.noneOf(Characteristics.class);
c.addAll(c1.characteristics());
c.retainAll(c2.characteristics());
c.retainAll(c3.characteristics());
c.remove(Characteristics.IDENTITY_FINISH);
return Collector.of(
() -> new Box<>(c1.supplier().get(), c2.supplier().get(), c3.supplier().get()),
(acc, v) -> {
c1.accumulator().accept(acc.a, v);
c2.accumulator().accept(acc.b, v);
c3.accumulator().accept(acc.c, v);
},
(acc1, acc2) -> {
acc1.a = c1.combiner().apply(acc1.a, acc2.a);
acc1.b = c2.combiner().apply(acc1.b, acc2.b);
acc1.c = c3.combiner().apply(acc1.c, acc2.c);
return acc1;
},
acc -> finisher.apply(c1.finisher().apply(acc.a), c2.finisher().apply(acc.b), c3.finisher().apply(acc.c)),
c.toArray(new Characteristics[c.size()])
);
}
and finally use it with
Content content = contentList.stream().collect(combining(
Collectors.mapping(Content::getA, Collectors.joining(", ")),
Collectors.mapping(Content::getB, Collectors.joining(", ")),
Collectors.mapping(Content::getC, Collectors.joining(", ")),
Content::new
));
static Content merge(List<Content> list) {
return new Content(
list.stream().map(Content::getA).collect(Collectors.joining(", ")),
list.stream().map(Content::getB).collect(Collectors.joining(", ")),
list.stream().map(Content::getC).collect(Collectors.joining(", ")));
}
EDIT: Expanding on Federico's inline collector, here is a concrete class dedicated to merging Content objects:
class Merge {
public static Collector<Content, ?, Content> collector() {
return Collector.of(Merge::new, Merge::accept, Merge::combiner, Merge::finisher);
}
private StringJoiner a = new StringJoiner(", ");
private StringJoiner b = new StringJoiner(", ");
private StringJoiner c = new StringJoiner(", ");
private void accept(Content content) {
a.add(content.getA());
b.add(content.getB());
c.add(content.getC());
}
private Merge combiner(Merge second) {
a.merge(second.a);
b.merge(second.b);
c.merge(second.c);
return this;
}
private Content finisher() {
return new Content(a.toString(), b.toString(), c.toString());
}
}
Used as:
Content merged = contentList.stream().collect(Merge.collector());
If you don't want to iterate 3 times over the list, or don't want to create too many Content intermediate objects, then you'd need to collect the stream with your own implementation:
public static Content collectToContent(Stream<Content> stream) {
return stream.collect(
Collector.of(
() -> new StringBuilder[] {
new StringBuilder(),
new StringBuilder(),
new StringBuilder() },
(StringBuilder[] arr, Content elem) -> {
arr[0].append(arr[0].length() == 0 ?
elem.getA() :
", " + elem.getA());
arr[1].append(arr[1].length() == 0 ?
elem.getB() :
", " + elem.getB());
arr[2].append(arr[2].length() == 0 ?
elem.getC() :
", " + elem.getC());
},
(arr1, arr2) -> {
arr1[0].append(arr1[0].length() == 0 ?
arr2[0].toString() :
arr2[0].length() == 0 ?
"" :
", " + arr2[0].toString());
arr1[1].append(arr1[1].length() == 0 ?
arr2[1].toString() :
arr2[1].length() == 0 ?
"" :
", " + arr2[1].toString());
arr1[2].append(arr1[2].length() == 0 ?
arr2[2].toString() :
arr2[2].length() == 0 ?
"" :
", " + arr2[2].toString());
return arr1;
},
arr -> new Content(
arr[0].toString(),
arr[1].toString(),
arr[2].toString())));
}
This collector first creates an array of 3 empty StringBuilder objects. Then defines an accumulator that appends each Contentelement's property to the corresponding StringBuilder. Then it defines a merge function that is only used when the stream is processed in parallel, which merges two previously accumulated partial results. Finally, it also defines a finisher function that transforms the 3 StringBuilder objects into a new instance of Content, with each property corresponding to the accumulated strings of the previous steps.
Please check Stream.collect() and Collector.of() javadocs for further reference.
If have 2 CompletionStages, I can combine them with thenCombine method:
CompletionStage<A> aCompletionStage = getA();
CompletionStage<B> bCompletionStage = getB();
CompletionStage<Combined> combinedCompletionStage =
aCompletionStage.thenCombine(bCompletionStage, (aData, bData) -> combine(aData, bData));
If I have 3 or more CompletionStages, I can make a chain of thenCombine methods, but I have to use temporary objects to pass results. For example, here is a solution using Pair and Triple from the org.apache.commons.lang3.tuple package:
CompletionStage<A> aCompletionStage = getA();
CompletionStage<B> bCompletionStage = getB();
CompletionStage<C> cCompletionStage = getC();
CompletionStage<D> dCompletionStage = getD();
CompletionStage<Combined> combinedDataCompletionStage =
aCompletionStage.thenCombine(bCompletionStage, (Pair::of))
.thenCombine(cCompletionStage, (ab, c) ->
Triple.of(ab.getLeft(), ab.getRight(), c))
.thenCombine(dCompletionStage, (abc, d) ->
combine(abc.getLeft(), abc.getMiddle(), abc.getRight(), d));
Is there a better way to combine results from multiple CompletionStages?
The only way to combine multiple stages that scales well with a growing number of stages, is to use CompletableFuture. If your CompletionStages aren’t CompletableFutures you may still convert them using .toCompletableFuture():
CompletableFuture<A> aCompletionStage = getA().toCompletableFuture();
CompletableFuture<B> bCompletionStage = getB().toCompletableFuture();
CompletableFuture<C> cCompletionStage = getC().toCompletableFuture();
CompletableFuture<D> dCompletionStage = getD().toCompletableFuture();
CompletionStage<Combined> combinedDataCompletionStage = CompletableFuture.allOf(
aCompletionStage, bCompletionStage, cCompletionStage, dCompletionStage)
.thenApply(ignoredVoid -> combine(
aCompletionStage.join(), bCompletionStage.join(),
cCompletionStage.join(), dCompletionStage.join()) );
This contains more boilerplate than combining two stages via thenCombine but the boilerplate doesn’t grow when adding more stages to it.
Note that even with your original thenCombine approach, you don’t need a Triple, a Pair is sufficient:
CompletionStage<Combined> combinedDataCompletionStage =
aCompletionStage.thenCombine(bCompletionStage, (Pair::of)).thenCombine(
cCompletionStage.thenCombine(dCompletionStage, Pair::of),
(ab, cd) -> combine(ab.getLeft(), ab.getRight(), cd.getLeft(), cd.getRight()));
Still, it doesn’t scale well if you want to combine more stages.
An in-between solution (regarding complexity) might be:
CompletionStage<Combined> combinedDataCompletionStage = aCompletionStage.thenCompose(
a -> bCompletionStage.thenCompose(b -> cCompletionStage.thenCompose(
c -> dCompletionStage.thenApply(d -> combine(a, b, c, d)))));
That’s simpler in its structure but still doesn’t scale well with more more stages.
Holger's third answer can be made a little bit shorter:
CompletionStage<Combined> combinedDataCompletionStage = aCompletionStage.thenCompose(
a -> bCompletionStage.thenCompose(
b -> cCompletionStage.thenCombine(dCompletionStage,
(c, d) -> combine(a, b, c, d))));
You asked about "3 or more", if you have them in a List as CompletableFutures (see other answers) you could use this handy method:
private static <T> CompletableFuture<List<T>> join(List<CompletableFuture<T>> executionPromises) {
CompletableFuture<Void> joinedPromise = CompletableFuture.allOf(executionPromises.toArray(CompletableFuture[]::new));
return joinedPromise.thenApply(voit -> executionPromises.stream().map(CompletableFuture::join).collect(Collectors.toList()));
}
It converts your "list of futures" to a "future for a list of the results".
Any number of CompletableFuture can be combined (reduced)
CompletionStage<A> futA = getA();
CompletionStage<B> futB = getB();
CompletionStage<C> futC = getC();
Stream.of(futA, futB, futC)
.reduce((f1, f2) -> f1.thenCombine(f2, (d1, d2) -> combine(d1, d2));
The implementation of combine method will be responsible to merge data values (A, B and C), which could be tricky if A, B and C are disparate.
I think you should use an intermediary object, but one of your own instead of using Pair and Tuple
public R method() {
CompletableFuture<A> aFuture = getAFuture();
CompletableFuture<B> bFuture = getBFuture();
CompletableFuture<C> cFuture = getCFuture();
CompletableFuture<D> dFuture = getDFuture();
return CompletableFuture.completedFuture(new WellNamedResultHolder())
.thenCombineAsync(aFuture, WellNamedResultHolder::withAResult)
.thenCombineAsync(bFuture, WellNamedResultHolder::withBResult)
.thenCombineAsync(cFuture, WellNamedResultHolder::withCResult)
.thenCombineAsync(dFuture, WellNamedResultHolder::withDResult)
.thenApplyAsync(this::combineAllTheResults);
}
private static class WellNamedResultHolder {
private A aResult;
private B bResult;
private C cResult;
private D dResult;
// Getters
public WellNamedResultHolder withAResult(final A aResult) {
this.aResult = aResult;
return this;
}
public WellNamedResultHolder withBResult(final B bResult) {
this.bResult = bResult;
return this;
}
public WellNamedResultHolder withCResult(final C cResult) {
this.cResult = cResult;
return this;
}
public WellNamedResultHolder withDResult(final D dResult) {
this.dResult = dResult;
return this;
}
}
The actual form of the result holder can obviously change to match your own needs, giving you greater flexibility. You also get to be in charge of what happens as these futures complete. Although there is more boilerplate, you get code that is more descriptive of what is happening (which lombok can tidy up).
I had a similar problem but had more than 3 completablefutures so building up on Holger's answer I made a small generic utility.
public static <T, R> CompletableFuture<R> allOf(List<CompletableFuture<T>> args, Function<List<T>, R> combiner) {
final Queue<CompletableFuture<T>> queue = new LinkedList<>();
for (CompletableFuture<T> arg : args) {
queue.add(arg);
}
return aggregator(queue, new ArrayList<>(), combiner);
}
private static <T, R> CompletableFuture<R> aggregator(Queue<CompletableFuture<T>> queue, List<T> arg,
Function<List<T>, R> combiner) {
if (queue.size() == 2)
return queue.poll().thenCombine(queue.poll(), (c, d) -> {
arg.add(c);
arg.add(d);
return combiner.apply(arg);
});
return queue.poll().thenCompose(data -> {
arg.add(data);
return aggregator(queue, arg, combiner);
});
}
I think that CompleableFuture.allOf() function can help you.
For example: (View full Class here)
List<String> urls = [
"https://webhook.site/1647465b-c28f-4ffe-bbfe-5d3ad95ef994",
"https://webhook.site/1647465b-c28f-4ffe-bbfe-5d3ad95ef994?a=1"
]
CompletableFuture<Response>[] futures = new Completablefuture[2]
for (int i = 0; i < urls.size(); i++) {
futures[i] = asyncHttpClient.prepareGet(urls[i]).execute().toCompletableFuture()
}
CompletableFuture.allOf(futures).thenApply { future ->
return futures.collect { it.join() }
}.thenApply({ responses ->
//Do something with results
responses.each { println("Status code: " + it.statusCode) }
})
You can create a helper function...
combine3(
futureA, futureB, futureC,
(a, b, c) -> {
// let's go!
}).toCompletableFuture();
Definition:
private static <A, B, C, D> CompletionStage<D> combine3(
CompletionStage<A> aStage,
CompletionStage<B> bStage,
CompletionStage<C> cStage,
TriFunction<A, B, C, D> f
) {
return aStage.thenCompose(
a -> bStage.thenCombine(cStage,
(b, c) -> f.apply(a, b, c)));
}
interface TriFunction<A, B, C, D> {
D apply(A a, B b, C c);
}
I had the exact same problem (and likewise for Optional) so I wrote a library to generate thenCombine-methods up to arity 26: https://github.com/wernerdegroot/applicatives
Perhaps it will be of help.
If you want to combine results from multiple CFs you need an aggregator for that.
Take a look on the following example, on how to combine multiple CFs returning strings:
public static void main(String[] args) throws ExecutionException, InterruptedException {
var executor = CompletableFuture.delayedExecutor(10, TimeUnit.SECONDS);
var cf1 = CompletableFuture.supplyAsync(() -> "1", executor);
var cf2 = CompletableFuture.supplyAsync(() -> "2", executor);
var cf3 = CompletableFuture.supplyAsync(() -> "3", executor);
var cf4 = CompletableFuture.supplyAsync(() -> "4", executor);
var collectees = List.of(cf1, cf2, cf3, cf4);
System.out.println(Instant.now() + ": before combine");
var combinedFuture = combine(collectees);
System.out.println(Instant.now() + ": after combine");
// .get() is a blocking function
// if you remove .get() the program will print uncompleted future and exit (e.g. works async)
var result = combinedFuture.thenApply(list -> String.join(" - ", list)).get();
System.out.println(Instant.now() + ": " + result);
}
static <T> CompletableFuture<? extends List<T>> combine(List<CompletableFuture<T>> input) {
var collector = CompletableFuture.completedFuture(new ArrayList<T>(input.size()));
for (var toCollect : input) {
collector = collector.thenCombine(toCollect, (ts, t) -> {
ts.add(t);
return ts;
});
}
return collector;
}
In Java, is it possible to have a lambda accept multiple different types?
I.e:
Single variable works:
Function <Integer, Integer> adder = i -> i + 1;
System.out.println (adder.apply (10));
Varargs also work:
Function <Integer [], Integer> multiAdder = ints -> {
int sum = 0;
for (Integer i : ints) {
sum += i;
}
return sum;
};
//....
System.out.println ((multiAdder.apply (new Integer [] { 1, 2, 3, 4 })));
But I want something that can accept many different types of arguments, e.g:
Function <String, Integer, Double, Person, String> myLambda = a , b, c, d-> {
[DO STUFF]
return "done stuff"
};
The main use is to have small inline functions inside functions for convenience.
I've looked around google and inspected Java's Function Package, but could not find. Is this possible?
It's possible if you define such a functional interface with multiple type parameters. There is no such built in type. (There are a few limited types with multiple parameters.)
#FunctionalInterface
interface Function6<One, Two, Three, Four, Five, Six> {
public Six apply(One one, Two two, Three three, Four four, Five five);
}
public static void main(String[] args) throws Exception {
Function6<String, Integer, Double, Void, List<Float>, Character> func = (a, b, c, d, e) -> 'z';
}
I've called it Function6 here. The name is at your discretion, just try not to clash with existing names in the Java libraries.
There's also no way to define a variable number of type parameters, if that's what you were asking about.
Some languages, like Scala, define a number of built in such types, with 1, 2, 3, 4, 5, 6, etc. type parameters.
For something with 2 parameters, you could use BiFunction. If you need more, you can define your own function interface, like so:
#FunctionalInterface
public interface FourParameterFunction<T, U, V, W, R> {
public R apply(T t, U u, V v, W w);
}
If there is more than one parameter, you need to put parentheses around the argument list, like so:
FourParameterFunction<String, Integer, Double, Person, String> myLambda = (a, b, c, d) -> {
// do something
return "done something";
};
For this case you could use interfaces from default library (java 1.8):
java.util.function.BiConsumer
java.util.function.BiFunction
There is a small (not the best) example of default method in interface:
default BiFunction<File, String, String> getFolderFileReader() {
return (directory, fileName) -> {
try {
return FileUtils.readFile(directory, fileName);
} catch (IOException e) {
LOG.error("Unable to read file {} in {}.", fileName, directory.getAbsolutePath(), e);
}
return "";
};
}}
To make the use of lambda : There are three type of operation:
1. Accept parameter --> Consumer
2. Test parameter return boolean --> Predicate
3. Manipulate parameter and return value --> Function
Java Functional interface upto two parameter:
Single parameter interface
Consumer
Predicate
Function
Two parameter interface
BiConsumer
BiPredicate
BiFunction
For more than two, you have to create functional interface as follow(Consumer type):
#FunctionalInterface
public interface FiveParameterConsumer<T, U, V, W, X> {
public void accept(T t, U u, V v, W w, X x);
}
You could also use jOOL library - https://github.com/jOOQ/jOOL
It has already prepared function interfaces with different number of parameters. For instance, you could use org.jooq.lambda.function.Function3, etc from Function0 up to Function16.
Another alternative, not sure if this applies to your particular problem but to some it may be applicable is to use UnaryOperator in java.util.function library.
where it returns same type you specify, so you put all your variables in one class and is it as a parameter:
public class FunctionsLibraryUse {
public static void main(String[] args){
UnaryOperator<People> personsBirthday = (p) ->{
System.out.println("it's " + p.getName() + " birthday!");
p.setAge(p.getAge() + 1);
return p;
};
People mel = new People();
mel.setName("mel");
mel.setAge(27);
mel = personsBirthday.apply(mel);
System.out.println("he is now : " + mel.getAge());
}
}
class People{
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
So the class you have, in this case Person, can have numerous instance variables and won't have to change the parameter of your lambda expression.
For those interested, I've written notes on how to use java.util.function library: http://sysdotoutdotprint.com/index.php/2017/04/28/java-util-function-library/
Some lambda function :
import org.junit.Test;
import java.awt.event.ActionListener;
import java.util.function.Function;
public class TestLambda {
#Test
public void testLambda() {
System.out.println("test some lambda function");
////////////////////////////////////////////
//1-any input | any output:
//lambda define:
Runnable lambda1 = () -> System.out.println("no parameter");
//lambda execute:
lambda1.run();
////////////////////////////////////////////
//2-one input(as ActionEvent) | any output:
//lambda define:
ActionListener lambda2 = (p) -> System.out.println("One parameter as action");
//lambda execute:
lambda2.actionPerformed(null);
////////////////////////////////////////////
//3-one input | by output(as Integer):
//lambda define:
Function<String, Integer> lambda3 = (p1) -> {
System.out.println("one parameters: " + p1);
return 10;
};
//lambda execute:
lambda3.apply("test");
////////////////////////////////////////////
//4-two input | any output
//lambda define:
TwoParameterFunctionWithoutReturn<String, Integer> lambda4 = (p1, p2) -> {
System.out.println("two parameters: " + p1 + ", " + p2);
};
//lambda execute:
lambda4.apply("param1", 10);
////////////////////////////////////////////
//5-two input | by output(as Integer)
//lambda define:
TwoParameterFunctionByReturn<Integer, Integer> lambda5 = (p1, p2) -> {
System.out.println("two parameters: " + p1 + ", " + p2);
return p1 + p2;
};
//lambda execute:
lambda5.apply(10, 20);
////////////////////////////////////////////
//6-three input(Integer,Integer,String) | by output(as Integer)
//lambda define:
ThreeParameterFunctionByReturn<Integer, Integer, Integer> lambda6 = (p1, p2, p3) -> {
System.out.println("three parameters: " + p1 + ", " + p2 + ", " + p3);
return p1 + p2 + p3;
};
//lambda execute:
lambda6.apply(10, 20, 30);
}
#FunctionalInterface
public interface TwoParameterFunctionWithoutReturn<T, U> {
public void apply(T t, U u);
}
#FunctionalInterface
public interface TwoParameterFunctionByReturn<T, U> {
public T apply(T t, U u);
}
#FunctionalInterface
public interface ThreeParameterFunctionByReturn<M, N, O> {
public Integer apply(M m, N n, O o);
}
}
I think we can pass a map as an argument and pass the different values as elements of the map.
Function <Map <String, Object>, Integer> multiAdder = i -> {
String param1 = (String)i.get("PARAM1");
Integer param2 = (Integer)i.get("PARAM2");
Double param3 = (Double)i.get("PARAM3");
Integer x = callAnotherMethod(param1, param2, param3);
return x;
};
//....
Map<String, Object> paramsMap = new HashMap<>();
paramsMap.put("PARAM1", "String Val");
paramsMap.put("PARAM2", new Integer(12));
paramsMap.put("PARAM3", new Double(45);
System.out.println ((multiAdder.apply (paramsMap )));