ArrayList()和Collections.emptyList()的区别&emptyList()、emptySet()、emptyMap()的作用和好处以及要注意的地方

x33g5p2x  于2022-05-05 转载在 Java  
字(5.0k)|赞(0)|评价(0)|浏览(320)

前言

Java中ArrayList或许是我们平时开发最常用的一个集合类了,其次是HashMap,基本上满足了业务开发的绝大多数场景。今天要说的就是Collections.emptyList()和new ArrayList()的区别以及注意事项。

先来一段代码

运行main方法,会有如下输出:

很显然,Collections.emptyList()会抛出“java.lang.UnsupportedOperationException”的异常。

使用及区别

日常开发中,我们经常会写一个方法,返回一个集合。当这个方法返回的数据为空时候,通常我们会返回一个Collections.emptyList(),而不是null。这样方法调用者就不用担心集合是否null了。比如这样的:

突然有一天,有一个同事调用了你的这个方法,然后再加入自己的数据:

就会可能出现如下几种情况:

  1. 自测时候被发现:幸亏本大神自测发现,不然测试那帮家伙肯定发现不了;
  2. 被测试发现:那是因为没有数据,好了好了,我兼容一下好了;
  3. 代码上线了:不断的抛出异常,不断的报警,大多数用户访问的页面出现系统异常。

很不幸,我们真的成功的走到了第三步,然后被用户反应出来了(因为只有部分用户在没有数据的情况下会产生这个bug)。。。

说了这么多,那既然Collections.emptyList()有问题我就直接用new ArrayList()没问题了吧?

是的!没有问题,但是总感觉low了点?

所以,搞清楚二者的区别以及适用场景才是一个爱学习的程序员要做的事情。

先看Collections.emptyList()方法的源码

返回一个不可变的空集合,那如何是不可变的呢?

原来是EmptyList类没有实现add()和remove()方法。

那使用这个方法的意义是什么?或者说JDK为什么要提供这个方法呢?

大家肯定看到了EMPTY_LIST这个类常量!

它在Collections类里面属于静态常量,静态常量什么概念?在JVM虚拟机加载完毕时候就已经存在了,当我们调用这个方法的时候不需要再去创建一个新的List对象了,减少了内存开销。所以当你的方法调用频率很高,并且可能会返回空集合时候,使用Collections.emptyList()会提高你的代码性能,降低内存开销。

总结

  1. Collections.emptyList()返回了一个不可变的空集合,不支持集合数据修改操作,多次调用不会额外增加内存消耗;
  2. new ArrayList()每次都会创建一个对象,需要内存开销;
  3. Collections.emptyList()使得调用者不需要去判断返回是否为null,但是需要注意这个集合不可变;
  4. EMPTY_LIST和emptyList()的唯一区别就是EMPTY_LIST没有支持泛型,需要强转一下;
  5. emptySet()、emptyMap()和emptyList()是一个道理;
  6. Java 9中的List.of()简化了emptyList()的使用。

背景

项目中有时候会使用Collections.emptyList返回一个空列表,但是emptyList在执行add,remove等方法时会直接抛出UnsupportedOperationException异常,我们可以看下源码

public class Collections {
    public static final List EMPTY_LIST = new EmptyList<>();

    public static final <T> List<T> emptyList() {
        return (List<T>) EMPTY_LIST;
    }

    private static class EmptyList<E>
        extends AbstractList<E>
        implements RandomAccess, Serializable {

        public int size() {return 0;}
        public boolean isEmpty() {return true;}
        public boolean contains(Object obj) {return false;}
        public E get(int index) {
            throw new IndexOutOfBoundsException("Index: "+index);
        }
    }
}

public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
    public boolean add(E e) {
        add(size(), e);
        return true;
    }
    
    public void add(int index, E element) {
        throw new UnsupportedOperationException();
    }
}

我们可以发现emptyList最后执行的是AbstractList里面的add方法,所以会直接抛出异常。为了避免报错,有同事提议将emptyList都用new ArrayList()代替,此时决定看下emptyList的优势

代码

通过百度知道emptyList不需要占用内存,而ArrayList每次new都会在堆中开辟内存空间存放对象,我们先通过代码验证一下

public class ListTest {
    private static final int printCount = 10000;

