Collections.emptyList() instead of null check?

2019-03-12 13:43发布

问题:

If I have a rarely used collection in some class which may be instantiated many times, I may sometimes resort to the following "idiom" in order to save unnecessary object creations:

List<Object> list = null;

void add(Object object) {
    if (list == null)
        list = new ArrayList<Object>();

    list.add(object);
}

// somewhere else
if (list != null)
    for (Object object : list)
         ;

Now I was wondering if I couldn't eliminate those null checks using Collections.emptyList(), however then I would have to alter the if check in add() like so:

if (list == Collections.<Object>emptyList())
    list = new ArrayList<Object>();

Is there a better way to handle this other than just allocating a new empty collection every time?

EDIT: just to be clear, I would like to use Collections.emptyList(), but the above check in add() is really really ugly... I was wondering if there's a better way to do it or even a whole other way of handling this.

回答1:

in order to save unnecessary object creations

That's a really bad idea which will litter your code with == null checks and other handling of corner cases (and presumably end up in null pointer exceptions anyway)!

Now I was wondering if I couldn't eliminate those null checks using Collections.emptyList()

No, not really. emptyList() returns an empty list. You could do

if (list.equals(Collections.<Object>emptyList()))

but that will still throw a NullPointerException if list == null, so it's still not what you're after.

My recommendation: Always initialize the list to new ArrayList<Object>, or, if you for instance want to return an empty list from a method, use Collections.emptyList() instead. (This returns the same instance every time, so no unnecessary object creation there either.)

And then use .isEmpty() to check if a collection is empty or not.



回答2:

The suggested answers are absolutly correct, just small tip - in Java 8 you can use the new Optional class to handle the case where list instance is null, in a more functional approach.

For example, something like this:

public static List<String> addElement(List<String> list, String toAdd) {
       List<String> newList = Optional.ofNullable(list).orElse(new ArrayList<>());
       newList.add(toAdd);
       return newList;
}


回答3:

emptyList() doesn't allocate an object each time.

I would create less of the object which contains the List so you can create the list every time.

What you can do is

private List<Object> list = Collections.emptyList();

private List<Object> listForWrite() {
    return list.isEmpty() ? list = new ArrayList<Object>() : list;
}


void add(Object object) {
    listForWrite().add(object);
}


// avoid creating an Iterator every time.
for (int i = 0, size = list.size(); i < size; i++) {
     ;
}


回答4:

Here is what I use for a helper method in some of my code. Really works nicely in reducing the ton of null checks I'd normally have to place before iterating over lists. If you want a list that wouldn't be immutable then you can return a new list object instead of Collections.emptyList

/**
 * Helper method to return an empty list if provided one is null.
 *
 * @param list the list
 * @return the provided list or an empty one if it was null
 */
private static <T> List<T> emptyIfNull(List<T> list) {
    if (list == null) {
        return Collections.emptyList();
    }
    return list;
}

You then just use the helper method like so:

for (Object object : emptyIfNull(existingList)) { ... }

If the list object is null, then the helper method will return the static empty list and the contents of your loop will be skipped. This is a nice way to avoid having to create null checks wrapping any list iterations.

I've made the internals of the list be of type Object just for the example, but you'd obviously change this to be whatever makes the most sense for your usage.



回答5:

If you only use the list for iterations, you could just use: for (Object object : list) which wouldn't do anything for empty lists, i.e. not a single iteration.

Otherwise just check list.isEmpty().



回答6:

You can create a utility class with static methods, like:

public class ListUtil {

/**
 * Checks if {@link List} is null or empty.
 *
 * @param <E> the generic type
 * @param list the list
 * @return true, if is null or empty
 */
public static <E> boolean isNullOrEmpty(List<E> list) {
    return list == null || list.size() == 0;
}

/**
 * Checks if {@link List} is not null and empty.
 *
 * @param <E> the generic type
 * @param list the list
 * @return true, if is not null and empty
 */
public static <E> boolean isNotNullAndEmpty(List<E> list) {
    return list != null && list.size() != 0;
}

}



回答7:

I find it easiest to follow this convention:

  1. If the point of my methods is to return a Collection, the method never returns null. Null is ambiguous. Instead I return Collection.emptyXXX() or ImmutableXXX.of() if using Guava.

  2. If I have an object which is maintaining an internal list as a member, instantiate it in the constructor. I try not to do lazy instantiation unless I can prove its a significant gain because lazy code, in my opinion, tends to be more difficult to debug when issues arise.

I really see immutable or unmodifiable empty collections being a part of a contract external to objects. If you are using the collection internally, I can only really see using immutable collections if you have a good reason (concurrency, consistency, immutability of the object)



回答8:

Here's a variation on using optional as @Stas suggested, but also using the isEmpty immutable collection as originally requested in the question:

public static List<String> addElement(List<String> list, String toAdd) {
   List<String> newList = Optional.ofNullable(list).orElse(Collections.emptyList());
   newList.add(toAdd);
   return newList;
}

This approach is also the closest thing I can find to the nice ability in Javascript to use an empty array if the collection is null.

For example:

// no need to indent everything inside a null check of myObjects
for (MyObj myObj : Optional.ofNullable(myObjects).orElse(Collections.emptyList())){
    // do stuff with myObj
}