Collections
In short: Java’s standard framework (java.util) for variable-size data structures — the central interfaces are List, Set, and Map.
In more detail: Unlike arrays, collections don’t have a fixed size and offer ready-made methods for sorting, searching, and filtering. Since Java 5, collections are generic (see Generics) — List<String> enforces at compile time that only String objects go in, instead of discovering type errors only at runtime.
In Depth
The Collections framework is built as a hierarchy of interfaces, each of which has several concrete implementations with different performance characteristics:
List<String> ordered = new ArrayList<>(); // allows duplicates, order is preserved
Set<String> unique = new HashSet<>(); // no duplicates, NO guaranteed order
Map<String, Integer> mapping = new HashMap<>(); // key-value pairs
ordered.add("Ben"); ordered.add("Ben"); // both end up in there
unique.add("Ben"); unique.add("Ben"); // only contained once
mapping.put("Ben", 25);
mapping.get("Ben"); // 25The utility class Collections (singular vs. plural is a common source of confusion: java.util.Collection is the base interface, java.util.Collections is the class with static helper methods like sort(), max(), unmodifiableList()) offers ready-made algorithms that work on any collection without having to implement them yourself. Collections.unmodifiableList(list), for example, returns a read-only “view” of an existing list — useful for exposing an internal data structure externally without the caller being able to accidentally modify it.
The interface hierarchy at a glance
The root interface Collection<E> is extended by three main branches: List (ordered, duplicates allowed, index access), Set (no duplicates, usually no guaranteed order), and Queue/Deque (FIFO/LIFO behaviour for queues and stacks). Map<K, V> technically does NOT belong to Collection (it has key-value pairs instead of individual elements), but still counts as part of the Collections framework:
Deque<String> stack = new ArrayDeque<>();
stack.push("first");
stack.push("second");
System.out.println(stack.pop()); // "second" - Last In, First Out (stack behaviour)
Queue<String> queue = new LinkedList<>();
queue.offer("first");
queue.offer("second");
System.out.println(queue.poll()); // "first" - First In, First OutTypical choice of implementation
Each interface has several concrete implementations with different strengths: ArrayList (fast index access) vs. LinkedList (fast insertion at both ends) for List; HashSet (fastest access, no order) vs. TreeSet (sorted, somewhat slower) vs. LinkedHashSet (insertion order preserved) for Set. The choice almost always depends on which operation occurs most frequently — frequent reading by index favours ArrayList, frequent checking of “is X already contained?” favours HashSet.
Streams as a modern addition
Since Java 8, collections can be processed elegantly with the Streams API instead of classic loops:
List<String> names = List.of("Anna", "Ben", "Clara");
List<String> uppercase = names.stream()
.filter(n -> n.length() > 3)
.map(String::toUpperCase)
.toList();Thread safety
The standard implementations (ArrayList, HashMap, HashSet) are NOT thread-safe — multiple threads writing concurrently can damage the internal structure or trigger a ConcurrentModificationException. For concurrent access there’s either Collections.synchronizedList(list) (wraps every method in synchronized, simple but slow under heavy contention) or the specialised classes from java.util.concurrent like ConcurrentHashMap, which lock more fine-grained internally and thus scale significantly better.