I am coding some validators for a REST service which parse a JSON and I found out something that sounds wierd for me (I am not a JAVA expert at all).
Consider having two ArrayLists:
ArrayList<Object> list1 = new ArrayList<Object>();
ArrayList<Object> list2 = new ArrayList<Object>();
Both lists have something in common: they are completely empty (or full of null elements). But if I do:
list1.add(null);
Although both remain completely empty, they have completely different behaviors. And to make some methods the results are very different:
System.out.println(list1.contains(null)); //prints true!
System.out.println(list2.contains(null)); //prints false
System.out.println(CollectionUtils.isNotEmpty(list1)); //prints true
System.out.println(CollectionUtils.isNotEmpty(list2)); //prints false
System.out.println(list1.size()); //prints 1
System.out.println(list2.size()); //prints 0
Doing some research, and looking at the implementation of each of these methods, you can determine the reason for these differences, but still do not understand why it would be valid or useful to differentiate between these lists.
- Why add(item) doesnt validate if item!=null ?
- Why contains(null) says false if the list is full of nulls?
Thanks in advance!!!
EDIT:
I am mostly agree whit the answers, but I'm not yet convinced all. This is the implementation of the method remove:
/**
* Removes the first occurrence of the specified element from this list,
* if it is present. If the list does not contain the element, it is
* unchanged. More formally, removes the element with the lowest index
* <tt>i</tt> such that
* <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt>
* (if such an element exists). Returns <tt>true</tt> if this list
* contained the specified element (or equivalently, if this list
* changed as a result of the call).
*
* @param o element to be removed from this list, if present
* @return <tt>true</tt> if this list contained the specified element
*/
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
/*
* Private remove method that skips bounds checking and does not
* return the value removed.
*/
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
}
So, now if i do:
ArrayList<Object> list = new ArrayList<Object>();
list.add(null);
System.out.println(list.contains(null)); //prints true!
list.remove(null);
System.out.println(list.contains(null)); //prints false!
what I'm missing?
Fortunately you don't have to be a java expert or an expert at all.
The best way to think of this is like a parking lot. An array with null elements is like a parking lot with nothing parked in it. An empty array is like the plans for a parking lot.
The entire confusion stems from the incorrect initial assumption that the ArrayLists you created are full of nulls. They are not, they are completely empty - meaning they have zero elements. As such, you will get false if you call
contains(null)
on them, and true when you callisEmpty()
.Once you added null to list1, it ceased to be empty, and indeed contains a null element - list2 is still empty and still doesn't contain null.
In your last code sample, the second call to Collections.isNotEmpty definitely does not return true. The list had one null element, you removed it, so it's empty again.
The main take-aways here are:
A
List
is empty if it contains zero elements. This is both the natural meaning of the term and the test performed by theisEmpty()
method.null
is a value that anyArrayList
may contain as an element. Clearly, in that case the list has at least one element, and therefore is not empty. No matter what the initial state of aList
, if an invocation ofadd(null)
on that list completes normally then afterward the list is not empty. Similarly, eachnull
element in a list contributes to its size.Ask Joshua Bloch. He designed it. Most people take him to be a pretty smart guy, and consider the Collections API a highly successful effort.
Seriously, it is a design choice that
List
s may containnull
elements. (Implementations are permitted to reject nulls, butArrayList
, as a general-purpose implementation, accepts them).It doesn't. Don't just take my word for it -- the data you present also contradict you.
An
ArrayList
explicitly is allowed and able to storenull
values, because they might be meaningful to your program. And empty list is empty (i.e. doesn't contain anything, not evennull
. After you successfully add(null) (it returnstrue
to signal success), the list must obviously returntrue
on contains(null) as well. In fact, you can even remove(null) from that list and it will be empty again.A list containing
null
is NOT empty. It containsnull
. Lists are allowed to contain null, so you can put null in it if you want.