How to write a generic isEmpty method which can check for null, empty?

25,108

Solution 1

This sounds like a bad design to me. Null is null, empty is empty, if it's a string it's a string, and so on. Don't try to jam everything up in one method. It's bad for maintainability and readability.

if (str == null || str.isEmpty())
    ...

and

if (coll == null || coll.isEmpty())

are both perfectly fine.

Personally however, I try to never ever equate null with an empty string or empty collection. I think it's a bad practice. A null collection is no collection at all, an empty collection is in fact still a collection. You can avoid many if (coll == null) checks by keeping a collection non-null. If you're worried about memory consumption, use use Collections.emptySet et al.


That being said, if you still want to go in this direction, I'd suggest you use plain method overloading and create one isEmpty(Collection<?> coll) and one isEmpty(String str) to avoid instanceof and casting.


Regarding your edit:

Don't do for instance

if (value == null || value.isEmpty()) {
    return true;
}
return false;

just do

return value == null || value.isEmpty();

Solution 2

For collections, you'll want to use isEmpty() instead of size(). For some collection types (such as LinkedList), size() is more expensive than isEmpty().

Solution 3

I like to have a utility class in a common library that handles this. Note that we use the object's own isEmpty, length, or size methods (in that order) if the object has one (after determining the object isn't null). By making calls to this, one doesn't need to worry about NPEs anymore -- you make your calls to this and you are good-to-go -- if it's true, then your collection/map/etc isn't null and has something in it; if it's false, skip over the item (it's either null or empty by it's own account). The second method checks an array to see if it's null or empty, but doesn't check the contents. When you iterate over the array, you simply do a check, then iterate, and as you iterate, check each element.

/**
 * Provides methods to perform input validation and boundary validation.
 */
public final class ValidationUtils {
    /**
     * Check to see if Object is empty or null.
     *
     * @param object
     *            The object to check
     * @return boolean {@code true} iff the Object is null or determined to be empty (using methods that it provides --
     *         if it doesn't provide such methods, it's only empty if it's null)
     */
    public static boolean isEmpty(@Nullable final Object object) {
        if (object == null)
            return true;

        try {
            // Try to use the object class's isEmpty method to check if we're empty, now that we know we're
            // not NULL
            final Method method = object.getClass().getMethod("isEmpty");
            final Object result = method.invoke(object);

            if (result instanceof Boolean)
                return Boolean.class.cast(result).booleanValue();
        } catch (@NotNull final NoSuchMethodException | InvocationTargetException | IllegalArgumentException
                | IllegalAccessException | SecurityException ignored) {
            // We couldn't invoke... let's go to the next common method
        }

        try {
            // Try to use the object class's length method to check if we're empty, now that we know we're
            // not NULL
            final Method method = object.getClass().getMethod("length");
            final Object result = method.invoke(object);

            if (result instanceof Integer)
                return Integer.class.cast(result).intValue() <= 0;
            if (result instanceof Long)
                return Long.class.cast(result).longValue() <= 0L;
        } catch (@NotNull final NoSuchMethodException | InvocationTargetException | IllegalArgumentException
                | IllegalAccessException | SecurityException ignored) {
            // We couldn't invoke... let's go to the next common method
        }

        try {
            // Try to use the object class's size method to check if we're empty, now that we know we're
            // not NULL
            final Method method = object.getClass().getMethod("size");
            final Object result = method.invoke(object);

            if (result instanceof Integer)
                return Integer.class.cast(result).intValue() <= 0;
            if (result instanceof Long)
                return Long.class.cast(result).longValue() <= 0L;
        } catch (@NotNull final NoSuchMethodException | InvocationTargetException | IllegalArgumentException
                | IllegalAccessException | SecurityException ignored) {
            // We couldn't invoke... but we're not null... treat it like an Object
        }

        // Let's treat it like an Object... we're not null, so we're not empty
        return false;
    }

