I found that function interface and getMethod seem to be replaceable, What makes it work?
public class App {
public static void main(String[] args) {
Map<String, String> collect = Stream.of(new App(), new App(), new App())
.collect(Collectors.toMap(App::getString, (app) -> "aaa"));
}
public String getString() {
return "str";
}
}
But when I use lambda to replace getMethod, it fails . Why this does not work
Map<String, String> collect = Stream.of(new App(), new App(), new App())
.collect(Collectors.toMap(() -> "str", (app) -> "aaa"));
Collectors.toMap requires a Function<? super T, ? extends K> as its first parameter, where T is the type of elements in the stream, and K is the key type of the map you want.
In this case, you have a stream of Apps and you want a Map<String, String>, so T is App and K is String. In other words, you need a function that accepts an App, and returns a String.
App::getString is such a Function<? super T, ? extends K>. You might be wondering why it accepts a App when getString accepts no parameters. Notice how getString is an instance method, and you are referring to it without an instance! A method reference of the form ClassName::instanceMethodName implicitly accepts an extra parameter of type ClassName, because you need an instance of that class to call it!
On the other hand, your lambda is not such a function. It accepts no parameters, as indicated by the empty brackets at the start (()). Your lambda expression would be represented by the Supplier<String> functional interface, not the Function<App, String> that you need.
To use a lambda expression here, simply do what you did to the second parameter of toMap, and add a lambda parameter:
.collect(Collectors.toMap((app) -> "str", (app) -> "aaa"));
// ^^^
Note that this is required even if you don't use app in the lambda expression.
Related
I have a Function and BiFunction and I would like to chain them
Function<Integer, String> function = n -> "" + n;
BiFunction<String, Boolean, List<Character>> biFunction =
(str, isOK) -> Collections.EMPTY_LIST;
Is there a way to chain these two Functions such as the returned value from Function is used as an input to BiFunction?
Pseudocode:
public List<Character> myMethod(int n, boolean isOK) {
return function.andThen(biFunction).apply([output_of_function], isOK)
}
I couldn't find a way to provide the integer n to Function nor to supply BiFunction with the output of the first Function.
Is it doable?
Default methods andThen() and compose() declared in the interface Function expect another Function as an argument. Hence, it's not possible to fuse Function and BiFunction using these methods (BiFunction and Function doesn't extend each other).
On the other hand method BiFunction.andThen() expects a Function as argument. But unfortunately it would be applied after BiFunction (i.e. on the result produced by the BiFunction), but you need the opposite, so this option doesn't fit into your use-case.
As a possible workaround, you can combine a Function and a BiFunction into an aggregate BiFunction expecting the input of the Function function and a boolean value and producing the result generated by the by BiFunction like this:
public static <T, R, RR> BiFunction<T, Boolean, RR> getCombinedFunction(
Function<T, R> fun, BiFunction<R, Boolean, RR> biFun
) {
return (t, isOk) -> biFun.apply(fun.apply(t), isOk);
}
It can be used in the following way:
Function<Integer, String> function = // initializing function
BiFunction<String, Boolean, List<Character>> biFunction = // initializing biFunction
List<Character> chars = getCombinedFunction(function, biFunction).apply(12345, true);
Sidenote:
The preferred way of converting an int into a String is to use static method String.valueOf(). And the function from your example could be expressed as the following method reference:
Function<Integer, String> function = String::valueOf;
You can define a generic method that compose Function and BiFunction like this.
public static <A, B, C, D> BiFunction<A, C, D> compose(Function<A, B> f, BiFunction<B, C, D> bf) {
return (a, c) -> bf.apply(f.apply(a), c);
}
And you can use like this.
Function<Integer, String> function = n -> ""+n;
BiFunction<String, Boolean, List<Character>> biFunction = (str, isOK) -> Collections.emptyList();
public List<Character> myMethod(int n, boolean isOK) {
return compose(function, biFunction).apply(n, isOK);
}
Node: You should use Collections.emptyList() instead of Collections.EMPTY_LIST.
The latter gives a warning.
What is a proper usage of the Comparator's methodthenComparing() and why it's not working properly in my code? I don't quite understand the reason of the error I'm getting:
no instance(s) of type variable(s) U exist so
that Object conforms to Comparable<? super U>
That is the code that produces the error:
Map<Integer, Long> sortedCards = new LinkedHashMap<>();
List<Card> cards = // initializing the list
cards.stream().collect(
Collectors.groupingBy(
card -> card.kind.rank,
Collectors.counting()
))
.entrySet().stream()
.sorted(Map.Entry.comparingByValue(Comparator.reverseOrder())
.thenComparing(Map.Entry::getKey))
.forEachOrdered(e -> sortedCards.put(e.getKey(), e.getValue()));
My Card class:
public static class Card implements Comparable<Card> {
private Kind kind;
// constructors, getters, etc.
}
Kind enum:
public enum Kind {
TWO(1, "2"), THREE(2, "3"), FOUR(3, "4"), // etc.;
public int rank;
// constructors, getters, etc.
}
The reason of the compilation error you've encountered is a type inference issue.
The when you're chaining methods while constructing a comparator, the compiler fails to infer the type of the method reference from the target type.
You can resolve it by using type-witness, i.e. providing the types of key and value explosively in the angle brackets <K,V> (these are the types declared by comparingByValue() which produces the first comparator in the chain):
Map.Entry.<Integer, Long>comparingByValue(Comparator.reverseOrder())
.thenComparing(Map.Entry::getKey)
Also, instead of forEachOrdered() it would be better to use collect() and generate a Map as a result of the stream execution then populate pre-created map via side-effects. It makes your code more verbose and less expressive because you need to instantiate the resulting collection separately, and goes against the guidelines listed in the documentation.
Methods forEach() and forEachOrdered() exist as a last resort, and it's discouraged to use them as a substitution of reduction operations like collect() or reduce(), for more information refer to the API documentation, pay attention to the code examples.
That's how it would look like if we make use of collect():
Map<Integer, Long> sortedCards = cards.stream()
.collect(Collectors.groupingBy(card -> card.kind.rank, Collectors.counting()))
.entrySet().stream()
.sorted(Map.Entry.<Integer, Long>comparingByValue(Comparator.reverseOrder())
.thenComparing(Map.Entry::getKey))
.collect(Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue,
(left, right) -> {
throw new AssertionError("duplicate keys are not expected");
},
LinkedHashMap::new
));
I can't understand why the String::toUpperCase() expression works fine inside the Stream map pipeline. When I look at this example here:
Stream.of("test1", "test2", "test3", "test4")
.filter(s -> s.contains("r"))
.map(s -> s + "map")
.map(String::toUpperCase)
.forEach(System.out::println);
When I look to the definition of the map operator used in the example below map(Function<? super String, ? extends String> mapper) I saw a function design pattern is been used.
In this example .map(s -> s + "map") is fine, as I understand we are looking for Function more precisely the R apply(T t);, it is totally what the lambda expression said s -> s + "map" here we have a function with a parameter s and it returns s + String "map" and it conforms to this spec. T and R, they are present.
On the other side the second one map(String::toUpperCase), I can't understand why the expression toUpperCase is considered as a Function interface, I should note the core of this function is like this
public String toUpperCase() {
return toUpperCase(Locale.getDefault());
}
and we are looking for R apply(T t); there is no T parameter in this method toUpperCase? Why does this one work?
What's much easier to understand in terms of the apply method of the Function interface is the anonymous class representation of the method reference String::toUpperCase. It goes like this -
new Function<String, String>() {
#Override
public String apply(String str) { // read as given a String return a String (uppercased)
return str.toUpperCase();
}
}
The string arguments(str) provided to the above apply method are the ones from the Stream after the previous map operation.
It's called a method reference and it's syntactic sugar for a lambda expression. In other words the following:
String::toUpperCase
is equivalent to:
s -> s.toUpperCase()
It's a method that takes a String s and returns a String with all letter from s uppercase, it's a Function<String, String>.
The definition of the BiFunction interface contains a method apply(T t, U u), which accepts two arguments. However, I don't understand the use or purpose of this interface and method. What do we need this interface for?
The problem with this question is that it's not clear whether you see the purpose of a Function, which has a method apply(T t).
The value of all the functional types is that you can pass code around like data. One common use of this is the callback, and until Java 8, we used to have to do this with anonymous class declarations:
ui.onClick(new ClickHandler() {
public void handleAction(Action action) {
// do something in response to a click, using `action`.
}
}
Now with lambdas we can do that much more tersely:
ui.onClick( action -> { /* do something with action */ });
We can also assign them to variables:
Consumer clickHandler = action -> { /* do something with action */ };
ui.onClick(clickHandler);
... and do the usual things we do with objects, like put them in collections:
Map<String,Consumer> handlers = new HashMap<>();
handlers.put("click", handleAction);
A BiFunction is just this with two input parameters. Let's use what we've seen so far to do something useful with BiFunctions:
Map<String,BiFunction<Integer,Integer,Integer>> operators = new HashMap<>();
operators.put("+", (a,b) -> a + b);
operators.put("-", (a,b) -> a - b);
operators.put("*", (a,b) -> a * b);
...
// get a, b, op from ui
ui.output(operators.get(operator).apply(a,b));
One of usages of BiFunction is in the Map.merge method.
Here is an example usage of the Map.merge method, which uses a BiFunction as a parameter. What merge does is basically replaces the value of the given key with the given value if the value is null or the key does not have a value. Otherwise, replace the value of the given key after applying the BiFunction.
HashMap<String, String> map = new HashMap<>();
map.put("1", null);
map.put("2", "Hello");
map.merge("1", "Hi", String::concat);
map.merge("2", "Hi", String::concat);
System.out.println(map.get("1")); // Hi
System.out.println(map.get("2")); // HelloHi
If a BiFunction were not used, you would have to write a lot more code, even spanning several lines.
Here is a link that shows all the usages of BiFunction in the JDK: https://docs.oracle.com/javase/8/docs/api/java/util/function/class-use/BiFunction.html
Go check it out!
An extra example of BiFunction is reduce():
public static void main(String[] args) {
List<Integer> list = new ArrayList<>(Arrays.asList(5,5,10));
Integer reduce = list.stream().reduce(0, (v1,v2) -> v1+v2);
System.out.println(reduce); // result is: 20
}
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.
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());
}
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 ;)