Skip to main content

List的不可变性

huhxOriginalAbout 5 minjavaJava

Java提供了各种各样的方法来创建List,从简单的new ArrayList()ArrayList.asList(),再到Collections.unmodifiableList,最后到Java 9中引入的List.copyOf方法。下面我们就List的可变性这个角度去分析以上List创建方法的特性以及区别。

ArrayList与asList

在java中常见的创建List有两种方式,一种是new ArrayList() , 另一种是ArrayList.asList()

ArrayList
var list1 = new ArrayList<String>();
list1.add("4"); // 正常
list1.set(1, "5"); // 正常

两者返回值都是名为ArrayList的类,但是同名不同类,且两者都继承自AbstractList<E>类。可以简单看下AbstractList类的代码,可以知道它的add和remove 方法都未实现只是抛出异常。new ArrayList实现了父类AbstractList的add和remove方法,所以调用不会有异常。但是Arrays.asList却没有重写add和remove方法,因此调用会有异常!

// AbstractList类中
public void add(int index, E element) {
    throw new UnsupportedOperationException();
}

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

上面提到的Arrays.asList是不允许add和remove,但是却可以调用set方法。下面我们来看看连set方法都不能调用的List.of

List.of的强不可变性

有一个List的接口,在java9中引入了一些有用的static方法,用于创建不可变的List。

static <E> List<E> of(E e1) {
    return new ImmutableCollections.List12<>(e1);
}

@SafeVarargs
@SuppressWarnings("varargs")
static <E> List<E> of(E... elements) {
    switch (elements.length) {
        case 0:
            @SuppressWarnings("unchecked")
            var list = (List<E>) ImmutableCollections.EMPTY_LIST;
            return list;
        case 1:
            return new ImmutableCollections.List12<>(elements[0]);
        case 2:
            return new ImmutableCollections.List12<>(elements[0], elements[1]);
        default:
            return ImmutableCollections.listFromArray(elements);
    }
}

















 


我们直接查看ImmutableCollections.listFromArray方法的代码,如下

static <E> List<E> listFromArray(E... input) {
    // copy and check manually to avoid TOCTOU
    @SuppressWarnings("unchecked")
    E[] tmp = (E[])new Object[input.length]; // implicit nullcheck of input
    for (int i = 0; i < input.length; i++) {
        tmp[i] = Objects.requireNonNull(input[i]);
    }
    return new ListN<>(tmp, false);
}







 

最后把元素数组传递给一个名为ListN的类,这个类继承自AbstractImmutableList,因AbstractImmutableList类中add和set等方法都没有实现且抛出异常,所以由List.of创建出来的列表是不可变的,甚至于不能调用set方法。

static abstract class AbstractImmutableList<E> extends AbstractImmutableCollection<E> implements List<E>, RandomAccess {
    // all mutating methods throw UnsupportedOperationException
    @Override public void    add(int index, E element) { throw uoe(); }
    @Override public boolean addAll(int index, Collection<? extends E> c) { throw uoe(); }
    @Override public E       remove(int index) { throw uoe(); }
    @Override public void    replaceAll(UnaryOperator<E> operator) { throw uoe(); }
    @Override public E       set(int index, E element) { throw uoe(); }
    @Override public void    sort(Comparator<? super E> c) { throw uoe(); }
}

上面讲述的是不可变列表,它接收的参数是可变长度的元素。下面介绍如何将可变列表转换成不可变的列表

copyOf与unmodifiableList

copyOf
var strings = new ArrayList<>();
strings.add("12");

var list2 = List.copyOf(strings);
list2.add("12"); // UnsupportedOperationException
list2.set(1, "23"); // UnsupportedOperationException
strings.add("23");
System.out.println(list2); // [12]

对比上述结果,可以看到两种方式都把可变列表转换成不可变的列表,也就是说新的列表不能使用add或者set等等可以修改列表的方法。但是有所区别的是,List.copyOf即使是原string列表做了修改,新的列表也没有影响。而对于unmodifiableList则不然,它随着旧的列表改变而改变。下面看下各自的源码,分析下原因:

  • 在unmodifiableList的方法中
