解析singletonList以及asList

在实际开发过程中,经常会用到Collections.singletonListArrays.asList。在之前的文章中提过"事物都有两面性,好用的工具可能存在着一些潜在的隐患",本文将介绍Collections.singletonListArrays.asList的优缺点以及注意点。

Collections.singletonList

Collections.singletonlist的作用是创建一个只包含一个元素的不可变List集合,也称单例列表。具有以下特点:

  1. 不可变性:创建出的集合是不可变的,集合大小固定为1。一旦有尝试修改集合的行为,都会报出UnsupportedOperationException异常。
  2. 内存优化:由于集合中只允许存储一个元素,内存分配效率高,且无需分配额外的内存
  3. 线程安全:由于集合是不允许改变的,也就是不允许有"写"的动作,因此在使用时不用担心线程安全问题。但这个线程安全是依赖集合的特殊结构进行保证,因为集合一旦被修改立即报错,这个和常规理解上的线程安全不同

结合以上特点,配合着源码进行理解:

java 复制代码
public static <T> List<T> singletonList(T o) {
    return new SingletonList<>(o);
}
java 复制代码
private static class SingletonList<E> extends AbstractList<E>
    implements RandomAccess, Serializable {

    private static final long serialVersionUID = 3093736618740652951L;

    private final E element;

    SingletonList(E obj)                {element = obj;}

    public Iterator<E> iterator() {
        return singletonIterator(element);
    }

    public int size()                   {return 1;}

    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
    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);
    }
}

从源码中得到以下信息:

  1. 调用Collections.singletonList(T o)方法底层其实是创建了一个SingletonList对象
  2. SingletonList中唯一的一个元素可以设置为null
  3. SingletonList中唯一的一个元素是final类型的,所以一旦被设置后就不可以进行修改,只能去读这个元素
  4. SingletonList的size方法的返回值固定为1
  5. SingletonList是Collections的内部类,继承AbstractList。其作为AbstractList的子类,并没有重写add、remove方法,那就是使用父类AbstractList的实现,AbstractList中的具体实现如下,在方法内部直接抛出异常:
java 复制代码
public void add(int index, E element) {
    throw new UnsupportedOperationException();
}

public E remove(int index) {
    throw new UnsupportedOperationException();
}

Arrays.asList

Arrays.asList()的作用是将数组转换为List集合,并且集合一旦创建,其大小就固定了。具有以下特点:

  1. 不可变性:创建出的集合是不可变的,集合大小固定但不限定大小。一旦有尝试修改集合的行为,都会报出UnsupportedOperationException异常
  2. 基本类型数组的装箱问题:如果尝试将一个基本类型的数组(如int[]double[]等)传递给Arrays.asList()方法,会导致自动装箱的问题。因为泛型无法接受基本类型,所以数组元素将被当作一个对象处理,而不是数组的元素;而对对象数组,则会将数组中的每个元素都作为 List 的单个元素返回
  3. 数组与列表的关联:由于Arrays.asList()方法返回的列表是对原始数组的引用,因此对列表的修改会反映到原始数组上。同样,对原始数组的修改也会反映到列表上

集合以上特点,配合源码分析:

可见Arrays.asList()方法实际上是创建了一个ArrayList,但这个ArrayList并不是我们日常使用的java.util.ArrayList,而是Arrays的内部类。

java 复制代码
public static <T> List<T> asList(T... a) {
    return new ArrayList<>(a);
}
java 复制代码
private static class ArrayList<E> extends AbstractList<E>
    implements RandomAccess, java.io.Serializable
{
    private static final long serialVersionUID = -2764017481108945198L;
    private final E[] a;

    ArrayList(E[] array) {
        a = Objects.requireNonNull(array);
    }

    @Override
    public int size() {
        return a.length;
    }
    
    // 还有其他代码
    // ...
    // ...

这个内部类也是继承AbstractList,并且也是没有重写add、remove方法的,所以一旦对集合有增删操作,都会报出UnsupportedOperationException异常

ArrayList内部维护了一个final修饰的数组,且ArrayList的构造方法接受一个数组,并且内部数组是直接使用了原始的数组,都是指向同一片内存地址,说明Arrays.asList()创建出的集合大小不可变,并且对原数组或者新集合的改变都会互相产生影响

因为asList方法接受的参数是一个泛型的变长参数,基本数据类型是无法泛型化的,任何类型的对象都有一个 class 属性,这个属性代表了这个类型本身。原生数据类型,比如 int,short,long等,是没有这个属性的,具有 class 属性的是它们所对应的包装类 Integer,Short,Long。所以对于基本数据类型的数组,会将其整体作为一个对象,而对对象数组,则会将数组中的每个元素都作为 List 的单个元素返回

相关推荐
程序员爱钓鱼33 分钟前
为什么学习 Rust?Rust 能做什么?
后端·rust
掘金者阿豪1 小时前
Docker命令太多记不住?用Portainer把容器管理变成可视化操作
后端
用户69371750013842 小时前
AI Agent 里的 Loop 到底是什么?
android·前端·后端
Rain的Java大神之路2 小时前
高并发下的抢优惠券如何设计
后端
钟智强2 小时前
AWS CloudGoat 实战:一个 SSRF 如何撬动整个云账户
后端
Ai拆代码的曹操2 小时前
@Transactional 写在 private 方法上,事务根本没生效
后端
云技纵横2 小时前
@Async 默认执行器又炸了:SimpleAsyncTaskExecutor 为什么不是线程池?
后端·面试
程序员Agions2 小时前
Fypro 新手快速上手与实战指南
后端
舒心的云朵2 小时前
用三张图片详解Asp.Net 全生命周期
后端·asp.net
卷无止境2 小时前
C++ 类型转换变迁之路:从隐式转换到 static_cast
c++·后端