14

How do you create an Unmodifiable List/Set/Map with Collectors.toList/toSet/toMap, since toList (and the like) are document as :

There are no guarantees on the type, mutability, serializability, or thread-safety of the List returned

Before java-10 you have to provide a Function with Collectors.collectingAndThen, for example:

 List<Integer> result = Arrays.asList(1, 2, 3, 4)
            .stream()
            .collect(Collectors.collectingAndThen(
                    Collectors.toList(),
                    x -> Collections.unmodifiableList(x)));
Naman
  • 27,789
  • 26
  • 218
  • 353
Eugene
  • 117,005
  • 15
  • 201
  • 306
  • In Java 9 you can also write `List.of(Arrays.asList(1, 2, 3, 4).stream().toArray(Integer[]::new));` – ZhekaKozlov Mar 05 '18 at 09:19
  • @ZhekaKozlov right. I am aware of that, internally btw they do the same thing `list -> (List)List.of(list.toArray())`, obviously since you can't create a generic array, they have to cast.. – Eugene Mar 05 '18 at 09:23
  • 2
    @ZhekaKozlov and even in Java 8, you can write `Stream.of(1, 2, 3, 4)` instead of `Arrays.asList(1, 2, 3, 4).stream()` and, of course, in Java 9, you can do it even simpler by writing `List.of(1, 2, 3, 4)` in the first place… – Holger Mar 08 '18 at 09:26
  • @Holger `Arrays.asList(1, 2, 3, 4)` should be read as **some list**. – ZhekaKozlov Mar 10 '18 at 09:19
  • What is the point of `Collectors.unmodifiableList()` in the first place? It guarantees that the list is unmodifiable. But `Collectors.toList()` already *can* be unmodifiable. So instead of continuing to say there's no guarantee with `toList`, why not make `toList` return an unmodifiable list and document it as such? – DodgyCodeException Mar 12 '18 at 08:01
  • 1
    @DodgyCodeException right, there was a comment from Stuart Marks that they *plan* for this, but since it is not documented as such, they probably introduces this one to make it clear. Since `toList` is not documented to return anything specific, it could in future return an `ArrayList` for example – Eugene Mar 12 '18 at 08:03
  • 4
    @DodgyCodeException It’s a pity that `toList()` still returns a mutable list, likely raising the amount of code that wrongly relies on this property. But even if `toList()` now returned an immutable list, it was fundamentally different to `toUnmodifiableList()`, as the former still doesn’t specify the type and properties of the returned list, while the latter makes specific guarantees which the user can rely on. Besides that, the list type returned by `toUnmodifiableList()` will be the same as with `List.of(…)`, disallowing `null`, not only as element, but even as argument to query methods. – Holger Mar 12 '18 at 08:31
  • 3
    And it would be quiet surprising if `toList()` returned a list type with such a special behavior. Being immutable is clearly within the things that a programmer should expect, throwing an NPE on `contains(null)` perhaps not. – Holger Mar 12 '18 at 08:33

4 Answers4

13

With Java 10, this is much easier and a lot more readable:

List<Integer> result = Arrays.asList(1, 2, 3, 4)
            .stream()
            .collect(Collectors.toUnmodifiableList());

Internally, it's the same thing as Collectors.collectingAndThen, but returns an instance of unmodifiable List that was added in Java 9.

Lii
  • 11,553
  • 8
  • 64
  • 88
Eugene
  • 117,005
  • 15
  • 201
  • 306
8

Additionally to clear out a documented difference between the two(collectingAndThen vs toUnmodifiableList) implementations :

The Collectors.toUnmodifiableList would return a Collector that disallows null values and will throw NullPointerException if it is presented with a null value.

static void additionsToCollector() {
    // this works fine unless you try and operate on the null element
    var previous = Stream.of(1, 2, 3, 4, null)
            .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));

    // next up ready to face an NPE
    var current = Stream.of(1, 2, 3, 4, null).collect(Collectors.toUnmodifiableList());
}

and furthermore, that's owing to the fact that the former constructs an instance of Collections.UnmodifiableRandomAccessList while the latter constructs an instance of ImmutableCollections.ListN which adds to the list of attributes brought to the table with static factory methods.

