19

I often find myself in the need to filter a Stream or to use a predicate that checks if a given field has a given value.

Say for example I have this POJO:

public class A {
    private Integer field;

    public A(final Integer field) {
        this.field = field;
    }

    public Integer getField() {
        return field;
    }
}

And I want to filter a Stream of objects based on the value of the field:

    final Integer someValue = 42;
    Stream.of(new A(42), new A(1729), new A(42), new A(87539319), new A(null))
            .filter(a -> Objects.equals(a.getField(), someValue))
            ...

Would there be a convenience method to generate the predicate for the filter method? I noticed there is Predicate.isEqual but it does not fit the need.

I could quite easily write one like this:

public static <T,R> Predicate<T> isEqual(Function<? super T, ? extends R> f, R value) {
    return v -> Objects.equals(value, f.apply(v));
}

and use it as:

    Stream.of(new A(42), new A(1729), new A(42), new A(87539319), new A(null))
            .filter(isEqual(A::getField, someValue))
            ...

but I'd prefer to reuse an existing method from the JDK if possible.

Didier L
  • 18,905
  • 10
  • 61
  • 103
  • 5
    Not that I know of. But note that if you know that `someValue` is not `null`, a simple `a -> someValue.equals(a.getField())` is sufficient. It doesn’t look worse than `isEqual(A::getField, someValue)` to me, especially if I consider that `A` is rather a `NameOfARealLifeClass`… – Holger Oct 23 '15 at 11:26

1 Answers1

15

There is no such builtin factory method, which you can easily check by looking at all usages of Predicate within the JFC and look for “Methods in … that return Predicate”. Besides the methods within Predicate itself, there is only Pattern.asPredicate() which returns a Predicate.

Before you’re going to implement such a factory method, you should ask yourself whether it’s really worth it. What makes your lambda expression in .filter(a -> Objects.equals(a.getField(), someValue)) look complicated, is the use of Objects.equals which is not necessary when you can predict for at least one argument whether it is null. Since here, someValue is never null, you can simplify the expression:

final Integer someValue = 42;
Stream.of(new A(42), new A(1729), new A(42), new A(87539319), new A(null))
    .filter(a -> someValue.equals(a.getField()))
    …

If you still want to implement the factory method and win a price for creatively using what is already there, you can use:

public static <T,R> Predicate<T> isEqual(Function<? super T, ? extends R> f, R value) {
    return f.andThen(Predicate.isEqual(value)::test)::apply;
}

However, for production code, I’d rather recommend an implementation like this:

public static <T,R> Predicate<T> isEqual(Function<? super T, ? extends R> f, R value) {
    return value==null? t -> f.apply(t)==null: t -> value.equals(f.apply(t));
}

This factors out the test whether the constant is null to simplify the operation that will be performed in each test. So it still doesn’t need Objects.equals. Note that Predicate.isEqual does similar.

Holger
  • 285,553
  • 42
  • 434
  • 765
  • can you please explain why the `value==null?...`-solution is preferable over `Objects.equals`? – Roland Apr 28 '17 at 11:26
  • 2
    @Roland: when you call `Objects.equals` in every function evaluation, you will effectively recheck whether `value` is `null` in every function evaluation despite it won’t change. Obviously, the effect will be biggest *if* `value` is `null`, as the resulting function `t -> f.apply(t)==null` will be much cheaper than `Objects.equals`. As you might be using such a utility method a lot of time at different places, the effects accumulate. As said, [`Predicate.isEqual` does similar](http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/8u40-b25/java/util/function/Predicate.java#114) – Holger Apr 28 '17 at 11:39
  • Thanks for the clarification! Oh dear... that was even the reason why I extracted the value in the first place... it's time for the weekend. – Roland Apr 28 '17 at 11:46
  • @Holger good idea of using `Predicate.isEqual` rather than `Object.equals`. – holi-java Apr 30 '17 at 09:32
  • @Holger is there a way to enforce the `R` data type? Because these 2 examples are compilable and yet do not make sense `.filter(isEqual(a::getField, "some string")` and `.filter(isEqual(a::getField, 12345)`. I wanted to use similar utility function but due to this pitfall it is dangerous. Especially for enums. Solution might be having another `isEqualEnum` with constraint `R extends Enum...` but this is really going to go sideway.. – Petr Újezdský Feb 22 '22 at 11:50
  • 2
    @PetrÚjezdský there is no way, as it is always possible for the compiler to infer `Object` for `R`. Which combination of classes may lead to a successful `equals` test, depends on the actual implementation of `equals`, two classes may be compatible without having a common base class (other than `Object`). When you change `? extends R` to `R`, you can restrict the arguments when passing existing `Function` implementations. But for the intended use case of passing lambda expressions or method references, this wouldn’t help. Simply said, it accepts arbitrary arguments the same way, `equals` does. – Holger Feb 22 '22 at 12:01
  • 3
    @PetrÚjezdský that’s what I meant with “when passing existing `Function` implementations”. But when passing method references directly, the compiler is free to infer `Function`. You could also use `(Function)A::getName` as method argument. Still, cumbersome. – Holger Feb 22 '22 at 13:35
  • A small remark. `Pattern.asPredicate()` isn't the only method outside the `Predicate` interface which allows to obtain a predicate. Java 11 has brought one more predicate-producing method - `Pattern.asMatchPredicate()`. – Alexander Ivanchenko Nov 22 '22 at 16:17
  • 1
    @AlexanderIvanchenko well, this answer is seven years old and clearly links to the Java 8 API. Surely, the number of methods is expected grow, but as long as no method matching the question’s issue has been added, it’s not that important. – Holger Nov 23 '22 at 09:40