Arrays and Collections
Introduction
This lesson covers the fifth objective of the Oracle Certified Professional Java SE 21 Developer exam (1Z0-830): Working with Arrays and Collections. Oracle does not publish a weighting per objective, but collections appear in a large share of questions, directly (what does this Map operation return?) and indirectly (streams and concurrency questions use lists, maps and sets as their raw material). Java 21 adds one new topic you must know: sequenced collections (JEP 431).
You will learn how arrays behave, which collection type to pick, the exact contracts of List, Set, Map and Deque, the immutable factory methods, sorting with Comparable and Comparator, and the rules of generics and wildcards. As always, we focus on what the exam asks: predicting output and spotting compile errors.
Learning objectives
After this lesson you will be able to:
- Declare, initialize, copy, compare and sort arrays, including multidimensional and ragged arrays.
- Choose among
ArrayList,LinkedList,HashSet,LinkedHashSet,TreeSet,HashMap,LinkedHashMap,TreeMapandArrayDeque. - Predict the effect of add, remove, replace and bulk operations, and the exceptions they throw.
- Use the Java 21 sequenced collection methods (
getFirst,getLast,reversed,addFirst). - Implement
ComparableandComparatorcorrectly, including chained comparators. - Create immutable collections and explain the difference between a copy and an unmodifiable view.
- Apply generic methods, bounded types and wildcards (
? extends,? super).
1. Arrays
An array is a fixed-size, zero-indexed object holding elements of one type. The length is fixed at creation and read with the length field (not a method).
int[] scores = new int[3]; // {0, 0, 0}
String[] names = {"Ana", "Bo", "Cy"}; // array initializer
int[][] grid = new int[2][]; // ragged: second dimension not allocated
grid[0] = new int[]{1, 2, 3};
grid[1] = new int[]{4};
Facts and traps:
- Valid declarations:
int[] a,int a[], andint[] a, b[](herebisint[][]). Mixed forms are a favorite trick. - Default values:
0,false,'\u0000',null.new String[2]holds twonullreferences. - Indexing outside
0..length-1throwsArrayIndexOutOfBoundsExceptionat run time; a negative size innew int[-1]throwsNegativeArraySizeException. - Arrays are covariant:
Object[] o = new String[1]; o[0] = 1;compiles but throwsArrayStoreException. ==compares references,equalson arrays is also reference equality. UseArrays.equals(a, b)for content andArrays.deepEqualsfor nested arrays.Arrays.toString(a)andArrays.deepToString(a)print content.- Useful helpers in
java.util.Arrays:sort,binarySearch,fill,copyOf,copyOfRange,asList,stream,mismatch,compare.
Binary search requires a sorted array. If the key is found it returns its index; otherwise it returns -(insertionPoint) - 1. For {10, 20, 30} searching 25 returns -3 (insertion point 2). On an unsorted array the result is undefined.
Arrays.asList returns a fixed-size list backed by the array: set writes through to the array, but add and remove throw UnsupportedOperationException.
String[] arr = {"b", "a"};
List<String> view = Arrays.asList(arr);
view.set(0, "z"); // arr is now {"z", "a"}
view.add("q"); // UnsupportedOperationException
2. The Collections Framework at a glance
| Interface | Duplicates | Order | Typical implementations |
|---|---|---|---|
List |
Yes | Insertion (indexed) | ArrayList, LinkedList |
Set |
No | Depends | HashSet (none), LinkedHashSet (insertion), TreeSet (sorted) |
Map |
Unique keys | Depends | HashMap, LinkedHashMap, TreeMap |
Deque / Queue |
Yes | FIFO / both ends | ArrayDeque, LinkedList, PriorityQueue |
Map is not a Collection. Collections (plural) is the utility class; Collection is the root interface.
3. List
ArrayList is backed by a resizable array: fast random access, slow insertion in the middle. LinkedList implements both List and Deque: fast insertion at the ends, slow indexed access.
List<String> list = new ArrayList<>(List.of("a", "b", "c"));
list.add("d"); // [a, b, c, d]
list.add(1, "x"); // [a, x, b, c, d]
list.remove("b"); // removes the object: [a, x, c, d]
list.remove(0); // removes the index: [x, c, d]
list.set(1, "z"); // [x, z, d]
list.indexOf("d"); // 2
list.removeIf(s -> s.equals("x")); // [z, d]
list.replaceAll(String::toUpperCase); // [Z, D]
The overload trap: with List<Integer> nums, nums.remove(1) removes index 1 (the int overload wins), while nums.remove(Integer.valueOf(1)) removes the value 1.
Iteration while modifying: adding or removing inside an enhanced for loop throws ConcurrentModificationException at the next iteration step (even removing the second-to-last element can, by chance, avoid it, but do not rely on it). Use Iterator.remove() or removeIf.
subList(from, to) returns a view (to exclusive); structural changes of the parent afterward make the view invalid.
4. Set
A Set rejects duplicates according to equals (and hashCode for hash-based sets, compareTo for TreeSet). add returns false if the element was already present.
Set<String> hs = new HashSet<>(List.of("b", "a", "c")); // order not guaranteed
Set<String> ls = new LinkedHashSet<>(List.of("b", "a")); // [b, a]
Set<String> ts = new TreeSet<>(List.of("b", "a", "c")); // [a, b, c]
TreeSet offers first(), last(), floor, ceiling, lower, higher, headSet, tailSet, descendingSet. Elements must be mutually comparable or the set needs a Comparator, otherwise a ClassCastException occurs on insertion. TreeSet does not allow null (natural ordering); HashSet allows one null.
Remember the contract: objects equal by equals must have equal hashCode. If you override equals but not hashCode, two "equal" objects land in different buckets and a HashSet may contain both.
5. Map
A Map<K,V> associates unique keys with values. put returns the previous value (or null).
Map<String, Integer> stock = new HashMap<>();
stock.put("java", 3);
stock.put("java", 5); // returns 3, replaces
stock.putIfAbsent("sql", 1);
stock.merge("java", 1, Integer::sum); // 6
stock.computeIfAbsent("go", k -> 0); // inserts 0
stock.computeIfPresent("sql", (k, v) -> v + 10); // 11
stock.getOrDefault("rust", -1); // -1, nothing inserted
for (Map.Entry<String, Integer> e : stock.entrySet()) { ... }
Key points:
HashMapallows onenullkey and null values;TreeMaphas sorted keys (nonullkey with natural order);LinkedHashMapkeeps insertion order (or access order if configured).keySet(),values()andentrySet()are views: removing from them removes from the map.Map.of(...)andMap.copyOfproduce immutable maps that rejectnulland duplicate keys (IllegalArgumentException) and have unspecified iteration order.Map.ofEntriesandMap.entryhelp with more than 10 pairs.merge(key, value, fn): if the key is absent (or its value isnull) it storesvalue; otherwise it storesfn(old, value); iffnreturnsnullthe key is removed.
6. Deque, Queue and stack operations
Deque is a double-ended queue. ArrayDeque is the standard implementation and also the recommended stack (the legacy Stack class extends Vector and is synchronized).
| Operation | Throws on failure | Returns special value |
|---|---|---|
| Insert at tail | add, addLast |
offer, offerLast |
| Insert at head | addFirst, push |
offerFirst |
| Remove from head | remove, removeFirst, pop |
poll, pollFirst |
| Remove from tail | removeLast |
pollLast |
| Examine head | element, getFirst |
peek, peekFirst |
push puts the element at the head, pop removes from the head, so push(1); push(2); pop() returns 2 (LIFO). ArrayDeque rejects null. PriorityQueue orders by natural order or a comparator, but its iteration and toString order is not sorted; only poll/peek give the smallest element.
7. Sequenced collections (Java 21)
JEP 431 adds interfaces SequencedCollection, SequencedSet and SequencedMap for collections with a defined encounter order. List, Deque, LinkedHashSet, SortedSet (so TreeSet), LinkedHashMap and SortedMap implement them; HashSet and HashMap do not.
List<String> steps = new ArrayList<>(List.of("plan", "code", "test"));
steps.getFirst(); // plan
steps.getLast(); // test
steps.addFirst("idea"); // [idea, plan, code, test]
steps.removeLast(); // [idea, plan, code]
List<String> back = steps.reversed(); // a reverse-ordered VIEW: [code, plan, idea]
LinkedHashMap<String, Integer> m = new LinkedHashMap<>();
m.put("a", 1); m.put("b", 2);
m.firstEntry(); // a=1
m.lastEntry(); // b=2
m.putFirst("z", 0); // z moves to the front
reversed() is a view: changes in the original show in the reversed one and the reverse. getFirst() on an empty list throws NoSuchElementException. Immutable lists (List.of) support getFirst/reversed but throw UnsupportedOperationException for addFirst. For TreeSet, addFirst/addLast throw UnsupportedOperationException because order is defined by the comparator.
8. Sorting: Comparable and Comparator
Comparable<T> defines the natural order with int compareTo(T other): negative, zero or positive. Comparator<T> defines an external order with int compare(T a, T b).
record Course(String title, int hours, double rating) { }
List<Course> courses = new ArrayList<>(List.of(
new Course("Java", 40, 4.8), new Course("SQL", 20, 4.8), new Course("Go", 20, 4.5)));
courses.sort(Comparator.comparingDouble(Course::rating).reversed()
.thenComparing(Course::hours)
.thenComparing(Course::title));
// SQL (4.8, 20h), Java (4.8, 40h), Go (4.5, 20h)
Rules: Collections.sort(list) needs elements implementing Comparable, otherwise a compile error (generic bound) or ClassCastException. Comparator.reversed() applies to the comparator built before it; chain .reversed() carefully because comparing(a).thenComparing(b).reversed() reverses both. Comparator.naturalOrder(), reverseOrder(), nullsFirst, and Comparator.comparing(keyExtractor, keyComparator) are common. An inconsistent compareTo (not matching equals) makes TreeSet treat "different" objects as duplicates.
9. Immutable collections and views
| Creation | Mutable? | Nulls | Notes |
|---|---|---|---|
List.of(...), Set.of(...), Map.of(...) |
No | Rejected | Set.of/Map.of reject duplicates at creation |
List.copyOf(c) |
No | Rejected | Independent copy (returns same instance if already immutable) |
Collections.unmodifiableList(l) |
Not through the view | Allowed | A view: changes to l show through |
Arrays.asList(arr) |
set only |
Allowed | Fixed-size, backed by array |
stream.toList() |
No | Allowed | Unmodifiable list |
Attempting add, remove or set on an immutable collection throws UnsupportedOperationException; even contains(null) on List.of throws NullPointerException.
10. Generics and wildcards
Generics give compile-time type safety; the type arguments are erased at run time, so List<String> and List<Integer> are the same class and you cannot do new T(), instanceof List<String> or create generic arrays.
static <T extends Comparable<T>> T max(List<T> items) {
T best = items.get(0);
for (T t : items) if (t.compareTo(best) > 0) best = t;
return best;
}
List<Object>is not a supertype ofList<String>(generics are invariant).List<? extends Number>(upper bound): you can readNumber, but you cannot add anything exceptnull. Use it for producers.List<? super Integer>(lower bound): you can addInteger, but you read onlyObject. Use it for consumers. The mnemonic is PECS: Producer Extends, Consumer Super.List<?>is the unknown type: read asObject, add onlynull.- The diamond
new ArrayList<>()infers the type from the context;varinfers the declared type from the right side (var l = new ArrayList<>()givesArrayList<Object>). - Raw types (
List l = new ArrayList();) compile with warnings and let wrong types in, causingClassCastExceptionlater.
Worked example: top-rated courses per level
Statement. Given this data, what does the program print?
record Course(String title, String level, double rating) { }
List<Course> all = List.of(
new Course("Java", "pro", 4.8), new Course("SQL", "assoc", 4.6),
new Course("Go", "pro", 4.5), new Course("Linux", "assoc", 4.9));
Map<String, Double> best = new TreeMap<>();
for (Course c : all) {
best.merge(c.level(), c.rating(), Math::max);
}
System.out.println(best);
Step 1. TreeMap sorts keys alphabetically: assoc before pro.
Step 2. Process in order. Java/pro is absent so pro=4.8. SQL/assoc gives assoc=4.6. Go/pro: merge computes max(4.8, 4.5) = 4.8. Linux/assoc: max(4.6, 4.9) = 4.9.
Step 3. toString of a map prints entries in key order.
Solution. {assoc=4.9, pro=4.8}.
Why other choices fail. With a HashMap the order might differ and the question would be unanswerable; using put instead of merge would leave pro=4.5 and assoc=4.9 because the last course wins; and Map.of would not allow later modification, so merge would throw UnsupportedOperationException.
Second worked example: list views and the Java 21 methods
Statement. What does this code print?
List<Integer> base = new ArrayList<>(List.of(1, 2, 3, 4));
List<Integer> rev = base.reversed();
base.add(5);
rev.addFirst(9);
System.out.println(base + " " + rev.getLast());
Step 1. reversed() returns a view, so rev shows [4, 3, 2, 1] at first.
Step 2. base.add(5) appends to the original: [1, 2, 3, 4, 5]. The view reflects it as [5, 4, 3, 2, 1].
Step 3. rev.addFirst(9) inserts at the front of the reversed view, which is the end of the original: base becomes [1, 2, 3, 4, 5, 9].
Step 4. rev.getLast() is the last element of the reversed view, which is the first of the original: 1.
Solution. [1, 2, 3, 4, 5, 9] 1.
Why the alternatives are worse. Treating reversed() as a copy would print [1, 2, 3, 4, 5] and miss the write-through. Assuming addFirst puts 9 at index 0 of base ignores that the view is reversed. Expecting an exception confuses the view with an immutable list: this one is backed by a modifiable ArrayList.
Common pitfalls and exam tips
- Exam tip: identify the concrete class first. Whether the output order is guaranteed depends entirely on it (
HashSetno,TreeSetsorted,LinkedHashSetinsertion). list.remove(int)versuslist.remove(Object)withList<Integer>is tested almost every session.Arrays.asListis not a resizable list;List.ofis not even settable.Arrays.sorton an array of objects needsComparableor aComparator; on a primitive array it uses natural numeric order.Arrays.equals(int[], int[])compares content;a.equals(b)does not.TreeSet/TreeMapusecompareTo, notequals, to detect duplicates.ArrayDequeandMap.ofrejectnull;HashMap,ArrayListaccept it.- A for-each over a list while calling
list.remove(...)throwsConcurrentModificationException. Collections.unmodifiableListis a view,List.copyOfis a snapshot.- Do not forget Java 21:
getFirst(),getLast(),reversed()exist onList.
Mini case study: shopping cart ranking
An online shop keeps cart lines as record Line(String sku, int qty, long unitCents). The product owner wants (1) the lines sorted by total price descending, ties by SKU ascending, (2) the total quantity per SKU if the same SKU appears several times, and (3) a safe, read-only list to hand to the invoice module.
Solution.
Comparator<Line> byTotalDescThenSku =
Comparator.comparingLong((Line l) -> l.qty() * l.unitCents()).reversed()
.thenComparing(Line::sku);
List<Line> sorted = new ArrayList<>(lines);
sorted.sort(byTotalDescThenSku);
Map<String, Integer> qtyBySku = new LinkedHashMap<>();
for (Line l : lines) qtyBySku.merge(l.sku(), l.qty(), Integer::sum);
List<Line> readOnly = List.copyOf(sorted);
Commentary: reversed() is applied right after comparingLong, so only the total is reversed, and thenComparing adds the ascending SKU tie-breaker after it. Copying lines into a new ArrayList avoids mutating the caller's data (and avoids UnsupportedOperationException if lines came from List.of). merge with Integer::sum is the idiomatic accumulator, and LinkedHashMap keeps first-seen order for predictable reports. List.copyOf is an independent immutable snapshot, whereas Collections.unmodifiableList(sorted) would reflect later changes to sorted, which could surprise the invoice module.
Key terms
- Encounter order: the order in which a collection yields its elements.
- Sequenced collection: a collection with a defined first and last element (Java 21).
- View: a collection backed by another, such as
subList,keySetorreversed(). - Natural ordering: the order defined by
Comparable.compareTo. - Type erasure: removal of generic type arguments at compile time.
- PECS: Producer Extends, Consumer Super.
- Fail-fast iterator: throws
ConcurrentModificationExceptionon structural modification. - Load factor: fill ratio of a hash table before it resizes (0.75 by default for
HashMap).
Summary checklist
- I can declare and initialize one-, two-dimensional and ragged arrays and know the default values.
- I know what
Arrays.asList,List.of,List.copyOfandunmodifiableListallow and forbid. - I can predict
List.remove(int)versusremove(Object)behavior. - I can pick
HashSet,LinkedHashSetorTreeSetfrom the ordering requirement. - I can use
put,putIfAbsent,merge,computeIfAbsentandgetOrDefaulton maps. - I can use
Dequeas a stack and a queue and know wherepushinserts. - I can use
getFirst,getLast,reversedandaddFirstfrom Java 21. - I can write chained comparators and avoid the
reversed()pitfall. - I can apply
? extends,? superand generic bounds.
Official sources
- Oracle exam page for 1Z0-830 (objectives): https://education.oracle.com/java-se-21-developer-professional/pexam_1Z0-830 (opens in a new tab)
- JEP 431: Sequenced Collections (final in Java 21): https://openjdk.org/jeps/431 (opens in a new tab)
- The Java Tutorials, Collections: https://docs.oracle.com/javase/tutorial/collections/index.html (opens in a new tab)
- The Java Tutorials, Generics: https://docs.oracle.com/javase/tutorial/java/generics/index.html (opens in a new tab)
- API documentation for
java.util(Java SE 21): https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/package-summary.html (opens in a new tab)