Naman
  • 27,789
  • 26
  • 218
  • 353
  • 2
    right, but that is really a property of `List.of` and the like, still, +1 – Eugene Mar 05 '18 at 10:11
  • 1
    @Eugene Agreed, just thought one should be clear of the difference in implementation. Not exactly the same thing after all ;) – Naman Mar 05 '18 at 10:14
3

Stream#toList

Java 16 adds a method on the Stream interface: toList(). To quote the Javadoc:

The returned List is unmodifiable; calls to any mutator method will always cause UnsupportedOperationException to be thrown.

Not just more convenient than Collectors, this method has some goodies like better performance on parallel streams.

In particular with parallel() -- as it avoids result copying. Benchmark is a few simple ops on 100K elem stream of Long.

Java Mission Control toList performance

For a further reading please go to: http://marxsoftware.blogspot.com/2020/12/jdk16-stream-to-list.html

In resume the link states something like this.

Gotcha: It may be tempting to go into one's code base and use stream.toList() as a drop-in replacement for stream.collect(Collectors.toList()), but there may be differences in behavior if the code has a direct or indirect dependency on the implementation of stream.collect(Collectors.toList()) returning an ArrayList. Some of the key differences between the List returned by stream.collect(Collectors.toList()) and stream.toList() are spelled out in the remainder of this post.

The Javadoc-based documentation for Collectors.toList() states (emphasis added), "Returns a Collector that accumulates the input elements into a new List. There are no guarantees on the type, mutability, serializability, or thread-safety of the List returned..." Although there are no guarantees regarding the "type, mutability, serializability, or thread-safety" on the List provided by Collectors.toList(), it is expected that some may have realized it's currently an ArrayList and have used it in ways that depend on the characteristics of an ArrayList

What i understand is that the Stream.toList() it will result in a inmutable List.

Stream.toList() provides a List implementation that is immutable (type ImmutableCollections.ListN that cannot be added to or sorted) similar to that provided by List.of() and in contrast to the mutable (can be changed and sorted) ArrayList provided by Stream.collect(Collectors.toList()). Any existing code depending on the ability to mutate the ArrayList returned by Stream.collect(Collectors.toList()) will not work with Stream.toList() and an UnsupportedOperationException will be thrown.

Although the implementation nature of the Lists returned by Stream.collect(Collectors.toList()) and Stream.toList() are very different, they still both implement the List interface and so they are considered equal when compared using List.equals(Object)

And this method will allow nulls so starting from Java 16 we will have a

mutable/null-friendly----->Collectors.toList()
immutable/null-friendly--->Stream.toList()
immutable/null-hostile---->Collectors.toUnmodifiableList() //Naughty 

Twitter conversation It's great.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
chiperortiz
  • 4,751
  • 9
  • 45
  • 79
  • there is [much broader discussion here](https://stackoverflow.com/questions/65741773/would-stream-tolist-perform-better-than-collectors-tolist) about this. Also check your facts about `null` – Eugene Jan 19 '21 at 12:33
  • which facts? mate? – chiperortiz Jan 19 '21 at 15:54
  • my bad. I should have not posted a comment at 2 AM. What I really wanted to say is the quote you made : _Stream.toList() provides a List implementation that is immutable (type ImmutableCollections.ListN..._ is wrong. If it would have been right, `Stream::toList` could have not accepted nulls, and this is not the case. – Eugene Jan 20 '21 at 03:17
  • 1
    @Eugene it seems, it *is* the same `List` implementation, but now, it has an internal flag whether to allow `null` or not. – Holger Jan 20 '21 at 08:03
  • Exactly @Holger they are the same than ListN implementation but just with a flag indeed the comments i put are from the guys from Oracle. – chiperortiz Jan 20 '21 at 10:17
  • Stream.toList() will allow nulls this conversation i had it with some guys from Open JDK by twitter :) – chiperortiz Jan 20 '21 at 10:18
0

List/Set/Map.copyOf

You asked:

How do you create an Unmodifiable List/Set/Map

As of Java 10, simply pass your existing list/set/map to:

These static methods return an unmodifiable List, unmodifiable Set, or unmodifiable Map, respectively. Read the details on those linked Javadoc pages.

  • No nulls allowed.
  • If the passed collection is already unmodifiable, that passed collection is simply returned, no further work, no new collection.

Note: If using the convenient Stream#toList method in Java 16+ as described in this other Answer, there is no point to this solution here, no need to call List.copyOf. The result of toList is already unmodifiable.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154