Diploma in Programming Languages (Java & Python)
Advanced Java (Multithreading, Collections, JDBC)
•
Diploma
Java Collections Framework Architecture
Introduction
Modern Java programs rarely operate on isolated values. They manage groups of objects: student records, transactions, configuration entries, search results, messages, and database rows. The Java Collections Framework (JCF) provides a standard architecture for representing, manipulating, and traversing such groups.
The framework is more than a set of ready-made classes. It is a coordinated design involving interfaces, abstract implementations, concrete data structures, algorithms, ordering rules, equality contracts, and generic type safety. Understanding this architecture enables a programmer to select an appropriate collection, predict its behaviour, write reusable methods, and avoid errors involving ordering, duplication, mutation, and object identity.
The central distinction is between a Collection, which represents a group of individual elements, and a Map, which represents associations between keys and values. Although both are part of the Collections Framework, Map is not a subtype of Collection because its conceptual model is different.
Learning Objectives
After completing this chapter, you should be able to:
describe the principal interfaces in the Java Collections Framework;
distinguish Iterable, Collection, List, Set, Queue, Deque, and Map;
explain the relationships between interfaces, abstract classes, and implementations;
use generics to provide compile-time type safety;
select suitable collection implementations for common applications;
use enhanced for loops and explicit iterators correctly;
explain collection contracts involving equality, hashing, ordering, and optional operations;
analyse a practical design involving multiple collection types.
1. Architecture: Interfaces, Implementations, and Algorithms
The JCF separates what a data structure promises from how it stores data. Interfaces define operations and behavioural contracts. Implementations provide storage strategies. Utility algorithms provide operations such as sorting, searching, reversing, and shuffling.
A typical design process is therefore:
identify the required behaviour;
select the narrowest suitable interface;
choose an implementation based on performance and ordering needs;
expose the interface rather than the concrete class where possible.
For example, a method that only needs to read a sequence should usually accept List<String> rather than ArrayList<String>. This reduces coupling and allows callers to provide another List implementation.
import java.util.List;
public static int totalLength(List<String> words) {
int total = 0;
for (String word : words) {
total += word.length();
}
return total;
}
The variable may be declared using an interface while the object is created using an implementation:
List<String> names = new ArrayList<>();
This principle is commonly called programming to an interface.
The principal hierarchy
The framework can be viewed conceptually as follows:
Conceptual hierarchy of Java collection interfaces
Figure: Conceptual hierarchy of Java collection interfaces
Figure: Simplified relationships among the principal collection interfaces. Map is related to the framework but does not extend Collection.
The diagram is conceptual rather than a complete class hierarchy. For example, SortedSet and NavigableSet extend Set, while SortedMap and NavigableMap extend Map.
2. Iterable and Traversal
Iterable<E> is the foundation of the enhanced for statement. It declares the method:
Iterator<E> iterator();
An object implementing Iterable<E> can be traversed element by element. Most collection interfaces extend Iterable, which explains why the following works:
for (String name : names) {
System.out.println(name);
}
The enhanced for loop is convenient, but it does not eliminate the need to understand iterators. Conceptually, the compiler uses an iterator to obtain elements and advance through the structure.
Explicit iterators
An Iterator<E> normally supports:
hasNext() — tests whether another element exists;
next() — returns the next element;
remove() — removes the last element returned by the iterator, when supported.
Example:
Iterator<String> iterator = names.iterator();
while (iterator.hasNext()) {
String name = iterator.next();
if (name.isBlank()) {
iterator.remove();
}
}
Removing from a collection directly during an ordinary traversal can cause a ConcurrentModificationException for many fail-fast implementations:
for (String name : names) {
if (name.isBlank()) {
names.remove(name); // unsafe in general
}
}
The iterator’s remove() method is the appropriate approach when removal is supported. Alternatively, removeIf expresses the intention clearly:
names.removeIf(String::isBlank);
A ListIterator<E> extends Iterator<E> and is available for lists. It can move forwards and backwards and can insert or replace elements during traversal.
Fail-fast behaviour is a debugging aid, not a synchronization guarantee. It is not correct to rely on a particular exception in concurrent programs. Concurrent access requires suitable synchronization or a concurrent collection.
3. Generics and Type Safety
Generics allow a collection to specify the type of element it is intended to hold. In List<String>, String is the type argument. The compiler can then reject incompatible insertions and provide precise return types.
List<Integer> scores = new ArrayList<>();
scores.add(82);
// scores.add("excellent"); // compile-time error
int first = scores.get(0); // no cast required
Without generics, values would commonly be treated as Object, requiring casts and increasing the risk of runtime ClassCastException.
Type parameters and the diamond operator
A generic class or method can declare type parameters:
public static <T> void printAll(Collection<T> items) {
for (T item : items) {
System.out.println(item);
}
}
The diamond operator allows Java to infer constructor type arguments:
Map<String, Integer> inventory = new HashMap<>();
Wildcards and flexible APIs
A wildcard represents an unknown type. The bounded forms are particularly important:
public static double sum(List<? extends Number> values) {
double result = 0;
for (Number value : values) {
result += value.doubleValue();
}
return result;
}
? extends Number permits a list of Integer, Double, or another subtype of Number, but values generally cannot be added safely because the exact subtype is unknown.
A consumer can often use ? super T:
public static void addDefaults(List<? super Integer> values) {
values.add(0);
values.add(1);
}
A practical guideline is PECS: Producer Extends, Consumer Super. A structure that produces values for the method may use extends; one that consumes values may use super. This guideline is useful but should be applied with attention to the actual operation.
Generic type arguments must be reference types. Therefore, use List<Integer> rather than List<int>. Java may automatically box and unbox primitive values, but boxing creates wrapper objects and can have performance implications in very large collections.
4. The Collection Interface and Its Contract
Collection<E> represents a group of elements. It provides operations such as:
size() and isEmpty();
contains(Object);
add(E) and addAll(Collection<? extends E>);
remove(Object) and removeAll(...);
clear();
iterator();
toArray();
bulk predicates and transformations such as removeIf and stream.
A collection does not necessarily allow duplicates, preserve insertion order, permit null, or support modification. These properties depend on the more specific interface and implementation.
Optional operations
Some interface methods are documented as optional. An implementation may throw UnsupportedOperationException if an operation is not supported. This is different from a compile-time restriction.
For example, List.of("A", "B") creates an unmodifiable list. Reading is allowed, but adding or removing elements is not. Similarly, Arrays.asList(array) returns a fixed-size list: replacing an existing element is supported, but changing its size is not.
Programmers must read the implementation contract rather than assume that every Collection is mutable.
5. Lists: Ordered, Indexed Sequences
A List<E> is an ordered collection in which elements have positions called indices. Lists generally permit duplicates and provide operations such as get(index), set(index, value), add(index, value), and subList.
The principal general-purpose implementations are ArrayList and LinkedList.
ArrayList
ArrayList stores elements in a resizable array. It offers efficient indexed access because an element’s position can be used to locate it directly. Appending is usually efficient, although occasional resizing requires allocation and copying. Inserting or removing near the beginning or middle usually requires shifting subsequent elements.
ArrayList is a strong default when:
indexed access is frequent;
iteration is frequent;
elements are commonly appended;
insertion in the middle is not dominant.
LinkedList
LinkedList stores nodes linked to one another. It can efficiently add or remove at an end and can use a ListIterator to modify around a known position. However, locating an arbitrary index requires traversal. Its node objects also impose memory overhead and often reduce cache locality.
LinkedList should not be selected merely because a task contains insertions. If positions must first be located by index, the traversal cost may dominate. For queue or deque operations, ArrayDeque is often a better choice.
List equality and ordering
List equality is generally order-sensitive. Two lists containing equal elements in different orders are not equal. A list may contain multiple elements that are equal according to equals.
6. Sets: Uniqueness and Membership
A Set<E> contains no duplicate elements according to the set’s equality or ordering rules. Its central question is membership: is a particular element present?
HashSet
HashSet is based on hashing. It generally offers efficient average-time insertion, removal, and membership testing, but it does not promise iteration order. Its correct operation depends on a stable relationship between equals and hashCode.
LinkedHashSet
LinkedHashSet maintains a linked structure in addition to hash storage, preserving insertion order during iteration. It uses more memory than HashSet but offers predictable traversal order.
TreeSet
TreeSet stores elements according to their natural ordering or a supplied Comparator. It supports sorted traversal and navigational operations such as finding elements less than or greater than a specified value. Its performance is typically logarithmic for basic tree operations.
The ordering must be consistent with the intended notion of equality. If a comparator returns zero for two distinct objects, a TreeSet may treat them as duplicates even when their equals methods return false.
Equality and hashing contract
If two objects are equal according to equals, they must return the same hashCode. The reverse is not required: unequal objects may share a hash code. A hash collision is handled by the implementation, but many collisions can reduce efficiency.
A dangerous practice is to mutate fields used by equals or hashCode after an object has been inserted into a hash-based set. The object may then be in a location associated with its old hash value and become difficult to find or remove.
7. Queues and Deques
A Queue<E> represents elements waiting for processing. Its common conceptual policy is first-in, first-out (FIFO), although the interface also supports priority-based queues and other policies.
Queue methods are provided in paired forms:
Purpose
Exception form
Special-value form
Insert
add(e)
offer(e)
Inspect head
element()
peek()
Remove head
remove()
poll()
The special-value forms return false or null when an operation cannot be completed, whereas the exception forms throw an exception. Because null may be a valid element for some implementations, the meaning of a null result must be considered carefully.
PriorityQueue removes elements according to priority, determined by natural ordering or a comparator; it is not FIFO. Its iterator does not promise sorted traversal. To observe removal order, repeatedly call poll().
A Deque<E>—double-ended queue—supports insertion and removal at both the front and rear. It can model both a queue and a stack:
ArrayDeque is generally preferred to the legacy Stack class for stack behaviour. It is also useful for breadth-first search queues, undo histories, and sliding-window processing. It does not permit null elements.
8. Maps: Associations Between Keys and Values
A Map<K,V> stores mappings from keys of type K to values of type V. A key can occur at most once in a map, although multiple keys may map to equal values.
Important operations include:
Map<String, Integer> counts = new HashMap<>();
counts.put("Java", 3);
counts.merge("Java", 1, Integer::sum);
int value = counts.getOrDefault("Python", 0);
put replaces the previous value associated with an existing key. get returns null when no mapping exists, but this can be ambiguous if null values are permitted. containsKey distinguishes absence from a present key whose value is null.
Map views
The methods keySet(), values(), and entrySet() return views backed by the map. Changes to the map may be reflected in these views, and supported removals through a view may affect the map.
When both key and value are needed, entrySet() is generally the clearest and most efficient traversal:
Map also has sorted and navigable variants. SortedMap specifies key ordering, while NavigableMap adds operations such as floorEntry, ceilingEntry, and range views.
9. Collection Contracts: Equality, Ordering, and Mutation
Correct collection use depends on respecting contracts rather than memorising class names.
equals and hashCode
The equals method should be reflexive, symmetric, transitive, consistent, and false when compared with null. The hashCode contract requires equal objects to have equal hash codes. Classes used as hash keys or hash-set elements should implement these methods using logically identifying, preferably immutable, state.
Natural ordering and comparators
An object may implement Comparable<T> to define natural ordering. A separate Comparator<T> can define an alternative ordering:
List<String> words = new ArrayList<>(List.of("pear", "fig", "watermelon"));
words.sort(Comparator.comparingInt(String::length));
A comparator should be transitive and consistent. In sorted collections, comparison determines placement and often determines whether an element is considered a duplicate.
Null values
Null policy varies. HashMap permits one null key and multiple null values; HashSet generally permits one null element; TreeSet, TreeMap, and ArrayDeque commonly reject null in relevant operations. Code should not assume uniform null support.
Ordering guarantees
A collection may provide no order guarantee, insertion order, access order, sorted order, or priority order. An observed order is not necessarily a contractual order. Production code should rely only on documented guarantees.
Mutability and unmodifiable views
Collections.unmodifiableList(list) creates a read-only view of a backing list. If the backing list changes elsewhere, the view may reflect those changes. Factory methods such as List.of create unmodifiable collections whose contents cannot be changed through the returned object. This distinction matters when designing APIs and protecting internal state.
10. Choosing an Implementation
A suitable choice depends on the required operations, not on a universal ranking of classes.
Requirement
Often suitable
Fast indexed sequence access
ArrayList
Uniqueness with no required order
HashSet
Uniqueness with insertion order
LinkedHashSet
Sorted unique elements
TreeSet
FIFO processing
ArrayDeque or a queue implementation
LIFO stack behaviour
ArrayDeque
General key-value lookup
HashMap
Ordered key-value traversal
LinkedHashMap
Sorted key-value traversal
TreeMap
Enum keys
EnumMap
These are starting points, not absolute rules. Workload, memory, concurrency, null policy, ordering, and profiling results can change the decision.
11. Applied Case: Processing Examination Results
Background
A college application receives examination results from several departments. Each result contains a student identifier, course code, mark, and submission sequence. The application must reject duplicate student-course submissions, preserve the order in which valid records arrive, calculate a course summary, and produce a sorted report.
This is an illustrative case, not a description of a specific production system.
Problem analysis
The requirements imply several different abstractions:
a Set is needed to identify already processed submission keys;
a List is useful for preserving valid records in arrival order;
a Map can associate a course code with its accumulated marks;
a sorted view is useful for reporting course codes alphabetically.
Using one collection for every purpose would make the design less clear and could produce incorrect behaviour.
Approach
record Result(String studentId, String course, int mark) {}
List<Result> accepted = new ArrayList<>();
Set<String> seen = new HashSet<>();
Map<String, Integer> totals = new HashMap<>();
Map<String, Integer> counts = new HashMap<>();
for (Result result : incomingResults) {
String key = result.studentId() + "|" + result.course();
if (seen.add(key)) { // true only for a new key
accepted.add(result); // preserves arrival order
totals.merge(result.course(), result.mark(), Integer::sum);
counts.merge(result.course(), 1, Integer::sum);
}
}
Map<String, Integer> sortedTotals = new TreeMap<>(totals);
The boolean result of Set.add is valuable: it indicates whether the set changed. The merge operation avoids a repetitive contains-and-put sequence and expresses accumulation directly.
Outcome
The design rejects duplicate submissions, retains accepted results in input order, and produces totals keyed by course. The TreeMap provides sorted course-key traversal for a report. Average lookup and insertion behaviour of the hash-based structures is typically efficient, while the sorted report imposes ordering costs appropriate to its purpose.
Learning points
One application may legitimately need several collection abstractions.
A collection’s return values can carry important information, such as whether a set insertion was new.
Key design is part of correctness; the duplicate-detection key must represent the business rule.
Sorting should be requested where it is required, rather than assumed from a hash-based implementation.
A record used as a map key should have stable equality and hash behaviour.
12. Professional Practice, Limitations, and Contemporary Features
API design
Public methods should accept the narrowest useful interface and document ordering, null, mutability, and thread-safety expectations. Returning an internal mutable collection can expose implementation state. Defensive copies, unmodifiable views, or immutable factory collections may be appropriate.
Concurrency
Most ordinary collections are not safe for unsynchronized concurrent structural modification. Options include external synchronization, synchronized wrappers, immutable data exchange, or concurrent collections such as ConcurrentHashMap and CopyOnWriteArrayList. A concurrent collection solves particular access problems; it does not automatically make a multi-step business operation atomic.
For example, checking a key and then inserting a value as two separate operations may still be vulnerable to races. Atomic methods such as putIfAbsent, computeIfAbsent, and merge are often more appropriate.
Streams and collection architecture
The Stream API works with collections but is not itself a collection. A stream describes a pipeline of processing operations and is normally consumed once. collect can materialise results into a collection:
Map<String, Long> frequency = words.stream()
.collect(Collectors.groupingBy(String::toLowerCase,
Collectors.counting()));
The desired result type and ordering should be specified when necessary, because not every collector guarantees the same map or list implementation.
Performance and measurement
Complexity descriptions such as “constant average-time lookup” are abstractions. Actual performance depends on data distribution, object allocation, memory hierarchy, implementation details, and workload. Big-O notation does not measure memory overhead or constant factors. Use documentation, reasoned analysis, and profiling rather than relying on slogans.
Security and governance
Collections can participate in security-sensitive systems. Untrusted input should not be allowed to cause uncontrolled memory growth. Keys should be designed to avoid accidental collisions or pathological workloads where relevant. Sensitive values should not be exposed through logs or publicly returned mutable collections. In concurrent services, predictable resource limits and safe publication of collection state are governance concerns as well as programming concerns.
Chapter Summary
The Java Collections Framework is an architecture of interfaces, implementations, algorithms, and behavioural contracts. Iterable enables traversal, while Collection represents groups of individual elements. List models ordered, indexed sequences; Set models uniqueness; Queue models processing order; and Deque supports operations at both ends. Map represents key-value associations and is separate from the Collection hierarchy.
Generics provide compile-time type safety and reduce casts. Iterators support controlled traversal and removal, while enhanced for loops provide concise iteration. Correct use of collections requires attention to equality, hashing, comparators, ordering, null policies, optional operations, mutability, and concurrency.
ArrayList, HashSet, TreeSet, ArrayDeque, HashMap, LinkedHashMap, and TreeMap address different requirements. The professional programmer selects an implementation according to required operations and documented guarantees, exposes suitable interfaces, and validates performance and concurrency assumptions in the target application.
Key Terms
Iterable: An interface whose objects can provide an iterator for traversal.
Collection: An interface representing a group of individual elements.
List: An ordered collection that supports positional access and usually permits duplicates.
Set: A collection that does not permit duplicate elements according to its membership rules.
Queue: An interface representing elements awaiting processing.
Deque: A double-ended queue supporting insertion and removal at both ends.
Map: An object associating unique keys with values.
Iterator: An object used to traverse elements sequentially.
Generic: A type or method parameterised by one or more types.
Wildcard: A generic notation representing an unknown type.
ArrayList: A resizable-array implementation of List.
HashSet: A hash-based implementation of Set.
TreeSet: A sorted, tree-based implementation of Set.
HashMap: A hash-based implementation of Map.
TreeMap: A sorted, tree-based implementation of Map.
Equality contract: Rules governing consistent implementation of equals.
Hashing contract: Rules requiring equal objects to have equal hash codes.
Comparator: An object defining an ordering between values.
Fail-fast iterator: An iterator that may detect unexpected structural modification and throw an exception.
Unmodifiable collection: A collection through which structural changes are not permitted.
Review Questions
Short Answer Questions
Why is Map not a subtype of Collection?
What is the purpose of the Iterable interface?
State one important difference between ArrayList and LinkedList.
Why must equal objects have the same hash code?
What is the difference between peek() and poll() on a queue?
Long Answer Questions
Explain the relationships among interfaces, implementations, and algorithms in the Java Collections Framework.
Compare HashSet, LinkedHashSet, and TreeSet, including ordering and typical use cases.
Explain Java generics, bounded wildcards, and the PECS guideline with examples.
Discuss the contracts involving equals, hashCode, Comparable, and Comparator.
Compare HashMap, LinkedHashMap, and TreeMap in terms of ordering, use, and design considerations.
Analytical / Application Questions
Design collection types for an order-processing system that must preserve order, reject duplicate order identifiers, and provide lookup by customer identifier. Justify each choice.
A program removes elements from an ArrayList inside an enhanced for loop and sometimes throws ConcurrentModificationException. Explain the cause and propose two correct solutions.
An application uses a TreeSet with a comparator that compares employees only by department. Analyse why employees in the same department may disappear as duplicates and propose a comparator that represents the intended ordering more accurately.
Multiple Choice Questions with Answers
Question 1
Which interface provides the method used by an enhanced for loop to obtain traversal state?
A. Iterable
B. Map
C. Comparator
D. Comparable
Correct Answer: A. Iterable
Question 2
Which collection is most appropriate when unique elements must be retained in insertion order?
A. HashSet
B. LinkedHashSet
C. TreeSet
D. PriorityQueue
Correct Answer: B. LinkedHashSet
Question 3
What does Map.put(key, value) normally do when the key already exists?
A. Adds a second mapping with the same key
B. Throws an exception in every map implementation
C. Replaces the value associated with the key
D. Removes the key from the map
Correct Answer: C. Replaces the value associated with the key
Question 4
Which implementation is generally the preferred modern choice for stack behaviour?
A. Stack
B. LinkedHashMap
C. ArrayDeque
D. TreeSet
Correct Answer: C. ArrayDeque
Question 5
Why is mutating a field used by hashCode dangerous after insertion into a HashSet?
A. The set automatically sorts the object incorrectly
B. The object may no longer be found in the expected hash location
C. The object becomes immutable
D. The iterator changes into a list iterator
Correct Answer: B. The object may no longer be found in the expected hash location
Question 6
Which structure provides sorted keys and navigational map operations?
A. HashMap
B. IdentityHashMap
C. TreeMap
D. WeakHashMap
Correct Answer: C. TreeMap
Question 7
What is the principal effect of ? extends Number in a method parameter?
A. It accepts a producer whose element type is Number or a subtype
B. It permits insertion of every possible Number subtype
C. It restricts the argument to exactly Number
D. It converts all values to primitive numbers
Correct Answer: A. It accepts a producer whose element type is Number or a subtype
Question 8
Which queue method returns null rather than throwing when the queue is empty?
A. remove
B. element
C. poll
D. add
Correct Answer: C. poll
Question 9
What does a Set generally use to determine whether an element is already present?
A. Only the object’s memory address
B. Equality or ordering rules defined by the implementation
C. The element’s insertion time only
D. The element’s string representation only
Correct Answer: B. Equality or ordering rules defined by the implementation
Question 10
Which traversal is usually the clearest when both keys and values are needed from a map?
A. Iterating over values() and reconstructing keys
B. Iterating over entrySet()
C. Calling get() with random keys
D. Converting the map to a Deque
Correct Answer: B. Iterating over entrySet()
Advanced Java (Multithreading, Collections, JDBC)
Chapter 2
of 15