解析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 的单个元素返回

相关推荐
齐 飞14 分钟前
MongoDB笔记01-概念与安装
前端·数据库·笔记·后端·mongodb
LunarCod31 分钟前
WorkFlow源码剖析——Communicator之TCPServer(中)
后端·workflow·c/c++·网络框架·源码剖析·高性能高并发
码农派大星。1 小时前
Spring Boot 配置文件
java·spring boot·后端
杜杜的man2 小时前
【go从零单排】go中的结构体struct和method
开发语言·后端·golang
幼儿园老大*2 小时前
走进 Go 语言基础语法
开发语言·后端·学习·golang·go
llllinuuu2 小时前
Go语言结构体、方法与接口
开发语言·后端·golang
cookies_s_s2 小时前
Golang--协程和管道
开发语言·后端·golang
为什么这亚子2 小时前
九、Go语言快速入门之map
运维·开发语言·后端·算法·云原生·golang·云计算
想进大厂的小王2 小时前
项目架构介绍以及Spring cloud、redis、mq 等组件的基本认识
redis·分布式·后端·spring cloud·微服务·架构
customer083 小时前
【开源免费】基于SpringBoot+Vue.JS医院管理系统(JAVA毕业设计)
java·vue.js·spring boot·后端·spring cloud·开源·intellij-idea