Initialization of an ArrayList in one line
Initialization of an ArrayList in one line
Question
I wanted to create a list of options for testing purposes. At first, I did this:
ArrayList<String> places = new ArrayList<String>();
places.add("Buenos Aires");
places.add("Córdoba");
places.add("La Plata");
Then, I refactored the code as follows:
ArrayList<String> places = new ArrayList<String>(
Arrays.asList("Buenos Aires", "Córdoba", "La Plata"));
Is there a better way to do this?
Accepted Answer
Actually, probably the "best" way to initialize the ArrayList
is the method you wrote, as it does not need to create a new List
in any way:
ArrayList<String> list = new ArrayList<String>();
list.add("A");
list.add("B");
list.add("C");
The catch is that there is quite a bit of typing required to refer to that list
instance.
There are alternatives, such as making an anonymous inner class with an instance initializer (also known as an "double brace initialization"):
ArrayList<String> list = new ArrayList<String>() {{
add("A");
add("B");
add("C");
}};
However, I'm not too fond of that method because what you end up with is a subclass of ArrayList
which has an instance initializer, and that class is created just to create one object -- that just seems like a little bit overkill to me.
What would have been nice was if the Collection Literals proposal for Project Coin was accepted (it was slated to be introduced in Java 7, but it's not likely to be part of Java 8 either.):
List<String> list = ["A", "B", "C"];
Unfortunately it won't help you here, as it will initialize an immutable List
rather than an ArrayList
, and furthermore, it's not available yet, if it ever will be.
Popular Answer
It would be simpler if you were to just declare it as a List
- does it have to be an ArrayList?
List<String> places = Arrays.asList("Buenos Aires", "Córdoba", "La Plata");
Or if you have only one element:
List<String> places = Collections.singletonList("Buenos Aires");
This would mean that places
is immutable (trying to change it will cause an UnsupportedOperationException
exception to be thrown).
To make a mutable list that is a concrete ArrayList
you can create an ArrayList
from the immutable list:
ArrayList<String> places = new ArrayList<>(Arrays.asList("Buenos Aires", "Córdoba", "La Plata"));
Read more… Read less…
The simple answer
In Java 9 or later, after List.of()
was added:
List<String> strings = List.of("foo", "bar", "baz");
With Java 10 or later, this can be shortened with the var
keyword.
var strings = List.of("foo", "bar", "baz");
This will give you an immutable List
, so it cannot be changed.
Which is what you want in most cases where you're prepopulating it.
Java 8 or earlier:
List<String> strings = Arrays.asList("foo", "bar", "baz");
This will give you a List
backed by an array, so it cannot change length.
But you can call List.set
, so it's still mutable.
You can make Arrays.asList
even shorter with a static import:
List<String> strings = asList("foo", "bar", "baz");
The static import:
import static java.util.Arrays.asList;
Which any modern IDE will suggest and automatically do for you.
For example in IntelliJ IDEA you press Alt+Enter
and select Static import method...
.
However, i don't recommend shortening the List.of
method to of
, because that becomes confusing.
List.of
is already short enough and reads well.
Using Stream
s
Why does it have to be a List
?
With Java 8 or later you can use a Stream
which is more flexible:
Stream<String> strings = Stream.of("foo", "bar", "baz");
You can concatenate Stream
s:
Stream<String> strings = Stream.concat(Stream.of("foo", "bar"),
Stream.of("baz", "qux"));
Or you can go from a Stream
to a List
:
import static java.util.stream.Collectors.toList;
List<String> strings = Stream.of("foo", "bar", "baz").collect(toList());
But preferably, just use the Stream
without collecting it to a List
.
If you really specifically need a java.util.ArrayList
(You probably don't.)
To quote JEP 269 (emphasis mine):
There is a small set of use cases for initializing a mutable collection instance with a predefined set of values. It's usually preferable to have those predefined values be in an immutable collection, and then to initialize the mutable collection via a copy constructor.
If you want to both prepopulate an ArrayList
and add to it afterwards (why?), use
ArrayList<String> strings = new ArrayList<>(List.of("foo", "bar"));
strings.add("baz");
or in Java 8 or earlier:
ArrayList<String> strings = new ArrayList<>(asList("foo", "bar"));
strings.add("baz");
or using Stream
:
import static java.util.stream.Collectors.toCollection;
ArrayList<String> strings = Stream.of("foo", "bar")
.collect(toCollection(ArrayList::new));
strings.add("baz");
But again, it's better to just use the Stream
directly instead of collecting it to a List
.
Program to interfaces, not to implementations
You said you've declared the list as an ArrayList
in your code, but you should only do that if you're using some member of ArrayList
that's not in List
.
Which you are most likely not doing.
Usually you should just declare variables by the most general interface that you are going to use (e.g. Iterable
, Collection
, or List
), and initialize them with the specific implementation (e.g. ArrayList
, LinkedList
or Arrays.asList()
).
Otherwise you're limiting your code to that specific type, and it'll be harder to change when you want to.
For example, if you're passing an ArrayList
to a void method(...)
:
// Iterable if you just need iteration, for (String s : strings):
void method(Iterable<String> strings) {
for (String s : strings) { ... }
}
// Collection if you also need .size(), .isEmpty(), or .stream():
void method(Collection<String> strings) {
if (!strings.isEmpty()) { strings.stream()... }
}
// List if you also need .get(index):
void method(List<String> strings) {
strings.get(...)
}
// Don't declare a specific list implementation
// unless you're sure you need it:
void method(ArrayList<String> strings) {
??? // You don't want to limit yourself to just ArrayList
}
Another example would be always declaring variable an InputStream
even though it is usually a FileInputStream
or a BufferedInputStream
, because one day soon you or somebody else will want to use some other kind of InputStream
.
If you need a simple list of size 1:
List<String> strings = new ArrayList<String>(Collections.singletonList("A"));
If you need a list of several objects:
List<String> strings = new ArrayList<String>();
Collections.addAll(strings,"A","B","C","D");
With java-9 and above, as suggested in JEP 269: Convenience Factory Methods for Collections, this could be achieved using collection literals now with -
List<String> list = List.of("A", "B", "C");
Set<String> set = Set.of("A", "B", "C");
A similar approach would apply to Map
as well -
Map<String, String> map = Map.of("k1", "v1", "k2", "v2", "k3", "v3")
which is similar to Collection Literals proposal as stated by @coobird. Further clarified in the JEP as well -
Alternatives
Language changes have been considered several times, and rejected:
Project Coin Proposal, 29 March 2009
Project Coin Proposal, 30 March 2009
JEP 186 discussion on lambda-dev, January-March 2014
The language proposals were set aside in preference to a library-based proposal as summarized in this message.
Related: What is the point of overloaded Convenience Factory Methods for Collections in Java 9
Collection literals didn't make it into Java 8, but it is possible to use the Stream API to initialize a list in one rather long line:
List<String> places = Stream.of("Buenos Aires", "Córdoba", "La Plata").collect(Collectors.toList());
If you need to ensure that your List
is an ArrayList
:
ArrayList<String> places = Stream.of("Buenos Aires", "Córdoba", "La Plata").collect(Collectors.toCollection(ArrayList::new));