为什么 IDEA 建议用 Collections.singletonList(o) 代替 Arrays.asList(o) ?

为什么 IDEA 建议用 Collections.singletonList(o) 代替 Arrays.asList(o) ?

问题描述

​ 今天在检查项目代码的时候发现 idea 报了一个⚠️Call to 'asList' with only one argument,我点击 quick fixes,idea 让我用 Collections.singletonList()代替Arrays.asList()

​ 但是为什么要这样重构?遇事不决,google 之。

​ 在 idea support 下找到了如下提问

Title :

​ Why does Idea suggest to replace Arrays.asList(o) with Collections.singletonList(o)?

https://intellij-support.jetbrains.com/hc/en-us/community/posts/115000213964-Why-does-Idea-suggest-to-replace-Arrays-asList-o-with-Collections-singletonList-o-

Descption :

​ Difference in behaviors of List objects returned by Arrays.asList(o) vs Collections.singletonList(o) is very subtle, but it exists, it lies in the implementation of #set() method, which will produce UnsupportedOperationException in case of Collections.singletonList(o).

​ Still Idea issues a warning saying “Call to ‘asList’ with only one argument” and suggests replacing it with Collections#singletonList without notifying of possible changes in behavior.

​ Should this be considered a safe refactor?

大致翻译了下:

标题 :

​ 为什么 Idea 建议用 Collections.singletonList(o) 代替 Arrays.asList(o)?

说明 :

​ 虽然 Arrays.asList(o) 与 Collections.singletonList(o) 返回的 List 对象的行为差异非常小,但差异确实是存在的,当使用 set() 方法 时,用 Collections.singletonList(o),会产生 UnsupportedOperationException。

​ 但 Idea 警告 “调用’asList’只有一个参数”,并建议用 Collections#singletonList 替换,但不告诉你这样做会发生什么。

​ 这应该被认为是一个安全的重构吗?

jetbrains 的员工回答如是:

​ 确实可以在 Arrays.asList 返回的 List 上调用’set’方法(也可以调用’Collections.sort’),但’add’和’remove’方法也会对这个 List 抛出 UnsupportedOperationException。

​ 所以 quick fixes 确实可能在极少数情况下破坏代码,也许这个警告在 quick fixes 里面提到就足够了。
具体看下面例子

example

Collections.singletonList()来看:

java.util.Collections 类的 singletonList() 方法用于返回一个只包含指定对象的不可变列表。

返回的列表是可序列化的。

  public static void main(String[] args) {
    try {
      var menu = Collections.singletonList("Snacks");
      System.out.println("singletonList:" + menu);
      //throw java.lang.UnsupportedOperationException
      menu.add("hot dog");
    } catch (IllegalArgumentException e) {
      System.out.println("Exception thrown :" + e);
    }
  }

output: singletonList:[Snacks]
Exception in thread “main” java.lang.UnsupportedOperationException

  • 我们看一下 SingletonList 类的源码 (jdk14)
private static class SingletonList<E>
    extends AbstractList<E>
    implements RandomAccess, Serializable {

    @java.io.Serial
    private static final long serialVersionUID = 3093736618740652951L;

    @SuppressWarnings("serial") // Conditionally serializable
    private final E element;

    SingletonList(E obj)                {element = obj;}

  	//迭代器直接返回唯一元素
    public Iterator<E> iterator() {
        return singletonIterator(element);
    }
		//SingletonList 中只有一个元素,所以直接返回 1。
    public int size()                   {return 1;}

  	/**
    *	判断唯一元素是否和传入对象是否相等来判断是否存在
   	* @return o1==null ? o2==null : o1.equals(o2)
  	*/
    public boolean contains(Object obj) {return eq(obj, element);}

    public E get(int index) {
        if (index != 0)
          throw new IndexOutOfBoundsException("Index: "+index+", Size: 1");
        return element;
    }

    // Override default methods for Collection
    @Override
    public void forEach(Consumer<? super E> action) {
        action.accept(element);
    }
    @Override
    public boolean removeIf(Predicate<? super E> filter) {
        throw new UnsupportedOperationException();
    }
    @Override
    public void replaceAll(UnaryOperator<E> operator) {
        throw new UnsupportedOperationException();
    }
    @Override
    public void sort(Comparator<? super E> c) {
    }
    @Override
    public Spliterator<E> spliterator() {
        return singletonSpliterator(element);
    }
    @Override
    public int hashCode() {
        return 31 + Objects.hashCode(element);
    }
}
  • Collection.singletonList 长度为 1,初始完后不可修改。✅根据这个特性,在开发的时候遇到只要储存一个元素的集合,并且整个过程存储或者传递为主,不会进行修改或调整的情况下,推荐使用。

  • Collections.singletonList 中保存元素的是一个对象

再说说 Arrays.asList()

java.util.Arrays 类的 asList() 方法用于返回一个由指定数组支持的固定大小的列表。

