An array has a fixed length. If a program reads an unknown number of log entries, you have to choose a capacity in advance or write resizing code. An ArrayList handles resizing for you and provides operations such as add, get, and remove. It is part of the Java Collections Framework, which supplies interfaces, implementations, and utility methods for working with groups of objects.
Different collection types solve different problems. A List keeps a sequence, a Set keeps distinct elements, and a Map associates keys with values. The useful question is not “Which class should I memorize?” but “What must this group of data do?”
Interfaces describe behavior; classes provide it
Collection<E> is a core interface for groups of elements. List<E>, Set<E>, and Queue<E> extend it with more specific behavior. Map<K,V> is also part of the framework, but it is not a subtype of Collection: it manages key–value entries rather than individual elements.
Classes such as ArrayList, HashSet, and HashMap implement those interfaces. The angle brackets specify generic types. For example, List<String> tells the compiler that the list holds strings, so retrieving an element does not require a cast. Collections hold object references, not primitives directly; use a wrapper such as Integer for int values. Java often converts between a primitive and its wrapper automatically, a feature called autoboxing.
List<String> events = new ArrayList<>();
events.add("login accepted");
events.add("session ended");
System.out.println(events.get(0)); // login accepted
Declaring events as a List<String> lets you change its implementation later. Declare it as an ArrayList<String> only when the code needs an ArrayList-specific operation. These types are in java.util; add the appropriate imports to your source file.

Start with the behavior you need
| Need | Common choice | Important behavior |
|---|---|---|
| Ordered sequence with indexed access | ArrayList |
Duplicates allowed; positions start at zero |
| Distinct elements without an ordering guarantee | HashSet |
Duplicate additions do not add another element |
| Distinct elements in sorted order | TreeSet |
Elements follow natural order or a comparator |
| Value lookup by unique key | HashMap |
Putting an existing key replaces its value |
| First-in, first-out processing | ArrayDeque as a Queue |
Add at one end and remove from the other |
These are starting points, not universal winners. An array still makes sense when the size is fixed or an API expects one. When ordering, equality, or null values matter, check the rules of the collection you choose.
Lists: a sequence that can change size
A list preserves positional order and allows duplicates. An ArrayList is backed by an array, so get(index) is fast. Adding at the end is typically efficient; inserting or removing near the beginning shifts later elements. An invalid index throws IndexOutOfBoundsException.
List<String> steps = new ArrayList<>();
steps.add("collect");
steps.add("review");
steps.add("review");
steps.set(0, "gather");
System.out.println(steps.size()); // 3
LinkedList is another List implementation. It can help with certain insertion patterns through an iterator, but finding a position may require walking the list. For an ordinary indexed list, start with ArrayList unless measurements or requirements point elsewhere. If you need a queue, use the Queue interface instead of picking a list for the job.
Sets: membership without duplicates
A Set answers questions like “Have we seen this identifier already?” A HashSet typically makes additions and membership checks fast, but its iteration order is unspecified. An order you observe in one run is not a guarantee.
Set<String> labels = new HashSet<>();
System.out.println(labels.add("warning")); // true
System.out.println(labels.add("warning")); // false
System.out.println(labels.contains("warning")); // true
Use LinkedHashSet when insertion order matters. For sorted iteration, TreeSet uses natural ordering or a supplied Comparator; its operations generally cost more than hash-based lookups. A set cannot replace a list if you need positions or repeated values.
Maps: values retrieved through keys
A Map associates each distinct key with one value. In a HashMap, putting a value under an existing key replaces the old value. Maps work well for lookup tables and counts, but you cannot keep two independent entries under the same key.
Map<String, Integer> counts = new HashMap<>();
counts.put("warning", 1);
counts.merge("warning", 1, Integer::sum);
System.out.println(counts.get("warning")); // 2
get returns null if a key has no mapping. It can also return null when the key is explicitly mapped to null, so use containsKey if you need to tell those cases apart. HashMap makes no promise about iteration order. LinkedHashMap preserves insertion order, while TreeMap orders entries by key.
Queues: process items in a deliberate order
A Queue models pending work. With ArrayDeque, offer adds an item, poll removes and returns the head, and peek examines the head without removing it. On an empty queue, poll and peek return null. ArrayDeque does not permit null elements, so that signal is unambiguous.
Queue<String> pending = new ArrayDeque<>();
pending.offer("parse file");
pending.offer("write report");
System.out.println(pending.poll()); // parse file
ArrayDeque also works as a stack through push, pop, and peek. If priority matters more than arrival order, PriorityQueue retrieves elements according to their ordering. Iterating over it, however, does not produce a sorted sequence.
Iteration, sorting, and utility methods
An enhanced for loop works well when you only need to read elements. For a map, iterate over entrySet() if you need both keys and values. To remove elements while traversing a collection, use an explicit iterator and its remove method. Calling the collection’s remove inside an enhanced for loop can cause many standard iterators to detect the structural change and throw ConcurrentModificationException.
List<String> names = new ArrayList<>(
List.of("Ada", "", "Lin"));
Iterator<String> it = names.iterator();
while (it.hasNext()) {
if (it.next().isEmpty()) {
it.remove();
}
}
List.of creates an unmodifiable list, so this example copies it into an ArrayList before removing anything. Calling add or remove on the list returned directly by List.of throws UnsupportedOperationException. A final reference is different: it prevents reassignment of the variable, not mutation of the collection it refers to.
The Collections utility class provides sort, reverse, and shuffle for suitable lists. You can also call sort on a list and pass a comparator. For instance, Comparator.comparingInt(String::length) sorts strings by length. Sorting changes the list in place; copy it first if you need to keep the original order.

Equality determines what “the same” means
List.contains and most set and map operations rely on equality, not on whether two references point to the same object. Strings with equal text compare as equal. For your own class, the inherited Object.equals compares identity unless you override it.
Hash-based collections have an additional rule: equal objects must return the same hashCode. Override equals and hashCode together when a custom object needs value-based identity. Do not change fields used by either method while the object is a HashMap key or a HashSet element; changing its hash can make an existing entry unexpectedly hard to find. A Java record is often a convenient immutable key when its components capture the identity you intend.
Keep collection behavior separate from security decisions. A map can count events for a report, but a count does not prove that a log line is authentic or that two similarly named accounts belong to the same user. Normalize identifiers only according to the system’s documented rules.
A small exercise that reveals the differences
In a local test program, read these event labels in order: login, error, login. Store them in an ArrayList<String> to keep all three occurrences. Add them to a LinkedHashSet<String> to get login and error once each, in first-seen order. Then use a HashMap<String, Integer> with merge(label, 1, Integer::sum) to count two logins and one error. When printing the counts, look up each label in the set’s order rather than iterating over the hash map, which offers no display-order guarantee.
