Reputation: 431
I need an immutable list where I can get derive a second immutable list preserving all elements of the previous list plus an additional element in Java (without additional libraries).
Note: This question is similar to What is an efficient and elegant way to add a single element to an immutable set? but I need a list and don't have Guava.
What I have tried so far:
var list = List.of(someArrayOfInitialElements);
var newList = Stream.concat(list.stream(), Stream.of(elementToAppend))
.collect(CollectorsCollectors.toUnmodifiableList());
That would work but creating a stream and copying elements one by one seems inefficient to me. You could basically bulk copy memory given that List.of()
stores data in a field-based or array-based data structure.
Is there a more efficient solution than using streams? A better data structure in the Java standard library that I am missing?
Upvotes: 6
Views: 18993
Reputation: 3484
I came across this question while looking for an answer to the same problem, and didn't find anything very good. Here is the best I could come up with:
// starting out with:
var list = List.of("item1", "item2", "item3");
String elementToAppend = "item4";
// copy once into array exactly sized for the additional element
int size = list.size();
String[] array = list.toArray(new String[size + 1]);
array[size] = elementToAppend;
// wrap as unmodifiable:
var newList = Collections.unmodifiableList(Arrays.asList(array));
Arrays.asList
is chosen because it reuses the single array that is created for this, although it unfortunately does so as a modifiable list, so an extra wrap with unmodifiable is needed.
I would have used one of the other solutions, but it seems like new ArrayList(list)
followed by add
could cause a resize.
Upvotes: 0
Reputation: 20039
Both answers are great, I would create a bit more generic solution:
private static <T> List<T> append(final List<T> al, final T... ts) {
final List<T> bl = new ArrayList<>(al);
for (final T t : ts) {
bl.add(t);
}
return List.copyOf(bl);
}
It can be used exactly like previous answer:
List<String> al = append(new ArrayList<>(), "1");
List<String> bl = append(al, "2");
System.out.println(bl);
But also slightly more efficient:
List<String> bl = append(new ArrayList<>(), "1", "2");
System.out.println(bl);
Upvotes: 1
Reputation: 338775
The Answer by Frisch is correct, and should be accepted. One further note…
Calling Collections.unmodifiableList
produces a collection that is a view onto the original mutable list. So a modification to the original list will "bleed through" to the not-so-immutable second list.
This issue does not apply to the correct code shown in that Answer, because the new ArrayList
object deliberately goes out-of-scope. Therefore that new list cannot be accessed for modification. But in other coding scenarios, this issue could be a concern.
List.copyOf
If you want an independent and truly immutable second list, use List.copyOf
in Java 10+. This returns an unmodifiable list.
return List.copyOf( bl ) ;
Upvotes: 5
Reputation: 201447
I would create a new ArrayList
append the element and then return that as an unmodifiable list. Something like,
private static <T> List<T> appendOne(List<T> al, T t) {
List<T> bl = new ArrayList<>(al);
bl.add(t);
return Collections.unmodifiableList(bl);
}
And to test it
public static void main(String[] args) {
List<String> al = appendOne(new ArrayList<>(), "1");
List<String> bl = appendOne(al, "2");
System.out.println(bl);
}
I get (unsurprisingly):
[1, 2]
See this code run at IdeOne.com.
Upvotes: 10