276

Java 9 introduced new factory methods for lists, List.of:

List<String> strings = List.of("first", "second");

What's the difference between the previous and the new option? That is, what's the difference between this:

Arrays.asList(1, 2, 3);

and this:

List.of(1, 2, 3);
ZhekaKozlov
  • 36,558
  • 20
  • 126
  • 155
  • 2
    See also this [talk](https://www.youtube.com/watch?v=ogRVWXuuAU4) by Stuart "Beaker" Marks. – user1803551 Oct 05 '17 at 11:40
  • 21
    @user1803551 Though I understand your frustration, this reasoning might set a really unwanted precedent. *A lot* of questions here have an answer that's 'clearly stated' (depending on how one defines this). I'd urge you to bring this discussion to meta but I'm pretty sure such a discussion should already exist (and I'm hoping someone can find it and link it :-) – Dimitris Fasarakis Hilliard Oct 05 '17 at 13:23
  • 4
    @user1803551 Javadocs do not mention the difference between implementation details of these two methods (like space consumption or performance). I think people would like to know these details too. – ZhekaKozlov Oct 05 '17 at 13:33
  • 5
    @ZhekaKozlov The accepted and super-upvoted answer doesn't either. What does that tell you about the accepted standards? It even has *less* information than in the docs (serialization, identity, ordering). If anything, file a request to OpenJDK to add that info. – user1803551 Oct 06 '17 at 03:20
  • @user1803551 This is because my answer is not ideal. But there is the other answer who mentions this topic. – ZhekaKozlov Oct 06 '17 at 03:56
  • 3
    This question is being discussed on [meta](https://meta.stackoverflow.com/questions/357495/require-clarification-on-the-amount-of-research-a-question-needs-in-order-not-to). – Dimitris Fasarakis Hilliard Oct 06 '17 at 07:15
  • 1
    @ZhekaKozlov "Javadocs do not mention the difference between implementation details of these two methods" wait, what? Javadocs includes the details for both, don't they? It's simply having both side by side and compare. – Braiam Oct 07 '17 at 11:11
  • related question: https://stackoverflow.com/questions/13395114/how-to-initialize-liststring-object-in-java – Ahmed Nabil Nov 18 '21 at 15:13

5 Answers5

400

Arrays.asList returns a mutable list while the list returned by List.of is immutable:

List<Integer> list = Arrays.asList(1, 2, null);
list.set(1, 10); // OK

List<Integer> list = List.of(1, 2, 3);
list.set(1, 10); // Fails with UnsupportedOperationException

Arrays.asList allows null elements while List.of doesn't:

List<Integer> list = Arrays.asList(1, 2, null); // OK
List<Integer> list = List.of(1, 2, null); // Fails with NullPointerException

contains behaves differently with nulls:

List<Integer> list = Arrays.asList(1, 2, 3);
list.contains(null); // Returns false

List<Integer> list = List.of(1, 2, 3);
list.contains(null); // Fails with NullPointerException

Arrays.asList returns a view of the passed array, so the changes to the array will be reflected in the list too. For List.of this is not true:

Integer[] array = {1,2,3};
List<Integer> list = Arrays.asList(array);
array[1] = 10;
System.out.println(list); // Prints [1, 10, 3]

Integer[] array = {1,2,3};
List<Integer> list = List.of(array);
array[1] = 10;
System.out.println(list); // Prints [1, 2, 3]
Matthias Braun
  • 32,039
  • 22
  • 142
  • 171
ZhekaKozlov
  • 36,558
  • 20
  • 126
  • 155
  • Thanks a lot for the answers... It really helped.. I have a small clarification in the last point regarding the view of the passed array.. Which is the better approach? First one or second one and how it is happening? –  Oct 05 '17 at 06:56
  • 39
    For a list to behave differently based on how it's constructed doesn't seem very object oriented to me. Maybe if List.of returned an ImmutableList type, this would make sense. This is a very leaky abstraction here. – Sandy Chapman Oct 05 '17 at 11:22
  • 1
    @SandyChapman That's a weird way to look at it - do you think the same thing about methods returning streams (either the Java 8 kind of input/output streams)? – Nicolai Parlog Oct 05 '17 at 11:46
  • 5
    I'm not a Java developer, so take it as a casual observation. There's possibly a good reason for the behavior to differ, but if I had a method returning a List like the example, the interface wouldn't be sufficient for me to know if I'll get a runtime exception if I check it for nulls. Likewise, a change in that methods implementation could affect code distant from the call site of my method if that check happens elsewhere. @Nicolai – Sandy Chapman Oct 05 '17 at 11:52
  • 12
    @SandyChapman this might be unexpected behaviour to some (or most?), but it is documented behaviour. From the [`List.contains(Object o)`'s javadoc](https://docs.oracle.com/javase/8/docs/api/java/util/List.html#contains-java.lang.Object-) : "Throws [...] NullPointerException - if the specified element is null and this list does not permit null elements (optional)". Or from the interface's lenghty introduction that few read : "Some collection implementations have restrictions on the elements that they may contain" – Aaron Oct 05 '17 at 14:04
  • 15
    @Aaron well at least it's a well documented leaky abstraction :) – Sandy Chapman Oct 05 '17 at 18:26
  • 6
    @Sandy Chapman: `List.of` *does* return some `ImmutableList` type, its actual name is just a non-public implementation detail. If it was public and someone cast it to `List` again, where was the difference? Where is the difference to `Arrays.asList`, which returns a non-public `List` implementation, that throws an exception when attempting `add` or `remove`, or the list returned by `Collections.unmodifiableList` which allows no modification at all? It’s all about contracts specified in the `List` interface. The Collections interfaces with optional methods always were impure OOP since Java 1.2… – Holger Oct 06 '17 at 06:32
  • 4
    @Holger I believe you're both talking about the same issue. Namely, that `java.util.Collection` and its descendants have too many responsibilities. Purer API (e.g. Eclipse Collections) would offer fine-grained interfaces for immutable collections. Now, I agree that this is quite an old problem, but it manifests itself again even in new methods. – default locale Oct 06 '17 at 09:01
  • Is List an interface in Java? Is this why it makes sense to some of you? – ZivS Oct 10 '17 at 23:03
  • 1
    Actually the last example is a little unfortunate and might be misleading since Integer is immutable. Let's rewrite it: class Person {String name; public Person(String name) {this.name = name} }. Person john = new Person("John"); List list = List.of(john); Now if I change the name: john.setName("Peter"); list.get(0).name returns "Peter". – pzeszko Oct 04 '18 at 15:51
  • @SandyChapman the Java collections API has worked like that since it was created. Any `Collection` implementation you are given may turn out to be immutable, with no way to tell in advance. – OrangeDog Sep 18 '19 at 12:31
  • 2
    Arrays.asList() is semi-mutable. As already noted the returned List is a view of the passed array argument. Yes changes pass though. However another consequence is that the returned List cannot dynamically increase or decrease its size as one would expect from that interface. You cannot add new elements to it. – Dimitrios Menounos May 12 '21 at 14:17
62

The differences between Arrays.asList and List.of

See the JavaDocs and this talk by Stuart Marks (or previous versions of it).

I'll be using the following for the code examples:

List<Integer> listOf = List.of(...);
List<Integer> asList = Arrays.asList(...);
List<Integer> unmodif = Collections.unmodifiableList(asList);

Structural immutability (Or: unmodifiability)

Any attempt to structurally change List.of will result in an UnsupportedOperationException. That includes operations such as add, set and remove. You can, however, change the contents of the objects in the list (if the objects are not immutable), so the list is not "completely immutable".

This is the same fate for unmodifiable lists created with Collections.unmodifiableList. Only this list is a view of the original list, so it can change if you change the original list.

Arrays.asList is not completely immutable, it does not have a restriction on set.

listOf.set(1, "a");  // UnsupportedOperationException
unmodif.set(1, "a"); // UnsupportedOperationException
asList.set(1, "a");  // modified unmodif! unmodif is not truly unmodifiable

Similarly, changing the backing array (if you hold it) will change the list.

Structural immutability comes with many side-effects related to defensive coding, concurrency and security which are beyond the scope of this answer.

Null hostility

List.of and any collection since Java 1.5 do not allow null as an element. Attempting to pass null as an element or even a lookup will result in a NullPointerException.

Since Arrays.asList is a collection from 1.2 (the Collections Framework), it allows nulls.

listOf.contains(null);  // NullPointerException
unmodif.contains(null); // allowed
asList.contains(null);  // allowed

Serialized form

Since List.of has been introduced in Java 9 and the lists created by this method have their own (binary) serialized form, they cannot be deserialized on earlier JDK versions (no binary compatibility). However, you can de/serialize with JSON, for example.

Identity

Arrays.asList internally calls new ArrayList, which guarantees reference inequality.

List.of depends on internal implementation. The instances returned can have reference equality, but since this is not guaranteed you can not rely on it.

asList1 == asList2; // false
listOf1 == listOf2; // true or false

Worth mentioning that lists are equal (via List.equals) if they contain the same elements in the same order, regardless of how they were created or what operations they support.

asList.equals(listOf); // true i.f.f. same elements in same order

Implementation (warning: details can change over versions)

If the number of elements in the list of List.of is 2 or less, the elements are stored in fields of a specialized (internal) class. An example is the list that stores 2 elements (partial source):

static final class List2<E> extends AbstractImmutableList<E> {
    private final E e0;
    private final E e1;

    List2(E e0, E e1) {
        this.e0 = Objects.requireNonNull(e0);
        this.e1 = Objects.requireNonNull(e1);
    }
}

Otherwise they are stored in an array in a similar fashion to Arrays.asList.

Time and Space efficiency

The List.of implementations which are field-based (size<2) perform slightly faster on some operations. As examples, size() can return a constant without fetching the array length, and contains(E e) does not require iteration overhead.

Constructing an unmodifiable list via List.of is also faster. Compare the above constructor with 2 reference assignments (and even the one for arbitrary amount of elements) to

Collections.unmodifiableList(Arrays.asList(...));

which creates 2 lists plus other overhead. In terms of space, you save the UnmodifiableList wrapper plus some pennies. Ultimately, the savings in the HashSet equivalent are more convincing.


Conclusion time: use List.of when you want a list that doesn't change and Arrays.asList when you want a list that can change (as shown above).

user1803551
  • 12,965
  • 5
  • 47
  • 74
  • 2
    For people wondering why this answer exists, see [this](https://meta.stackoverflow.com/a/357505/1803551). – user1803551 Oct 06 '17 at 14:36
  • 6
    `Arrays.asList` is not fully mutable. `asList.add(1);` throws an `UnsupportedOperationException`. – mapeters Oct 06 '17 at 20:44
  • "Null hostile" is a great way to put it. I pretty much can't use `List.of` any time people might want to call `contains` and not be surprised by a NullPointerException. – Noumenon Feb 26 '20 at 06:08
37

Apart from the above answers there are certain operations on which both List::of and Arrays::asList differ:

+----------------------+---------------+----------+----------------+---------------------+
|      Operations      | SINGLETONLIST | LIST::OF | ARRAYS::ASLIST | JAVA.UTIL.ARRAYLIST |
+----------------------+---------------+----------+----------------+---------------------+
|          add         |       ❌      |     ❌  |        ❌      |          ✔️          |
+----------------------+---------------+----------+----------------+---------------------+
|        addAll        |       ❌      |     ❌  |        ❌      |          ✔️          |
+----------------------+---------------+----------+----------------+---------------------+
|         clear        |       ❌      |     ❌  |        ❌      |          ✔️          |
+----------------------+---------------+----------+----------------+---------------------+
|        remove        |       ❌      |     ❌  |        ❌      |          ✔️          |
+----------------------+---------------+----------+----------------+---------------------+
|       removeAll      |       ❗️       |     ❌   |        ❗️       |          ✔️          |
+----------------------+---------------+----------+----------------+---------------------+
|       retainAll      |       ❗️       |     ❌  |        ❗️        |          ✔️          |
+----------------------+---------------+----------+----------------+---------------------+
|      replaceAll      |       ❌      |     ❌  |        ✔️       |          ✔️          |
+----------------------+---------------+----------+----------------+---------------------+
|          set         |       ❌      |     ❌  |        ✔️       |          ✔️          |
+----------------------+---------------+----------+----------------+---------------------+
|         sort         |       ✔️       |     ❌   |        ✔️      |          ✔️          |
+----------------------+---------------+----------+----------------+---------------------+
|  remove on iterator  |       ❌      |     ❌  |        ❌      |          ✔️          |
+----------------------+---------------+----------+----------------+---------------------+
| set on list-iterator |       ❌      |     ❌  |        ✔️       |          ✔️          |
+----------------------+---------------+----------+----------------+---------------------+
  1. ✔️ means the method is supported
  2. ❌ means that calling this method will throw an UnsupportedOperationException
  3. ❗️ means the method is supported only if the method's arguments do not cause a mutation, e.g. Collections.singletonList("foo").retainAll("foo") is OK but Collections.singletonList("foo").retainAll("bar")throws an UnsupportedOperationException

More about Collections::singletonList Vs. List::of

user1803551
  • 12,965
  • 5
  • 47
  • 74
Vishwa Ratna
  • 5,567
  • 5
  • 33
  • 55
30

Let summarize the differences between List.of and Arrays.asList

  1. List.of can be best used when data set is less and unchanged, while Arrays.asList can be used best in case of large and dynamic data set.

  2. List.of take very less overhead space because it has field-based implementation and consume less heap space, both in terms of fixed overhead and on a per-element basis. while Arrays.asList take more overhead space because while initialization it creates more objects in heap.

  3. Collection returned by List.of is immutable and hence thread-safe while Collection returned by Arrays.asList is mutable and not thread safe. (Immutable collection instances generally consume much less memory than their mutable counterparts.)

  4. List.of doesn't allow null elements while Arrays.asList allows null elements.

Mohit Tyagi
  • 2,788
  • 4
  • 17
  • 29
  • 2
    "Immutable collection instances generally consume much less memory than their mutable counterparts." - Really? Would you care to elaborate a bit on that - do you mean because they can be shared safely, or do you mean that the instances themselfes can be implemented more efficiently somehow? – Hulk Oct 05 '17 at 09:15
  • 2
    @Hulk The answerer is right about the space efficiency. See Stuart Marks' talk: https://youtu.be/q6zF3vf114M?t=49m48s – ZhekaKozlov Oct 05 '17 at 09:34
  • 3
    @ZhekaKozlov That appears to be true in general, but I'm very skeptical that it's true when talking about `Arrays.asList` versus `List.of`, given that the former is literally just a wrapper around an array. At least [OpenJDK's implementation](http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/util/Arrays.java#3361) appears to have extremely small overhead. In fact, `List.of` would need to make copies of any array passed in, so unless the array itself is going to be GC'd soon, it would seem like `List.of` has a significantly larger memory footprint. – Chris Hayes Oct 05 '17 at 09:40
  • @ZhekaKozlov thanks for that link, very interesting talk indeed. I fully agree that this is true for the immutable implementations of `HashSet` and `HashMap` that are mentioned there (because they don't need to provide capacity to enable growth, etc.). I was more sceptical regarding lists, especially compared to the fixed sized lists here. – Hulk Oct 05 '17 at 09:50
  • 2
    Still, I agree - I had not considered that when comparing to mutable lists in general, the statement holds true because they don't need capacity for growth. – Hulk Oct 05 '17 at 09:56
  • 6
    @ChrisHayes At least `List.of(x)` and `List.of(x, y)` are more efficient because they do not allocate arrays at all – ZhekaKozlov Oct 05 '17 at 10:20
  • 3
    @Hulk: don’t forget that the `List.of` methods are not required to return new lists each time. These lists have an unspecified identity, so there could be caching or deduplication or scalarization handled on the JVM level. If not in this version, then perhaps in the next. It’s allowed by the contract. In contrast, `Array.asList` depends on the identity of the array you’re passing in, as the resulting list is mutable view on the array, reflecting all changing bidirectionally. – Holger Oct 06 '17 at 06:39
  • @Holger indeed - the unspecified identity is a bullet point that might be worth adding to this answer. – Hulk Oct 06 '17 at 07:05
  • Lists returned by `Arrays.asList` are not fully mutable, as they are fixed-size. – mapeters Oct 06 '17 at 20:54
8

Arrays.asList(1, 2, 3);

A fixed-sized List will be create:

public static void main(String[] args) {
        List<Integer> asList = Arrays.asList(1, 2, 3, 4, 5);
        asList.add(6);    // java.lang.UnsupportedOperationException
        asList.remove(0); // java.lang.UnsupportedOperationException
        asList.set(0, 0); // allowed
}

List.of(1, 2, 3);

A Immutable(Java 9)/Unmodifiable(Java 11) List will be create:

public static void main(String[] args) {
    List<Integer> listOf = List.of(1, 2, 3, 4, 5);
    listOf.add(6);    // java.lang.UnsupportedOperationException
    listOf.remove(0); // java.lang.UnsupportedOperationException
    listOf.set(0, 0); // java.lang.UnsupportedOperationException
}
dwbFrank
  • 115
  • 1
  • 5