简体   繁体   中英

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.

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM