今天在检查项目代码的时候发现 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 里面提到就足够了。
具体看下面例子
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
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 最大特点是整个集合允许有多个元素存入数组,一旦初始化后,这个数组的长度就是固定的,但数组的元素可以修改,所以也就是为什么 add 和 remove 方法报错的原因。✅如果在开发中碰到长度可以确定的集合,并且在初始化时已经确定了储存元素的情况下,推荐使用。
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