56

If I have a Name object and have an ArrayList of type Name (names), and I want to ascertain whether my list of names contains a given Name object (n), I could do it two ways:

boolean exists = names.contains(n);

or

boolean exists = names.stream().anyMatch(x -> x.equals(n));

I was considering if these two would behave the same and then thought about what happens if n was assigned null?

For contains, as I understand, if the argument is null, then it returns true if the list contains null. How would I achieve this anyMatch - would it be by using Objects.equals(x, n)?

If that is how it works, then which approach is more efficient - is it anyMatch as it can take advantage of laziness and parallelism?

user1803551
  • 12,965
  • 5
  • 47
  • 74
Tranquility
  • 3,061
  • 5
  • 23
  • 37
  • What is `names` type? – Tunaki Feb 04 '16 at 11:50
  • 1
    If `names` is indeed an `ArrayList`, then I'd guess the performance would be similar. But if it's something like a `HashSet` then a `contains` call would be almost certainly more efficient (as it doesn't actually need to loop through the elements). – Joachim Sauer Feb 04 '16 at 11:54
  • @JoachimSauer He *said* that it is an `ArrayList`. It is at least reasonable to assume that this is true. – Marco13 Feb 04 '16 at 11:58
  • @Marco13: I'm not questioning it, just giving a tiny bit more information than asked for. Answering the specific narrow question alone would possibly give a wrong picture. – Joachim Sauer Feb 04 '16 at 11:59
  • @JoachimSauer Agreed, it might be worth to point out this difference (as Zbynek Vyskovsky - kvr000 also did in his answer) – Marco13 Feb 04 '16 at 12:01
  • @Marco13 It is worth mentioning in my humble opinion. – Neil Feb 04 '16 at 12:01

2 Answers2

63

The problem with the stream-based version is that if the collection (and thus its stream) contains null elements, then the predicate will throw a NullPointerException when it tries to call equals on this null object.

This could be avoided with

boolean exists = names.stream().anyMatch(x -> Objects.equals(x, n));

But there is no practical advantage to be expected for the stream-based solution in this case. Parallelism might bring an advantage for really large lists, but one should not casually throw in some parallel() here and there assuming that it may make things faster. First, you should clearly identify the actual bottlenecks.

And in terms of readability, I'd prefer the first, classical solution here. If you want to check whether the list of names.contains(aParticularValue), you should do this - it just reads like prose and makes the intent clear.

EDIT

Another advantage of the contains approach was mentioned in the comments and in the other answer, and that may be worth mentioning here: If the type of the names collection is later changed, for example, to be a HashSet, then you'll get the faster contains-check (with O(1) instead of O(n)) for free - without changing any other part of the code. The stream-based solution would then still have to iterate over all elements, and this could have a significantly lower performance.

Marco13
  • 53,703
  • 9
  • 80
  • 159
  • Also: *if* performance becomes relevant, then switching to a different data type is a way simpler way to improve performance than to throw parallelism at it (`LinkedHashSet` or similar, if the order needs to be maintained). – Joachim Sauer Feb 04 '16 at 12:01
  • Nice quality content, indeed. – GhostCat Sep 16 '19 at 13:13
4

They should provide the same result if hashCode() and equals() are written in reasonable way.

But the performance may be completely different. For Lists it wouldn't matter that much but for HashSet contains() will use hashCode() to locate the element and it will be done (most probably) in constant time. While with the second solution it will loop over all items and call a function so will be done in linear time.

If n is null, actually doesn't matter as usually equals() methods are aware of null arguments.

Zbynek Vyskovsky - kvr000
  • 18,186
  • 3
  • 35
  • 43
  • 4
    You're right that every `equals` method should check whether its *argument* is `null`. But here, the predicate might try to call the `equals` method on a `null` object, leading to an NPE – Marco13 Feb 04 '16 at 12:03
  • The String literal should be on the left side of the comparison, and you dont have to worry about NPE. – Martin Nov 23 '20 at 07:41