Collectors.toUnmodifiableList in java-10
Asked Answered
M

4

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)));
Megaera answered 5/3, 2018 at 9:6 Comment(8)
In Java 9 you can also write List.of(Arrays.asList(1, 2, 3, 4).stream().toArray(Integer[]::new));Teasley
@Teasley right. I am aware of that, internally btw they do the same thing list -> (List<T>)List.of(list.toArray()), obviously since you can't create a generic array, they have to cast..Megaera
@Teasley 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…Overbite
@Overbite Arrays.asList(1, 2, 3, 4) should be read as some list.Teasley
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?Cockatrice
@Cockatrice 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 exampleMegaera
@Cockatrice 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.Overbite
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.Overbite
M
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.

Megaera answered 5/3, 2018 at 9:6 Comment(0)
H
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.

Hyperostosis answered 5/3, 2018 at 10:10 Comment(2)
right, but that is really a property of List.of and the like, still, +1Megaera
@Megaera Agreed, just thought one should be clear of the difference in implementation. Not exactly the same thing after all ;)Hyperostosis
A
4

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.

Anodic answered 19/1, 2021 at 11:34 Comment(6)
there is much broader discussion here about this. Also check your facts about nullMegaera
which facts? mate?Anodic
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.Megaera
@Megaera it seems, it is the same List implementation, but now, it has an internal flag whether to allow null or not.Overbite
Exactly @Overbite they are the same than ListN implementation but just with a flag indeed the comments i put are from the guys from Oracle.Anodic
Stream.toList() will allow nulls this conversation i had it with some guys from Open JDK by twitter :)Anodic
T
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.

Thayne answered 19/8, 2021 at 22:3 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.