    /**
     * Check to see if the array of Objects is empty or null.
     *
     * @param obj
     *            Object Array to check
     * @return boolean true if empty
     */
    public static boolean isEmpty(@Nullable final Object... obj) {
        return ((obj == null) || (obj.length == 0));
    }
}

Example uses:

    final Map<String, String[]> postData = ServletActionContext.getRequest().getParameterMap();
    // We're testing if the map is null or empty... we could just do a null check here because of how we're using the map after, but...
    if (!ValidationUtils.isEmpty(postData)) {
        for (final Map.Entry<String, String[]> reqKey : postData.entrySet()) {
            // We're checking if the array is null or doesn't have any length; again, the foreach does the latter, but this is perfectly fine
            if (!ValidationUtils.isEmpty(reqKey.getValue())) {
                for (final String value : reqKey.getValue()) {
                    // Checking the value
                    if (ValidationUtils.isEmpty(value)) {
                        continue;
                    }

                    ...
                }
            }
        }
    }

Solution 4

As I just wrote in my answer to your other question posted 30 minutes before this one, it is wasteful to check everything every time.

However, these types of functions are still useful in some situations. Instead of using an "is-valid" function, however, I would implement it as a "crash-if-bad" function. Also note that this function is for collections only.

An example use is

CrashIfCollection.badNullLength(coll, "coll", Null.BAD, 1);

Code:

   import  java.util.Arrays;
   import  java.util.Collection;

enum Null {OK, BAD};

public class CrashIfCollection  {
   public static final void main(String[] ignored)  {
      test(null);
      test(Arrays.asList(new String[] {}));
      test(Arrays.asList(new String[] {"one element"}));
   }
      private static final void test(Collection<?> to_test)  {
         System.out.println("Testing " + ((to_test == null) ? "null"
            :  Arrays.toString(to_test.toArray())));
         try  {
            CrashIfCollection.badNullLength(to_test, "to_test", Null.BAD, 1);
         }  catch(Exception x)  {
            System.out.println(x);
         }
      }
   public static final void badNullLength(Collection<?> coll, String coll_name, Null nullness, int min_len)  {
      try  {
         if(nullness == Null.OK)  {
            if(coll == null)  {
               return;
            }
            if(coll.size() < min_len)  {
               throw  new IllegalArgumentException(coll_name + ".size() (" + coll.size() + ") is less than min_len (" + min_len + ")");
            }
         }
      }  catch(NullPointerException npx)  {
         if(nullness == null)  {
            throw  new NullPointerException("nullness");
         }
         throw  npx;
      }

      //Null.BAD

      try  {
         if(coll.size() < min_len)  {
            throw  new IllegalArgumentException(coll_name + ".size() (" + coll.size() + ") is less than min_len (" + min_len + ")");
         }
      }  catch(NullPointerException npx)  {
         throw  new NullPointerException(coll_name);
      }
   }
}

Output:

Testing null
java.lang.NullPointerException: to_test
Testing []
java.lang.IllegalArgumentException: to_test.size() (0) is less than min_len (1)
Testing [one element]
Share:
25,108
john
Author by

john

Updated on July 05, 2022

