7

So, I have an ArrayList of autogenerated strings. I want to find the first element that contains some character or else if there is no one element that matches this filter, to apply another filter. In another way, I want to return null object.

So I write this lambda expression:

str.stream()
   .filter(s -> s.contains("q"))
   .findFirst()
   .orElseGet(() -> str.stream()
                       .filter(s -> s.contains("w"))
                       .findFirst()
                       .orElseGet(null))

But if there is no one element that matches this two filters I will have NullPointerException. How, can I get something like: return null?

Nikolas Charalambidis
  • 40,893
  • 16
  • 117
  • 183
Roman Shmandrovskyi
  • 883
  • 2
  • 8
  • 22
  • 2
    Don't. Instead, return an `Optional` and let the client decide what to do with it. – Luiggi Mendoza Jul 27 '18 at 13:37
  • but think about it, you really want to return that `null` so that you move the problem further? May be a default value? or even better throw the exception earlier? – Eugene Jul 27 '18 at 13:49
  • @Eugene using FP and null... it's not the best option, but if you have to, I'd prefer to throw the exception prior rather than handling null. – Luiggi Mendoza Jul 27 '18 at 13:55

4 Answers4

6

Unless I'm missing something obvious, simply .orElse(null) instead of the last orElseGet. orElseGet accepts a Supplier and you pass a null, calling get() on a null reference, well...

Eugene
  • 117,005
  • 15
  • 201
  • 306
4

The problem is Optional::orElseGet(null) which accepts a Supplier<T> and throws the NullPointerException.

Use the Optional::orElse(null) which accepts T which is String in your case.

The following code works:

List<String> str = Arrays.asList("a", "b", "c");
String output = str.stream()
                   .filter(s -> s.contains("q"))
                   .findFirst()
                   .orElseGet(() -> str.stream()
                                       .filter(s -> s.contains("w"))
                                       .findFirst()
                                       .orElse(null));                 // <-- HERE

System.out.println(output);    // Prints null

Alternatively, use the Optional::orElseGet(Supplier<? extends T> other) to return null. The Supplier itself must be not null:

.orElseGet(() -> null));
Nikolas Charalambidis
  • 40,893
  • 16
  • 117
  • 183
4

An additional, simpler approach: you can filter on strings containing q or w, then sort to move those containing q first, find first, return null if the optional is empty:

str.stream()
    .filter(s -> s.contains("q") || s.contains("w"))
    .sorted((s1, s2) -> s1.contains("q") ? -1 : 1 )
    .findFirst()
    .orElse(null);

.sorted((s1, s2) -> s1.contains("q") ? -1 : 1 ) will move the strings containing "q" first. Since the stream has been filtered only on values containing either q or w, then returning null will only happen no element is retained.

ernest_k
  • 44,416
  • 5
  • 53
  • 99
3

orElseGet expects a Supplier, you need to pass a value, which is the case for orElse.

I wouldn't use Stream API here, one iteration is enough to solve the problem:

String wString = null;
for (String s : str) {
    if (s.contains("q")) return s;
    if (s.contains("w") && wString == null) wString = s;
}
return wString;
Andrew Tobilko
  • 48,120
  • 14
  • 91
  • 142