Call a method on the return value of a method reference

大兔子大兔子 提交于 2019-12-06 03:02:50

问题


I have a stream of files that I want to filter based on the ending of the file name:

public Stream<File> getFiles(String ending) throws IOException {
    return Files.walk(this.path)
            .filter(Files::isRegularFile)
            .map(Path::toFile)
            .filter(file -> file.getName().endsWith(ending));
}

While the lambda in the last line is not bad, I thought I could use method references there as well, like so:

 .filter(File::getName.endsWith(ending));

Or alternatively wrapped in parentheses. However, this fails with The target type of this expression must be a functional interface

Can you explain why this doesn't work?


回答1:


Can you explain why this doesn't work?

Method references are syntactical sugar for a lambda expression. For example, the method reference File::getName is the same as (File f) -> f.getName().

Lambda expressions are "method literals" for defining the implementation of a functional interface, such as Function, Predicate, Supplier, etc.

For the compiler to know what interface you are implementing, the lambda or method reference must have a target type:

// either assigned to a variable with =
Function<File, String> f = File::getName;
// or assigned to a method parameter by passing as an argument
// (the parameter to 'map' is a Function)
...stream().map(File::getName)...

or (unusually) cast to something:

((Function<File, String>) File::getName)

Assignment context, method invocation context, and cast context can all provide target types for lambdas or method references. (In all 3 of the above cases, the target type is Function<File, String>.)

What the compiler is telling you is that your method reference does not have a target type, so it doesn't know what to do with it.




回答2:


File::getName is a method reference and String::endsWith is as well. However they cannot be chained together. You could create another method to do this

public static Predicate<File> fileEndsWith(final String ending) {
    return file -> file.getName().endsWith(ending);
}

and then use it

.filter(MyClass.fileEndsWith(ending))

This doesn't buy you much if you're not re-using it though.




回答3:


A couple of helpers might assist in providing some semblance of what you wish for. Using the helpers below, you can replace your lambda with an expression containing method references, like this:

// since your predicate is on the result of a function call, use this to create a predicate on the result of a function
public static <A,B> Predicate<A> onResult(Function<A,B> extractor, Predicate<B> predicate){
    return a -> predicate.test(extractor.apply(a));
}

// since your predicate involves an added parameter, use this to reduce the BiPredicate to a Predicate with one less parameter
public static <T,U> Predicate<T> withParam(BiPredicate<T,U> pred, U param){
    return t -> pred.test(t,param);
}

public Stream<File> getFiles(String ending) throws IOException {
    return Files.walk(Paths.get("."))
            .filter(Files::isRegularFile)
            .map(Path::toFile)
            .filter(onResult(File::getName, withParam(String::endsWith, ending)));
}


来源:https://stackoverflow.com/questions/36798635/call-a-method-on-the-return-value-of-a-method-reference

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!