Comments

  • john
    john almost 2 years

    I am writing a utility method which can check for empty and null string, or collection or an object or any general types -

    public static boolean isEmpty(Object obj) {
        if (obj == null)
            return true;
        if (obj instanceof Collection)
            return ((Collection<?>) obj).size() == 0;
    
        // is below line expensive?
        final String s = String.valueOf(obj).trim();
    
        return s.length() == 0 || s.equalsIgnoreCase("null");
    }
    

    How can I make my above method efficient, since above isEmpty method will be called multiple times from the application which is very performance critical?

    I am suspecting below line will be expensive because of heavy toString methods and it will create temporary garbage as well that might cause GC and slow down the performance?

    final String s = String.valueOf(obj).trim();
    

    Update:-

    I have separated isEmpty method for each type now. Below is what I got after simplifying the above isEmpty method.

    public static boolean isEmpty(Object obj) {
        if (obj == null) {
            return true;
        }
        return false;
    }
    
    public static boolean isEmpty(Collection<?> value) {
        if (value == null || value.isEmpty()) {
            return true;
        }
        return false;
    }
    
    public static boolean isEmpty(String value) {
        if (value == null || value.isEmpty()) {
            return true;
        }
        return false;
    }
    

    Update 2:-

    If I need to check for map null or empty, should I keep both collection isEmpty and Map isEmpty method both or Collection isEmpty method will be fine for that?

    public static void main(String[] args) {
    
        Map<String, String> hello = new HashMap<String, String>();
        System.out.println(isEmpty(hello));
    
        Map<String, HashMap<Integer, String>> primary = new HashMap<String, HashMap<Integer, String>>();
        System.out.println(isEmpty(primary));
    
    }
    
    public static boolean isEmpty(Collection<?> value) {
        return value == null || value.isEmpty();
    }
    
    public static boolean isEmpty(Map<?, ?> value) {
        return value == null || value.isEmpty();
    }
    
  • aioobe
    aioobe almost 10 years
    There may be cases where you're right, but I'd be extremely surprised if size() was noticeably slower than isEmpty() for linked list.
  • Steve Kuo
    Steve Kuo almost 10 years
    Many (if not all) of Google Guava's collections are lazily evaluated. Calling size() will cause it to evaluate (walk all elements in the case of a filtered collection). In this case, all one cares about is 0 or non-0. Why compute the exact size only to not use that information?
  • aioobe
    aioobe almost 10 years
    Good to know. I still stand by my comment though.
  • john
    john almost 10 years
    @aioobe Thanks for your suggestion. I learned something new today. I have updated the question with your suggestion. Can you let me know that's what you meant?
  • aioobe
    aioobe almost 10 years
    Yea. Your update is nicer than your original suggestion, but I'd still reconsider allowing null at all.
  • john
    john almost 10 years
    Thanks aioobe. I didn't understand this line of yours still reconsider allowing null at all. What do you mean by that?
  • aioobe
    aioobe almost 10 years
    If you let null and "" mean the same thing, you'll always have to check for null when you want to use the string. Say for instance you want to take the length of a string to make sure its at least 8 characters long. Then you'd have to do if (str == null || str.length() < 8) { ... }. If you instead say, I will make sure my str is never null and whenever I'm about to set it to null, I'll set it to "" instead, you'll never need to cover special cases by doing if (str == null). It just easier for you to write the code and for other to read the code if you maintain such invariant.
  • Hannes
    Hannes almost 10 years
    @aioobe To determine the size of a LinkedList, you have to walk the whole list. To determine if it's empty, you only have to check the existence of an element. O(1) beats O(n).
  • john
    john almost 10 years
    Makes sense now. Thanks a lot aioobe for the help. After I edited my actual code to use isEmpty like the way you suggested, performance of my application improved a lot from 4 ms to 0.6 ms as 95th percentile. I cannot imagine, such a small thing can make this big difference. :)
  • john
    john almost 10 years
    @aioobe One last thing. I have added another update in my question. If I need to check Map for null and empty, should I keep both isEmpty method, one for Collection and other for Map? Or collection isEmpty one will be fine?
  • aioobe
    aioobe almost 10 years
    A Map is not a collection so that would require a method of its own.
  • David Ehrmann
    David Ehrmann almost 10 years
    "I try to never ever equate null with an empty string or empty collection. I think it's a bad practice." This.
  • aioobe
    aioobe almost 10 years
    No, the LinkedList (obviously) maintains a size property. size() is O(1): Link to java/util/LinkedList.java.
  • pqsk
    pqsk over 9 years
    @aioobe that may be the case for LinkedList, but if you have the option to do a guaranteed O(1) vs O(n), where n could be millions why take the risk with a generic method for all types of collections? I hope you don't have code like that in production if you use a similar generic method.
  • aioobe
    aioobe over 9 years
    Using isEmpty is obviously better than size. I've never claimed anything else.