该方法与 Collection.toArray() 相结合,充当了基于数组和基于集合的 API 之间的桥梁。

返回的列表是可序列化的,并且实现了 RandomAccess。

时间复杂度 O(1)。

	public static void main(String[] argv) 
		throws Exception 
	{ 
		try { 
			String a[] = new String[] { "A", "B", "C", "D" }; 
			List<String> list = Arrays.asList(a); 
			list.set(3,"SET");//succeed
			System.out.println("The list is: " + list); 
      
      list.set()
      //throw java.lang.UnsupportedOperationException
      list.add("F");
      //throw java.lang.UnsupportedOperationException
      list.remove("A");
		} 
		catch (NullPointerException e) { 
			System.out.println("Exception thrown : " + e); 
		} 
	} 

output: The list is: [A, B, C, SET]

Exception in thread “main” java.lang.UnsupportedOperationException

  • 来看看 Arrays.asList() 的源码 (jdk14)

    public static <T> List<T> asList(T... a) {
            return new ArrayList<>(a);
        }
    /**
    实现了快速随机访问 RandomAccess 和序列化
    ⚠️ 这里实现的 ArraysList 和我们日常用的 java.unit.ArraysList 不同,虽然他们两个都继承了`AbstractList` 但他们对 List 接口和 AbstractList 抽象类中的实现大有不同
    */
    private static class ArrayList<E> extends AbstractList<E>
        implements RandomAccess, java.io.Serializable
    {
        @java.io.Serial
        private static final long serialVersionUID = -2764017481108945198L;
        
      	// `final`来修饰这个不可变数组来储存元素
        @SuppressWarnings("serial") // Conditionally serializable
        private final E[] a;
    
        ArrayList(E[] array) {
            a = Objects.requireNonNull(array);
        }
    
    		//返回该数组长度,由于数组是不可变类型的
        @Override
        public int size() {
            return a.length;
        }
    
        @Override
        public Object[] toArray() {
            return Arrays.copyOf(a, a.length, Object[].class);
        }
    
        @Override
        @SuppressWarnings("unchecked")
        public <T> T[] toArray(T[] a) {
            int size = size();
            if (a.length < size)
                return Arrays.copyOf(this.a, size,
                                     (Class<? extends T[]>) a.getClass());
            System.arraycopy(this.a, 0, a, 0, size);
            if (a.length > size)
                a[size] = null;
            return a;
        }
    
        @Override
        public E get(int index) {
            return a[index];
        }
    
      	//与 Collection.singletonList 不同,Arrays.asList 提供了 set 方法
        @Override
        public E set(int index, E element) {
            E oldValue = a[index];
            a[index] = element;
            return oldValue;
        }
    
        @Override
        public int indexOf(Object o) {
            E[] a = this.a;
          	//传入参数可以为 null, 但不允许只有一个 null 
            if (o == null) {
                for (int i = 0; i < a.length; i++)
                    if (a[i] == null)
                        return i;
            } else {
                for (int i = 0; i < a.length; i++)
                    if (o.equals(a[i]))
                        return i;
            }
            return -1;
        }
    
      	//contains 方法,传入的参数可以为 null
        @Override
        public boolean contains(Object o) {
            return indexOf(o) >= 0;
        }
    
        @Override
        public Spliterator<E> spliterator() {
            return Spliterators.spliterator(a, Spliterator.ORDERED);
        }
    
        @Override
        public void forEach(Consumer<? super E> action) {
            Objects.requireNonNull(action);
            for (E e : a) {
                action.accept(e);
            }
        }
    
        @Override
        public void replaceAll(UnaryOperator<E> operator) {
            Objects.requireNonNull(operator);
            E[] a = this.a;
            for (int i = 0; i < a.length; i++) {
                a[i] = operator.apply(a[i]);
            }
        }
    
        @Override
        public void sort(Comparator<? super E> c) {
            Arrays.sort(a, c);
        }
    
        @Override
        public Iterator<E> iterator() {
            return new ArrayItr<>(a);
        }
    }
    
  • 从源码发现,Arrays.asList 最大特点是整个集合允许有多个元素存入数组,一旦初始化后,这个数组的长度就是固定的,但数组的元素可以修改,所以也就是为什么 addremove 方法报错的原因。✅如果在开发中碰到长度可以确定的集合,并且在初始化时已经确定了储存元素的情况下,推荐使用。

  • Arrays.asList 中传入的数组参数中允许存在 null,但不允许只有一个 null

总结

​ 当我们调用Arrays.asList (o) 时,如果只传一个参数时,idea 更推荐我们用 Collection.singletonList(o),但是当我们调用了Collection.singletonList(o) 时,我们返回的列表时不能被修改的,idea 没有告诉我们,所以这算是一个 idea 的小 bug 吧。

善用 google 和源码。

源码解读部分参考:

https://blog.csdn.net/zhangzehai2234/article/details/106040080

你可能感兴趣的:(java,intellij,idea,jdk)