Java 8: Do not understand the way Java implements Functional Interfaces - java

lets say we a Predicate and a Function-Interface:
Function<String, String> function = null;
Predicate<String> predicate = null;
Now I want to give the Predicate-Interface a method reference where the return type is a boolean and in our case the parameter a string. But why the following method reference seems to be right:
Predicate<String> predicate = String::isEmpty;
The isEmpty-method has no String-Parameter,although the Predicate-Interface requires a String-Parameter. Why it is still right? Am I missing something?
Another Example: The Function interface returns in our case a String and takes a String as parameter. But the following method reference seems to be wrong:
Function<String, String> function = String::concat; //wrong
The Concat-Method has a String as Parameter and returns a String. Why its wrong?
Hopefully somebody can explain it to me.

When you use a method reference on an instance method, the method receiver becomes the first argument. So
String::isEmpty
is equivalent to
(String str) -> str.isEmpty()
and
String::concat
is equivalent to
(String a, String b) -> a.concat(b)
...which does not match the type of Function.

The reason why
Function<String, String> function = String::concat;
does not compile is because it is equivalent to (as Louis written)
Function<String, String> function = (String a, String b) -> a.concat(b);
while Function.apply(T t) takes only one argument (t) and you are passing a function that takes two arguments (a and b).

String::isEmpty can, in theory, mean one of two things.
A static method in the String class:
s -> String.isEmpty(s)
An instance method in the String class:
(String s) -> s.isEmpty()
Your case falls into #2.
Similarly, String::concat can mean one of two things:
(s1, s2) -> String.concat(s1, s2)
or
(String s1, String s2) -> s1.concat(s2) // Your case
(However, this is not a Function<String, String>, as it does not take precisely one argument. It is, however, a BinaryOperator<String>.)

Related

Combine two lambda expressions without intermediate step

I'm trying to write a lambda expression that sorts a string array by string length by using a single lambda expression variable. This is what I have come up with (using two)
public static void main(String[] args) {
Consumer<String[]> sort = s -> Arrays.sort(s, (String s1, String s2) -> s1.length() - s2.length());
Consumer<String[]> print = sort.andThen(s -> System.out.println(Arrays.toString(s)));
print.accept(strings);
}
Is there a way to combine sort and print so that they are just one expression? I have tried this:
Consumer<String[]> print = (s -> Arrays.sort(s, (String s1, String s2) -> s1.length() - s2.length())).andThen(s -> System.out.println(Arrays.toString(s)));
But I'm just getting
String s1, String s2) -> s1.length() - s2.length()
marked red saying "Cannot infer functional interface type". Is what I'm trying even possible? I know it's a pretty useless this worrying about but now I'm curious if this is possible.
Lambdas in Java always need to target a functional interface (an interface with only a single abstract method). In your case, that is the Consumer-interface.
The reason you get the error when you chain the two lambdas together with andThen is because the compiler doesn't know which functional interface the first lambda is targetting. So it doesn't know which andThen function to call. It only knows that the whole expression should result in a Consumer.
To fix it, you would need to cast the first lambda to Consumer<String[]>. Like this:
Consumer<String[]> print = ((Consumer<String[]>) s -> Arrays.sort(s, (String s1, String s2) -> s1.length() - s2.length()))
.andThen(s -> System.out.println(Arrays.toString(s)));
This can be done using :
Arrays.stream(s).sorted(Comparator.comparingInt(String::length))
.forEach(System.out::println);

How to, from a list, filter it and convert to Map<String,String>

I am trying to, from a list of Strings, filter it, and load the results into a Map<String,String> of with the strings that passed through the test and a generic reason.
This is why I am trying:
Map<String,String> invalidStrings = null;
invalidStrings = src.getAllSubjects()
.stream()
.filter(name -> !(name.contains("-value") || name.contains("-key")))
.collect(Collectors.toMap(Function.identity(), "Some Reason"));
This is what I am getting:
The method toMap(Function, Function) in the type Collectors is not applicable for the arguments
(Function, String)
No idea why I am not being able to do this... Everywhere I search the suggestions are basically the same as I am doing.
This is the important part of the error:
The method toMap(Function, Function) <--- note the Function, Function
This means, toMap expects the first argument to be a function which you've done correctly via Function.identity() i.e. v -> v but the second value you've passed is a String
Rather the value mapper must be a function:
.collect(Collectors.toMap(Function.identity(), v -> "Some Reason"));
note the v -> "Some Reason";

