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.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.