5

I wrote this generic predicate:

private static <T> Predicate<T> isNull(){
    return Objects::isNull;
}

But I can't use it in combination with other predicates like this:

private static Predicate<String> isEmpty(){
    return string -> string.isEmpty();
}

Because this snippet will not compile (expects Predicate< String> in or operation):

isNull().or(isEmpty())

Any idea to solve it? Thanks!

italktothewind
  • 1,950
  • 2
  • 28
  • 55

4 Answers4

6

Since isNull() is generic, and the compiler cannot infer the generic parameter when combined like that, you need to explicitly specify the type parameter.

To do that, you must qualify with the class name, e.g. Test:

Test.<String>isNull().or(isEmpty())

Full example:

public class Test {
    public static void main(String[] args) {
        Predicate<String> isNullOrEmpty = Test.<String>isNull().or(isEmpty());
        System.out.println(isNullOrEmpty.test(null));   // true
        System.out.println(isNullOrEmpty.test(""));     // true
        System.out.println(isNullOrEmpty.test("Foo"));  // false
    }
    private static <T> Predicate<T> isNull(){
        return Objects::isNull;
    }
    private static Predicate<String> isEmpty(){
        return string -> string.isEmpty();
    }
}

You can also resolved it by assigning each part to a variable:

Predicate<String> isNull = isNull(); // String is inferred from assignment operator
Predicate<String> isEmpty = isEmpty();
Predicate<String> isNullOrEmpty = isNull.or(isEmpty);

Or just the first part:

Predicate<String> isNull = isNull();
Predicate<String> isNullOrEmpty = isNull.or(isEmpty());
Andreas
  • 154,647
  • 11
  • 152
  • 247
2

Well in our project we have a small work-around for this:

private static <T> Predicate<T> allOr(Predicate<T> ... predicates) {
    return Arrays.stream(predicates).reduce(Predicate::or).orElse(x -> true);
}

And using it with:

Predicate<String> both = allOr(isEmpty(), isNull());
Eugene
  • 117,005
  • 15
  • 201
  • 306
  • Nice. It will automatically choose a `T` common to all the predicates. --- Though, I would add `if (predicates.length == 0) throw new IllegalArgumentException();`, rather than defaulting to a `true` value, especially since I would have expected the default to be `false`, i.e. simulating a dummy `() -> false` in the list of predicates. – Andreas Aug 22 '18 at 19:09
  • @Andreas oh the debate we had for this in our internal reviewing also... :) I agree with you, but apparently not all on my team do – Eugene Aug 22 '18 at 19:17
  • 2
    @Andreas well, `() -> false` is the [identity element](https://stackoverflow.com/a/32867283/2711488) for `Predicate::or` like `() -> true` is for `Predicate::and`, so it is indeed preferable as default for an empty predicate list too. Alternatively, you may enforce at least one argument using `allOr(Predicate first, Predicate... more)` – Holger Aug 24 '18 at 08:35
  • 1
    @Holger that is indeed correct, I will open an internal issue for this for our code :( and why in the world did I not specify `Predicate first, Predicate ... allOthers`... thank you!! – Eugene Aug 24 '18 at 08:44
0

When you call isNull(), there is no context to infer what the actual type of T be.

isNull().or(..) here expects a Predicate<? super Object> and hence you cannot pass a Predicate<String> to it.

Flipping them will work (isEmpty().or(isNull())) isEmpty() now returns a Predicate<String> and isEmpty.or(..) expects a Predicate<? super String> and so you can pass a Predicate<Object>

But this is not what you want as you want to do the null check before checking if empty (Thanks to Slaw@ for pointing this out. Was focusing on making it compile that I didn't pay attention to the meaning/semantic of it).

You can give compiler the clue what the type T is by writing

<YourClassName>.<String>isNull().or(isEmpty());
Thiyagu
  • 17,362
  • 5
  • 42
  • 79
  • 3
    Wouldn't flipping them be equivalent to calling `str.isEmpty() || str == null`? The first part will throw a `NullPointerException` if `str` is `null`. – Slaw Aug 22 '18 at 18:45
  • @Slaw oh ya. You are right.. – Thiyagu Aug 22 '18 at 18:46
  • 1
    @Slaw I have updated the answer (saying it won't work in this case). But I decided to leave it as it is - it could be useful to see how flipping works (keeping aside the current context of what each predicate is doing) – Thiyagu Aug 22 '18 at 18:48
0

Specifically for String, what you might just be looking for is a single Predicate like this -

private static Predicate<String> isNullOrEmpty() {
    return s -> s == null || s.isEmpty();
}

This can be further used as

List<String> strings = Arrays.asList("", "one", "two", null, "three");
strings.stream().filter(isNullOrEmpty()).forEach(System.out::println);
// ofcourse the above would print "null" and is less useful

Or with the capability of Predicate.not in Java11, the more intuitive way and seemingly useful would be -

List<String> strings = Arrays.asList("", "one", "two", null, "three");
strings.stream().filter(Predicate.not(isNullOrEmpty)).forEach(System.out::println); 
// this would print one, two and three
Naman
  • 27,789
  • 26
  • 218
  • 353