public static <T> List<T> unmodifiableList(List<? extends T> list) {
    if (list.getClass() == UnmodifiableList.class || list.getClass() == UnmodifiableRandomAccessList.class) {
       return (List<T>) list;
    }

    return (list instanceof RandomAccess ?
            new UnmodifiableRandomAccessList<>(list) :
            new UnmodifiableList<>(list));
}

// 其中add, addAll, clear, remove, removeIf, removeAll, retainAll方法皆是抛出异常
  • 在copyOf方法中
static <E> List<E> copyOf(Collection<? extends E> coll) {
    return ImmutableCollections.listCopy(coll);
}

static <E> List<E> listCopy(Collection<? extends E> coll) {
    if (coll instanceof List12 || (coll instanceof ListN && ! ((ListN<?>)coll).allowNulls)) {
        return (List<E>)coll;
    } else {
        return (List<E>)List.of(coll.toArray()); // implicit nullcheck of coll
    }
}

通过对比可以得知:

  • UnmodifiableRandomAccessList是继承自UnmodifiableList的,而在UnmodifiableList中维护了一个参数传递过来的List引用。 因为是引用,所以原列表的修改会反映到新的列表上面来。
  • UnmodifiableList中的add和remove等可以修改列表的方法,皆是抛出异常,这就是新列表不可变的秘密所在
  • List.copyOf方法可以看到,最终的List对象还是交由List.of处理的。而在分析List.of方法时, 我们知道它是将列表参数放到一个新的数组里面传递给不可变List里面的,而不是原列表的引用。这也是上述原列表的修改没有反映到新的列表上的原因

Tips

虽说List.of是将列表参数放到一个新的数组而不是引用传递到不可变List,但是如果列表类型是Person对象这种的,修改Person里面的属性,还是会映射到新列表上面的。所以说List.copyOf只是浅拷贝

下面给出一个示例来加以说明List.copyOf方法的特性:

var books = new ArrayList<Book>();
var book = new Book(1L, "Thinking in java");
books.add(book);

var list2 = List.copyOf(books);
book.setTitle("Kotlin in action");
System.out.println(books); // [Book(id=1, title=Kotlin in action)]
System.out.println(list2); // [Book(id=1, title=Kotlin in action)]






 
 

Stream中的List

Java 8开启了java函数式编程的新世界,同时也引入了Stream的流式操作,它提供了一种更便捷的方式来处理集合数据,如过滤、映射、排序等。

  • Java 8引入的Collectors.toList()
var list = List.of("a", "b", "c");
var stringList = list.stream()
    .map(String::toUpperCase)
    .collect(Collectors.toList());
stringList.add("d"); // 正常

System.out.println(list.size()); // 3

Collectors.toList方法其实返回的是new ArrayList(),然后这个list调用了addAll(oldList)方法把数据复制过来。返回之后的List行为和上述new ArrayList()别无二致

  • Java 10中引入的Collectors.toUnmodifiableList()
var stringList = List.of("a", "b", "c").stream()
    .map(String::toUpperCase)
    .collect(Collectors.toUnmodifiableList());
stringList.add("d"); // UnsupportedOperationException




好吧,正如名字所言返回的是toUnmodifiableList,是不可修改的

  • Java 16中Stream引入的toList()

示例代码如下

var stringList = List.of("a", "b", "c").stream()
    .map(String::toUpperCase)
    .toList();
stringList.add("d"); // UnsupportedOperationException

这个方法是Stream接口的默认方法,实现代码如下:

@SuppressWarnings("unchecked")
default List<T> toList() {
    return (List<T>) Collections.unmodifiableList(new ArrayList<>(Arrays.asList(this.toArray())));
}

可以看到本质上和上面分析的Collections.unmodifiableList是一样的,具体参考: unmodifiableList

Tips

如果想Stream返回的是可修改的List,那么可以使用Collectors.toList()。否则如果是Java 16之后,返回不可修改的List,直接上toList方法

FAQ

总结

参考