    public static void main(String[] args) {
       
        long freeMemory = Runtime.getRuntime().freeMemory();
        System.out.println("freeMemory: " + freeMemory);

        for (int i = 0; i < printCount; i++) {
            List newList = new ArrayList();
        }

        long freeMemoryNew=Runtime.getRuntime().freeMemory();
        System.out.println("freeMemory use: "+(freeMemory-freeMemoryNew));

        for(int i = 0;i < printCount; i++){
            List emptyList = Collections.emptyList();
        }
        long freeMemoryEmpty = Runtime.getRuntime().freeMemory();
        System.out.println("freeMemory use: "+(freeMemoryNew-freeMemoryEmpty));
    }
}

此时我们看一下执行结果

Connected to the target VM, address: '127.0.0.1:63534', transport: 'socket'
Disconnected from the target VM, address: '127.0.0.1:63534', transport: 'socket'
freeMemory: 253398816
freeMemory use: 1430376
freeMemory use: 0

Process finished with exit code 0

我们可以看出new ArrayList执行一万次会消耗1430376KB内存,而Collections.emptyList不会消耗内存,那有人会说emptyList不也是new EmptyList()吗?其实我们再仔细看下上面的源码就发现emptyList是一个static变量,只会初始化一次,所以后续使用不会再初始化对象。此时我们可以得出结论,emptyList不占用内存,但是无法执行add等方法,new ArrayList()占用内存,但是会初始化对象数组,可以执行add等方法。

Java之Collections.emptyList()、emptySet()、emptyMap()的作用和好处以及要注意的地方

先说明一下好处有哪些:
1,如果你想 new 一个空的 List ,而这个 List 以后也不会再添加元素,那么就用 Collections.emptyList() 好了。
new ArrayList() 或者 new LinkedList() 在创建的时候有会有初始大小,多少会占用一内存。
每次使用都new 一个空的list集合,浪费就积少成多,浪费就严重啦,就不好啦
2,为了编码的方便。
比如说一个方法返回类型是List,当没有任何结果的时候,返回null,有结果的时候,返回list集合列表。
那样的话,调用这个方法的地方,就需要进行null判断。使用emptyList这样的方法,可以方便方法调用者。返回的就不会是null,省去重复代码。

注意的地方:
这个空的集合是不能调用.add(),添加元素的。因为直接报异常。因为源码就是这么写的:直接抛异常。

哦,Collections里面没这么写,但是EmptyList继承了AbstractList这个抽象类,里面简单实现了部分集合框架的方法。
这里面的add方法最后调用的方法体,就是直接抛异常。
throw new UnsupportedOperationException();
这么解释add报异常就对啦。

下面简单看下这个源码:

/** 
    * Collections 类里面的方法如下,一步步往下看就是啦 
    */  
   public static final <T> List<T> emptyList() {  
       return (List<T>) EMPTY_LIST;  
   }  
//。。。。。  
   /** 
    * Collections 类里面的方法如下,一步步往下看就是啦 
    */  
   public static final List EMPTY_LIST = new EmptyList<>();  
//。。。。。  
 /** 
    * Collections里面的一个静态内部类 
    */  
   private static class EmptyList<E> extends AbstractList<E> implements RandomAccess, Serializable {  
       private static final long serialVersionUID = 8842843931221139166L;  
  
       public Iterator<E> iterator() {  
           return emptyIterator();  
       }  
       public ListIterator<E> listIterator() {  
           return emptyListIterator();  
       }  
  
       public int size() {return 0;}  
       public boolean isEmpty() {return true;}  
  
       public boolean contains(Object obj) {return false;}  
       public boolean containsAll(Collection<?> c) { return c.isEmpty(); }  
  
       public Object[] toArray() { return new Object[0]; }  
  
       public <T> T[] toArray(T[] a) {  
           if (a.length > 0)  
               a[0] = null;  
           return a;  
       }  
  
       public E get(int index) {  
           throw new IndexOutOfBoundsException("Index: "+index);  
       }  
  
       public boolean equals(Object o) {  
           return (o instanceof List) && ((List<?>)o).isEmpty();  
       }  
  
       public int hashCode() { return 1; }  
  
       // Preserves singleton property  
       private Object readResolve() {  
           return EMPTY_LIST;  
       }  
   }

除了这个emptyList,之外,还有类似的,emptyMap,emptySet等等。具体看下图,都是一个套路。

相关文章