I tend to agree.

Presumably it is similar reasoning that led to Collection<E>.contains() to take 
Object rather than E.

> On Jan 29, 2023, at 7:28 AM, John Hendrikx <john.hendr...@gmail.com> wrote:
> 
> TLDR; why does contains(null) not just return false for collections that 
> don't allow nulls. Just because the interface allows it, does not mean it 
> should do it as it devalues the usefulness of the abstraction provided by the 
> interface.
> 
> Background:
> 
> I'm someone that likes to check correctness of any constructor or method 
> parameter before allowing an object to be constructed or to be modified, in 
> order to maintain invariants that are provided by the class to its users.
> 
> This ranges from simple null checks, to range checks on numeric values, to 
> complete checks like "is a collection sorted" or is a list of nodes acyclic. 
> Anything I can check in the constructor that may avoid problems further down 
> the line or that may affect what I can guarantee on its own API methods.  For 
> example, if I check in the constructor that something is not null, then the 
> associated getter will guarantee that the returned value is not null.  If I 
> check that a List doesn't contain nulls, then the associated getter will 
> reflect that as well (assuming it is immutable or defensivily copied).
> 
> For collections, this is currently becoming harder and harder because more 
> and more new collections are written to be null hostile.  It is fine if a 
> collection doesn't accept nulls, but throwing NPE when I ask such a 
> collection if it contains null seems to be counter productive, and reduces 
> the usefulness of the collection interfaces.
> 
> This strict interpretation makes the collection interfaces harder to use than 
> necessary. Interfaces are only useful when their contract is well defined. 
> The more things an interface allows or leaves unspecified, the less useful it 
> is for its users.
> 
> I know that the collection interfaces allow this, but you have to ask 
> yourself this important question: how useful is an interface that makes 
> almost no guarantees about what its methods will do? Interfaces like `List`, 
> `Map` and `Set` are passed as method parameters a lot, and to make these 
> useful, implementations of these interfaces should do their best to provide 
> as consistent an experience as is reasonably possible.  The alternative is 
> that these interfaces will slowly decline in usefulness as methods will start 
> asking for `ArrayList` instead of `List` to avoid having to deal with a too 
> lenient specification.
> 
> With the collection interfaces I get the impression that recently there has 
> been too much focus on what would be easy for the collection implementation 
> instead of what would be easy for the users of said interfaces. In my 
> opinion, the concerns of the user of interfaces almost always far outweigh 
> the concerns of the implementors of said interfaces.
> 
> In the past, immutable collections were rare, but they get are getting more 
> and more common all the time.  For example, in unit testing. Unfortunately, 
> these immutable collections differ quite a bit from their mutable 
> counterparts.  Some parts are only midly annoying (not accepting `null` as 
> the **value** in a `Map` for example), but other parts require code to be 
> rewritten for it to be able to work as a drop-in replacement for the mutable 
> variant. A simple example is this:
> 
>     public void addShoppingItems(Collection<String> shoppingItems) {
>         if (shoppingItems.contains(null)) {  // this looks quite reasonable 
> and safe...
>             throw new IllegalArgumentException("shoppingItems should not 
> contain nulls");
>         }
> 
>         this.shoppingItems.addAll(shoppingItems);
>     }
> 
> Testing this code is annoying:
> 
>      x.addShoppingItems(List.of("a", null"));   // can't construct immutable 
> collection with null
> 
>      x.addShoppingItems(Arrays.asList("a", null"));  // fine, go back to what 
> we did before then...
> 
> The above problems, I suppose we can live with it; immutable collections 
> don't want `null` although I don't see any reason to not allow it as I can 
> write a similar `List.of` that returns immutable collections that do allow 
> `null`. For JDK code this is a bit disconcerting, as it is supposed to be as 
> flexible as is reasonable without having too much of an opinion about what is 
> good or bad.
> 
> This next one however:
> 
>      assertNoExceptionThrown(() -> x.addShoppingItems(List.of("a", "b")));  
> // not allowed, contains(null) in application code throws NPE
> 
> This is much more insidious; the `contains(null)` check has been a very 
> practical way to check if collections contain null, and this works for almost 
> all collections in common use, so there is no problem.  But now, more and 
> more collections are starting to just throw NPE immediately even just to 
> **check** if a null element is present. This only serves to distinguish 
> themselves loudly from other similar collections that will simply return 
> `false`.
> 
> I think this behavior is detrimental to the java collection interfaces in 
> general, especially since there is a clear answer to the question if such a 
> collection contains null or not. In fact, why `contains` was ever allowed to 
> throw an exception aside from "UnsupportedOperationException" is a mystery to 
> me, and throwing one when one could just as easily return the correct and 
> expected answer seems very opiniated and almost malicious -- not behavior I'd 
> expect from JDK core libraries.
> 
> Also note that there is no way to know in advance if `contains(null)` is 
> going to be throwing the NPE. If interfaces had a method `allowsNulls()` that 
> would already be an improvement.
> 
> --John
> 
> 

Reply via email to