Difference between an empty ArrayList and an Array

2019-04-08 08:08发布

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&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;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?

5条回答
干净又极端
2楼-- · 2019-04-08 08:48

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.

查看更多
Lonely孤独者°
3楼-- · 2019-04-08 09:06

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 call isEmpty().

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:

  • Calling either the empty constructor, or the constructor with initial capacity of an ArrayList creates an empty list. This list does not contain any elements, not even null.
  • null is a perfectly valid element of an ArrayList. The cases where this might be useful are not extremely common but they do exist.
查看更多
Deceive 欺骗
4楼-- · 2019-04-08 09:08

A List is empty if it contains zero elements. This is both the natural meaning of the term and the test performed by the isEmpty() method.

null is a value that any ArrayList 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 a List, if an invocation of add(null) on that list completes normally then afterward the list is not empty. Similarly, each null element in a list contributes to its size.

Why add(item) doesnt validate if item!=null ?

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 Lists may contain null elements. (Implementations are permitted to reject nulls, but ArrayList, as a general-purpose implementation, accepts them).

Why contains(null) says false if the list is full of nulls?

It doesn't. Don't just take my word for it -- the data you present also contradict you.

查看更多
We Are One
5楼-- · 2019-04-08 09:11

An ArrayList explicitly is allowed and able to store null values, because they might be meaningful to your program. And empty list is empty (i.e. doesn't contain anything, not even null. After you successfully add(null) (it returns true to signal success), the list must obviously return true on contains(null) as well. In fact, you can even remove(null) from that list and it will be empty again.

查看更多
贼婆χ
6楼-- · 2019-04-08 09:13

A list containing null is NOT empty. It contains null. Lists are allowed to contain null, so you can put null in it if you want.

查看更多
登录 后发表回答