Why can't Java typecheck this code?

I have some stream handling code that takes a stream of words and performs some operations on them, then reduces them to a Map containing the words as keys and the number of occurrences of the word as a Long value. For the sake of the brevity of the code, I used the jOOL library's Seq class, which contains a number of useful shortcut methods.
The code compiles just fine if I write it like this:
item.setWordIndex (
getWords (item) // returns a Seq<String>
.map (this::removePunctuation) // String -> String
.map (stemmer::stem) // String -> String
.groupBy(str -> str, Collectors.counting ()));
However, if I attempt to replace the str -> str lambda with the more self-documenting Function::identity, I get the following errors:
The method setWordIndex(Map<String,Long>) in the type MyClass is not applicable for the arguments (Map<Object,Long>)
The type Function does not define identity(String) that is applicable here
Why does Function::identity behave any differently to str -> str, which I (perhaps naively) assumed was directly equivalent, and why can't the compiler handle it when it is used?
(And yes, I'm aware I could remove the identity function by moving the previous map application into the groupBy operation, but I find the code clearer like this, because it follows the application logic more directly)
You want Function.identity() (which returns a Function<T, T>), not Function::identity (which matches the SAM type Supplier<Function<T, T>>).
The following code compiles fine:
static String removePunctuation(String x) { return x; }
static String stem(String x) { return x; }
// ...
final Map<String, Long> yeah = Seq.of("a", "b", "c")
.map(Test::removePunctuation)
.map(Test::stem)
.groupBy(Function.identity(), Collectors.counting());
There is a slight difference between the two types; they are not directly equivalent:
Function.identity() has to return the input type, because its type is Function<T, T>;
str -> str can return a wider type; effectively it is Function<? extends T, T>.

How to do additional processing in Java Stream API near the "::" method reference operator

Using the Java Stream API, is there a way to do additional processing to adjust the value of whatever is passed to a method reference?
I'll give two examples.
Example 1.
In the first example, I start with a Stream<Path>, and I want to return a Map<String, Path> in which the keys in the map are processed version of the filename using another function that takes a String filename (not a Path). Specifically:
public Map<String, Path> createMap(Path sourceFolder, PathMatcher filter) {
return stream.filter(filter::matches)
.collect(Collectors.toMap(FilenameHelper::parseFilename, Function.identity()));
parseFilename(String filename) takes a String filename, but of course the method reference gets a Path. I'd like to say something like, FilenameHelper::parseFilename(((Path)Function.identity()).toFile().getName()) but that doesn't work (Eclipse says: "The left-hand side of an assignment must be a variable"). I can work around it by creating a new method that takes a Path and just does return parseFilename(path.toFile().toName()) but that's not cool.
Example 2.
In the second example, I have rows, a List<List<String>>> that represents a data table (rows, then columns). I have a method that should return a List<String> consisting of a specific column in that table for every nth row. I want to do something like:
public List<String> getDataFromColumn(String columnName, int nth) {
/// Without a clause at ???, this returns a List<List<String>>
return IntStream.range(0, rows.size())
.filter(n -> n % nth == 0) // Get every nth row
.mapToObj(rows::get)
.???
.collect(Collectors.toList());
}
Where "???" should be something like map(ArrayList::get(headers.indexOf(columnName))) (where headers is a List<String> containing the column headers) but if I put that in, I get an AssignmentOperator syntax error in the get part of this clause. Replacing map with forEach doesn't help here. In other words, I don't want rows.get(n), I want rows.get(n).get(headers.indexOf(columnName).
Question
In both of these examples, I want to do something additional to the value that is being passed to the method pointed to with the method reference operator (::). Is there a "Java Stream-ic" way to do additional processing to the thing being passed to the method reference?
Method references are essentially a convenient substitute for lambdas where the function signature is an exact match to the method signature. In your case you can just use regular lambdas:
public Map<String, Path> createMap(Path sourceFolder, PathMatcher filter) {
return stream.filter(filter::matches)
.collect(Collectors.toMap(path -> FilenameHelper.parseFilename(path.toFile().getName()), Function.identity()));
}
public List<String> getDataFromColumn(String columnName, int nth) {
return IntStream.range(0, rows.size())
.filter(n -> n % nth == 0)
.mapToObj(rows::get)
.map(row -> row.get(headers.indexOf(columnName)))
.collect(Collectors.toList());
}
How about Function.compose? Of course you cannot use FilenameHelper::parseFilename.compose, but you can easily write a static helper method to work around it:
static <T, V, R> Function<T, R> compose(Function<T, V> f, Function<V, R> g) {
return g.compose(f);
}
Now we can compose method references:
return stream.filter(filter::matches)
.collect(Collectors.toMap(
compose(
compose(Path::getFileName, Path::toString),
FilenameHelper::parseFilename),
Function.identity()));
This is actually not very readable but an alternative to writing a full lambda.
No, this functionality is currently not provided.
The usual way would be to just not use a method reference and instead call the method the "usual" way using a lambda expression:
stream.filter(filter::matches)
.collect(Collectors.toMap(p -> FilenameHelper.parseFilename(p.getFileName()), Function.identity()));
No, there is not. There is no syntax to do that.
And if you wanted such a thing then lambda expression is what you want.
Method reference or lambda, under the hood you are still going to get a class that actually implements the Predicate/Function so it does not matter.
And that argument but that's not cool, to me under the conditions that there is no syntax for that, it's the best option you have.
Underneath the actual calls that you there is a MethodHandle (introduced in jdk-7) and MethodHandles do not have a way to achieve what you want. I think the same restriction exists in C++ with method pointers.

Lambda Expressions functional programming

I have to programm regular expression with lambda expressions for the university. I got stuck by 2 methods in a method.
here is my code:
static String ausdruck = "abcd";
public static Function<String, String> Char = (c) -> {
return (ausdruck.startsWith(c)) ? ausdruck = ausdruck.substring(1,
ausdruck.length()) : "Value Error";
};
public static BiFunction<Function<String, String>,
Function<String, String>,
Function<String, String>>
And = (f1, f2) -> {return null;};
what I want to do in the And method is: Char(Char.apply("a")) -> I want to call the function f2 with the f1 as a parameter.
the Call of the And Method have to look like:
And.apply(Char.apply("a"), Char.apply("b"));
I guess this is what you want
Func<Str,Str> f = and( comsume("a"), consume("b") );
f.apply("abcd"); // "cd"
Func<Str,Str> consume(String x)
return input->{
if(input.startsWith(x)) return input.substring(x.length());
else throws new IllegalArgument()
};
Func<Str,Str> and(Fun<Str,Str> f1, Func<Str,Str> f2)
return input-> f2.apply(f1.apply(input))
and is not necessary though, see Function.andThen method
f = consume("a").andThen( consume("b) )
Unfortunately, there is no "curry"; otherwise, we could do this
f = consume2.curry("a") .andThen ( consume2.curry("b") );
static BiFunc<Str,Str,Str> consume2 = (input,x)-> {...return input.substring(x.length()); ..
It's better off if you design your own functional interfaces, with needed methods like curry.
interface F1
String apply(String);
F1 and(F1);
interface F2
String apply(String,String);
F1 curry(String);
If I understand the question correctly, you want to create a function that compones a new function, executing one function with the result of another function. The best way to do this in a lambda would be to return a new lambda.
Try something like this:
BiFunction<Function<String, String>, Function<String, String>, Function<String, String>> compose =
(f1, f2) -> (a -> f2.apply(f1.apply(a)));
Example:
Function<String, String> upper = s -> s.toUpperCase();
Function<String, String> twice = s -> s + s;
Function<String, String> upperTwice = compose.apply(upper, twice);
System.out.println(upperTwice.apply("foo"));
Output is FOOFOO.
Concerning your concrete example
the Call of the And Method have to look like:
And.apply(Char.apply("a"), Char.apply("b");
I do not know exactly what you are trying to do, but I don't think this will work, given your current implementation of Char. It seems like you want to compose a lambda to remove a with another to remove b, but instead Char.apply("a") will not create another function, but actually remove "a" from your ausdruck String! Instead, your Char lambda should probably also return another lambda, and that lambda should not modify some static variable, but take and return another String parameter.
Function<String, Function<String, String>> stripChar =
c -> (s -> s.startsWith(c) ? s.substring(1) : "ERROR");
Function<String, String> stripAandC = compose.apply(stripChar.apply("c"), stripChar.apply("a"));
System.out.println(stripAandC.apply("cash"));
Output is sh.
Finally, in case you want to use this with anything other than just String, it might make sense to make compose an actual method instead of a lambda, so you can make use of generics. Also, you can make this a bit simpler by using andThen:
public static <A, B, C> Function<A, C> compose(Function<A, B> f1, Function<B,C> f2){
return f1.andThen(f2);
}

Categories

Resources