15

I created a function to filter with multiple predicates for which I perform a logical AND for them:

@SafeVarargs
public static <T> Stream<T> filter(Stream<T> source, Predicate<T>... predicates) {
    return source.filter(Arrays.stream(predicates).reduce(predicates[0], Predicate::and));
}  

When calling:

filter(IntStream.range(0, 10).boxed(), x -> x % 2 != 0, x -> x%3 == 0).forEach(System.out::println);

It works fine and prints 3 and 9. However when I pass a single predicate such as:

filter(IntStream.range(0, 10).boxed(), x -> x % 2 != 0).forEach(System.out::println);

I get a compilation error:

The target type of this expression must be a functional interface

Why is this?

enter image description here For infos I use Eclipse Luna version 1.

user2336315
  • 15,697
  • 10
  • 46
  • 64
  • Can you post more code.... – Devavrata Jan 04 '15 at 23:36
  • 1
    @Devavrat More code of what? Everything is already posted... The method signature and body + the invocation that produces the compilation error. – user2336315 Jan 04 '15 at 23:37
  • 3
    Does compilation work on the command line? I was able to run both of your examples in IntelliJ 14.0.2 and got `3`, `9` and `1`, `3`, `5`, `7`, `9` respectively. Looks like it could be an Eclipse issue. – mkobit Jan 04 '15 at 23:46
  • 1
    @MikeKobit Damn you're right! It compiles fine with javac.... – user2336315 Jan 04 '15 at 23:48
  • 1
    This does really seem to be a problem in the Eclipse compiler. I suggest to file a bug with this exact example to https://bugs.eclipse.org/bugs/ . In the meantime a workaround for your problem is adding a type to your single lamda expression `(Integer x) -> x % 2 != 0` – Stefan Ferstl Jan 05 '15 at 01:02

3 Answers3

8

This is a corner case for the compiler. In order to determine whether it should apply varargs wrapping of arguments into an array or simply pass an array, it needs to know the type of the last argument, however, in the case of a lambda expression it needs the invoked method signature to determine the type. But it’s clear what should happen as a lambda expression can never be an array type and so, javac compiles it without problems.

One acceptable work-around would be to overload the method:

@SafeVarargs
public static <T> Stream<T> filter(Stream<T> source, Predicate<T>... predicates) {
    return source.filter(
        Arrays.stream(predicates).reduce(predicates[0], Predicate::and));
}
public static <T> Stream<T> filter(Stream<T> source, Predicate<T> predicate) {
    return source.filter(predicate);
}

This would be an an acceptable work-around as it does not require any changes on the calling side while improving the efficiency for the single-arg case at the same time.


Please note that your varargs method allows zero arguments but will fail if called that way. So you should either, add another overload:

public static <T> Stream<T> filter(Stream<T> source) {
    return source;
}

or make the method safe for the zero argument case:

@SafeVarargs
public static <T> Stream<T> filter(Stream<T> source, Predicate<T>... predicates) {
    return Arrays.stream(predicates).reduce(Predicate::and)
                 .map(source::filter).orElse(source);
}
Holger
  • 285,553
  • 42
  • 434
  • 765
2

Both Netbeans and Eclipse have a number of bugs in the area of parsing lambda expressions in Java. They are slowly getting fixed but, until they are, the best workarounds I have found are: 1. declare types 2. if that doesn't work, use a block 3. if that doesn't work, create an anonymous object that implements the predicate/function etc.

These make your code messier but they are necessary in a number of situations.

sprinter
  • 27,148
  • 6
  • 47
  • 78
0

Sometimes Eclipse needs a good ol' clean and rebuild of all projects and the problem goes away.

Peteter
  • 691
  • 9
  • 25