0% found this document useful (0 votes)
2 views27 pages

Java Collection Frameworks

nice notes

Uploaded by

samueld8448
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
2 views27 pages

Java Collection Frameworks

nice notes

Uploaded by

samueld8448
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd

The Java Collections Framework

1. Introduction: The Problem That Collections Solve


In your programming journey so far, you have worked extensively with arrays. Arrays are
useful but come with significant limitations. When you declare an array, you must specify its
size upfront, and that size cannot change during program execution. If you need to store more
elements than the array can hold, you cannot simply expand it. You would have to create a
larger array, copy all elements from the old array to the new one, and then discard the old
array. This process is inefficient and error-prone. Furthermore, arrays provide only basic
functionality. If you want to insert an element in the middle of an array, you must manually
shift all subsequent elements. If you want to search for a specific element, you must write
your own search logic. If you want to remove an element, you must shift elements again and
keep track of the "empty" spot. As your programs become more complex, managing data
using only arrays becomes cumbersome.
The Java Collections Framework (JCF) was designed to solve these problems. A collection is
an object that groups multiple elements into a single unit. The Collections Framework
provides a unified architecture for storing, retrieving, manipulating, and processing groups of
objects. It includes interfaces (which define the operations you can perform),
implementations (the concrete classes that provide the actual data structures), and algorithms
(reusable methods for common operations like sorting and searching). By using the
Collections Framework, you can focus on what you want to do with your data rather than
how to implement the underlying data structures.
The framework is built around a small set of core interfaces: Collection, List, Set, Queue,
and Map. Understanding these interfaces and their most common implementations is the key
to becoming an effective Java programmer.

2. The Hierarchy of the Collections Framework


To use the Collections Framework effectively, you must understand its inheritance hierarchy.
The root interface of the entire framework is the Collection interface. It defines the most
basic operations that all collections support, such as adding an element (add()), removing an
element (remove()), checking if an element is present (contains()), and finding the number of
elements (size()).
From the Collection interface, three main sub-interfaces branch out: List, Set, and Queue.
Each represents a different type of collection with distinct characteristics:
 List: An ordered collection that allows duplicate elements. Elements in a list have a
position (index), just like an array. You can access, insert, or remove elements at
specific positions.
 Set: A collection that does not allow duplicate elements. Sets model the mathematical
concept of a set. They are unordered (though some implementations provide
ordering).
 Queue: A collection designed for holding elements prior to processing. Queues
typically order elements in a FIFO (first-in-first-out) manner.
Separate from the Collection hierarchy is the Map interface. A Map is not a true collection in
the sense that it does not extend Collection. Instead, a Map stores key-value pairs. Each key
is associated with a value, and you retrieve the value by providing the key. Keys in a map
must be unique. A map is analogous to a dictionary: you look up a definition (value) using a
word (key).
The following diagram illustrates the hierarchy:
text
Iterable (interface)

└── Collection (interface)
├── List (interface)
│ ├── ArrayList (class)
│ └── LinkedList (class)
├── Set (interface)
│ ├── HashSet (class)
│ └── TreeSet (class)
└── Queue (interface)
└── PriorityQueue (class)

Map (interface)
├── HashMap (class)
└── TreeMap (class)
Understanding this hierarchy helps you choose the right collection for your specific needs.
For example, if you need to maintain order and allow duplicates, you choose a List. If you
need to ensure uniqueness, you choose a Set. If you need fast lookups by a key, you choose
a Map.

3. Generics: Type Safety for Collections


Before we explore individual collection types, we must discuss generics. Generics were
introduced in Java 5 to provide compile-time type safety. Without generics, a collection could
hold any type of object. You could put a String, an Integer, and a Student object all into the
same ArrayList. While this might seem flexible, it leads to problems. When you retrieve an
element, you receive it as an Object, and you must cast it back to its original type. If you
mistakenly cast to the wrong type, the program compiles but crashes at runtime with
a ClassCastException.
Generics solve this by allowing you to specify the type of elements that a collection can hold.
The syntax uses angle brackets (<>). For example, List<String> declares a list that can only
contain String objects. The compiler checks that you never add an incompatible type. When
you retrieve an element, no casting is needed because the compiler already knows the type.
Consider this example that contrasts non-generic and generic code:
java
// Without generics (old style - still works but is unsafe)
import [Link].*;

public class WithoutGenerics {


public static void main(String[] args) {
ArrayList list = new ArrayList(); // Raw type - can hold anything
[Link]("Hello");
[Link]([Link](42)); // Mixing types is allowed but dangerous

// Retrieving elements requires casting


String first = (String) [Link](0); // Works
// String second = (String) [Link](1); // Compiles but crashes at runtime!
}
}

// With generics (modern, safe style)


public class WithGenerics {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>(); // Diamond operator <>
[Link]("Hello");
// [Link]([Link](42)); // Compilation error! Incompatible type

String first = [Link](0); // No casting needed


[Link](first);
}
}
Always use generics when working with collections. They make your code safer, more
readable, and self-documenting.

4. The List Interface


The List interface represents an ordered collection of elements. Lists allow duplicates and
provide indexed access, meaning you can get, set, insert, or remove elements by their integer
position. The two most commonly used implementations
of List are ArrayList and LinkedList. They both implement the same interface, but their
internal data structures and performance characteristics differ significantly.
4.1 ArrayList
ArrayList is the most frequently used list implementation. Internally, it uses a dynamic array
(a resizable array) to store elements. When you add more elements than the current capacity,
the ArrayList automatically creates a new, larger array and copies the existing elements into
it. This resizing operation is relatively expensive, but it happens infrequently, so for most use
cases, ArrayList performs very well.
The strengths of ArrayList are fast random access and fast iteration. Accessing an element by
index (get(index)) takes constant time, O(1), because it directly calculates the memory
location. Iterating through all elements is also efficient because elements are stored
contiguously in memory. The weakness of ArrayList is inserting or removing elements in the
middle. When you insert an element at an arbitrary position, all subsequent elements must be
shifted to make room. Similarly, removing an element from the middle requires shifting
elements to fill the gap. These operations take linear time, O(n), which can be slow for large
lists.
java
import [Link].*;

public class ArrayListDemo {


public static void main(String[] args) {
// Creating an ArrayList
ArrayList<String> students = new ArrayList<>();

// Adding elements
[Link]("Alice"); // Appends to the end
[Link]("Bob");
[Link]("Charlie");
[Link](1, "David"); // Inserts at index 1 (shifts Bob to index 2)

[Link]("Students: " + students); // [Alice, David, Bob, Charlie]

// Accessing elements by index


String firstStudent = [Link](0); // "Alice"
[Link]("First student: " + firstStudent);

// Modifying an element
[Link](2, "Bobby"); // Changes Bob to Bobby

// Removing elements
[Link](1); // Removes element at index 1 (David)
[Link]("Charlie"); // Removes Charlie by value

[Link]("After removals: " + students); // [Alice, Bobby]

// Checking size and emptiness


[Link]("Number of students: " + [Link]()); // 2
[Link]("Is empty? " + [Link]()); // false

// Checking if an element exists


boolean hasAlice = [Link]("Alice"); // true
[Link]("Contains Alice? " + hasAlice);

// Finding the index of an element


int index = [Link]("Bobby"); // 1
[Link]("Index of Bobby: " + index);

// Iterating using a for-each loop


[Link]("All students: ");
for (String student : students) {
[Link](student + " ");
}
[Link]();
}
}
4.2 LinkedList
LinkedList implements the List interface using a doubly-linked list data structure. In a linked
list, each element (called a node) contains the data and two references (pointers): one to the
previous node and one to the next node. Unlike ArrayList, elements are not stored
contiguously in memory.
The strengths of LinkedList are fast insertions and deletions at the beginning or middle,
provided you already have a reference to the position. Adding or removing an element only
requires updating a few references; no shifting of other elements is needed. Therefore, these
operations take constant time, O(1). The weaknesses of LinkedList are slower random access
and higher memory overhead. To access an element by index, the list must traverse from the
beginning (or end) until it reaches the desired position, taking O(n) time. Additionally, each
node stores two extra references, consuming more memory than an ArrayList.
You should choose LinkedList when you frequently insert or remove elements in the middle
of the list and when you rarely need random access by index. If you mostly add elements to
the end and access elements by index, ArrayList is the better choice.
java
import [Link].*;

public class LinkedListDemo {


public static void main(String[] args) {
// Creating a LinkedList
LinkedList<String> queue = new LinkedList<>();

// LinkedList can be used as a queue (FIFO)


[Link]("Task 1"); // Add to end
[Link]("Task 2");
[Link]("Task 3");
String nextTask = [Link](); // Remove from front
[Link]("Processing: " + nextTask); // Task 1

// LinkedList can also be used as a stack (LIFO)


LinkedList<String> stack = new LinkedList<>();
[Link]("Page 1"); // Push
[Link]("Page 2");
[Link]("Page 3");

String topPage = [Link](); // Pop


[Link]("Navigating back to: " + topPage); // Page 3

// LinkedList implements List, so it supports index operations


// But these are slower than ArrayList
LinkedList<String> names = new LinkedList<>();
[Link]("Alice");
[Link]("Bob");
[Link]("Charlie");

// Insert at beginning (efficient)


[Link]("Zoe");

// Insert at end (efficient)


[Link]("Dave");

[Link]("Names: " + names); // [Zoe, Alice, Bob, Charlie, Dave]

// Access by index (inefficient - traverses from start)


String second = [Link](1); // "Alice"
[Link]("Second name: " + second);
}
}
4.3 Comparing ArrayList and LinkedList

Operation ArrayList LinkedList

Access by index (get(index)) O(1) - very fast O(n) - slow

Add at end O(1) amortized O(1)

Add at beginning O(n) - slow O(1) - fast

Add in middle O(n) - slow O(1) with reference, else O(n)

Remove from end O(1) O(1)

Remove from beginning O(n) - slow O(1) - fast

Higher (extra references per


Memory overhead Low (only array)
node)
In practice, ArrayList is the default choice for most scenarios. Use LinkedList only when you
have a specific need for frequent insertions or deletions at the beginning or middle, or when
you need a queue or deque.

5. The Set Interface


The Set interface represents a collection that contains no duplicate elements. It models the
mathematical concept of a set. If you attempt to add an element that already exists in the set,
the operation is ignored (returns false). Sets are useful when you need to ensure uniqueness,
such as maintaining a list of unique usernames, IP addresses, or ID numbers.
The two most common implementations of Set are HashSet and TreeSet.
5.1 HashSet
HashSet is the most frequently used set implementation. It stores elements in a hash table,
using the element's hashCode() method to determine where to place the element. This
provides constant-time performance, O(1), for the basic operations: add, remove,
and contains. However, HashSet does not guarantee any specific order of elements. The order
may change over time as elements are added or removed.
Because HashSet relies on hashCode() and equals(), you must ensure that any custom objects
you store in a HashSet properly override these methods. Two objects that are considered
equal must return the same hash code.
java
import [Link].*;

public class HashSetDemo {


public static void main(String[] args) {
// Creating a HashSet
HashSet<String> uniqueNames = new HashSet<>();

// Adding elements - duplicates are ignored


[Link]("Alice");
[Link]("Bob");
[Link]("Alice"); // Ignored - already exists
[Link]("Charlie");
[Link]("Bob"); // Ignored

[Link]("Unique names: " + uniqueNames);


// Order is unpredictable! Might be [Bob, Alice, Charlie] or any order

// Checking if an element exists


boolean hasAlice = [Link]("Alice"); // true
[Link]("Contains Alice? " + hasAlice);

// Removing an element
[Link]("Bob");
[Link]("After removing Bob: " + uniqueNames);

// Iterating through a set (order not guaranteed)


[Link]("Iterating: ");
for (String name : uniqueNames) {
[Link](name + " ");
}
[Link]();

// Practical use: Removing duplicates from a list


List<String> namesWithDuplicates = [Link]("John", "Mary", "John", "Peter",
"Mary", "John");
HashSet<String> uniqueFromList = new HashSet<>(namesWithDuplicates);
[Link]("Original list with duplicates: " + namesWithDuplicates);
[Link]("After removing duplicates: " + uniqueFromList);
}
}
5.2 TreeSet
TreeSet stores elements in a red-black tree structure, which keeps the elements in sorted
order (natural order or a custom comparator). This ordering comes at a cost: basic operations
like add, remove, and contains take O(log n) time, which is slower than HashSet's O(1) but
still efficient for most applications.
TreeSet is useful when you need the elements to be stored in a specific order, such as
alphabetical order for strings or numerical order for integers. It also provides additional
methods for navigating the set, such as first(), last(), lower(), higher(), subSet(),
and headSet().
java
import [Link].*;

public class TreeSetDemo {


public static void main(String[] args) {
// Creating a TreeSet (automatically sorts in natural order)
TreeSet<Integer> numbers = new TreeSet<>();

[Link](50);
[Link](10);
[Link](30);
[Link](20);
[Link](40);
[Link]("Sorted numbers: " + numbers); // [10, 20, 30, 40, 50]

// Navigation methods
[Link]("First (smallest): " + [Link]()); // 10
[Link]("Last (largest): " + [Link]()); // 50
[Link]("Lower than 30: " + [Link](30)); // 20
[Link]("Higher than 30: " + [Link](30)); // 40

// Subset views
SortedSet<Integer> subSet = [Link](20, 45); // 20 to 45, excluding 45
[Link]("Subset 20-45: " + subSet); // [20, 30, 40]

// TreeSet with strings (alphabetical order)


TreeSet<String> words = new TreeSet<>();
[Link]("banana");
[Link]("apple");
[Link]("cherry");
[Link]("date");

[Link]("Sorted words: " + words); // [apple, banana, cherry, date]

// Custom ordering with Comparator (reverse order)


TreeSet<String> reverseOrder = new TreeSet<>([Link]());
[Link]("banana");
[Link]("apple");
[Link]("cherry");
[Link]("Reverse order: " + reverseOrder); // [cherry, banana, apple]
}
}
5.3 Comparing HashSet and TreeSet
Feature HashSet TreeSet

Ordering No guaranteed order Sorted order (natural or custom)

Performance O(1) for add, remove, contains O(log n) for add, remove, contains

Requires Proper hashCode() and equals() Elements must be Comparable or provide Compa

Null elements Allows one null Does not allow null (throws NullPointerException

Use when Order doesn't matter, need speed Need sorted order or range queries

6. The Map Interface


The Map interface is not a subinterface of Collection, but it is an integral part of the
Collections Framework. A Map stores key-value pairs, where each key maps to exactly one
value. Keys must be unique; values may have duplicates. The primary operations are put(key,
value) to store a pair, get(key) to retrieve the value associated with a key, and remove(key) to
delete a pair.
Maps are incredibly useful for building lookup tables, caches, dictionaries, and any scenario
where you need fast retrieval based on a unique identifier.
The two most common implementations are HashMap and TreeMap.
6.1 HashMap
HashMap stores key-value pairs in a hash table. Like HashSet, it provides constant-time
performance, O(1), for put, get, and remove operations, assuming a good hash
function. HashMap does not guarantee any specific order of its entries. The order may
change over time.
HashMap allows one null key and multiple null values. When you use a custom object as a
key, you must override hashCode() and equals() appropriately.
java
import [Link].*;

public class HashMapDemo {


public static void main(String[] args) {
// Creating a HashMap (key: student ID, value: student name)
HashMap<Integer, String> students = new HashMap<>();
// Adding key-value pairs
[Link](101, "Alice");
[Link](102, "Bob");
[Link](103, "Charlie");
[Link](101, "Alicia"); // Overwrites the value for key 101

[Link]("Students map: " + students);


// Output might be {101=Alicia, 102=Bob, 103=Charlie} (order not guaranteed)

// Retrieving a value by key


String student102 = [Link](102); // "Bob"
[Link]("Student with ID 102: " + student102);

// Retrieving with a key that doesn't exist returns null


String student999 = [Link](999); // null
[Link]("Student with ID 999: " + student999);

// Checking if a key exists


boolean hasKey103 = [Link](103); // true
[Link]("Has ID 103? " + hasKey103);

// Checking if a value exists


boolean hasValueAlice = [Link]("Alice"); // false (we have "Alicia")
[Link]("Has value 'Alice'? " + hasValueAlice);

// Removing a key-value pair


[Link](102);
[Link]("After removing ID 102: " + students);

// Iterating through keys


[Link]("All student IDs: ");
for (Integer id : [Link]()) {
[Link](id + " ");
}
[Link]();

// Iterating through values


[Link]("All student names: ");
for (String name : [Link]()) {
[Link](name + " ");
}
[Link]();

// Iterating through key-value pairs


[Link]("All entries:");
for ([Link]<Integer, String> entry : [Link]()) {
[Link]("ID: " + [Link]() + ", Name: " + [Link]());
}

// Practical use: Counting word frequencies


String sentence = "the quick brown fox jumps over the lazy dog the fox";
String[] words = [Link](" ");
HashMap<String, Integer> wordCount = new HashMap<>();

for (String word : words) {


[Link](word, [Link](word, 0) + 1);
}

[Link]("Word frequencies: " + wordCount);


// Output: {the=3, quick=1, brown=1, fox=2, jumps=1, over=1, lazy=1, dog=1}
}
}
6.2 TreeMap
TreeMap stores key-value pairs in a red-black tree, maintaining the keys in sorted order.
Like TreeSet, basic operations take O(log n) time. TreeMap is useful when you need to
iterate through keys in sorted order or perform range queries (e.g., find all keys between two
values).
TreeMap does not allow null keys (throws NullPointerException), but it does
allow null values.
java
import [Link].*;

public class TreeMapDemo {


public static void main(String[] args) {
// Creating a TreeMap (keys are sorted automatically)
TreeMap<String, Double> productPrices = new TreeMap<>();

[Link]("Apple", 0.50);
[Link]("Banana", 0.30);
[Link]("Cherry", 0.75);
[Link]("Date", 1.20);
[Link]("Elderberry", 2.00);

[Link]("Product prices (sorted by product name):");


for ([Link]<String, Double> entry : [Link]()) {
[Link]([Link]() + ": $" + [Link]());
}
// Output in alphabetical order: Apple, Banana, Cherry, Date, Elderberry

// Navigation methods
[Link]("First product: " + [Link]()); // Apple
[Link]("Last product: " + [Link]()); // Elderberry
[Link]("Product before Cherry: " + [Link]("Cherry")); //
Banana
[Link]("Product after Cherry: " + [Link]("Cherry")); //
Date

// Range views
SortedMap<String, Double> subMap = [Link]("Banana", "Elderberry");
[Link]("Products from Banana to before Elderberry: " + [Link]());
// [Banana, Cherry, Date]

// TreeMap with integer keys


TreeMap<Integer, String> employees = new TreeMap<>();
[Link](1003, "Charlie");
[Link](1001, "Alice");
[Link](1004, "David");
[Link](1002, "Bob");

[Link]("Employees sorted by ID: " + employees);


// {1001=Alice, 1002=Bob, 1003=Charlie, 1004=David}

// Finding the closest keys


[Link]("Entry with key >= 1002: " + [Link](1002)); //
1002=Bob
[Link]("Entry with key <= 1002: " + [Link](1002)); //
1002=Bob
}
}
6.3 Comparing HashMap and TreeMap

Feature HashMap TreeMap

Ordering No guaranteed order Keys sorted in natural or custom order


Feature HashMap TreeMap

Performance O(1) for put, get, remove O(log n) for put, get, remove

Null keys Allows one null key Does not allow null keys

Null values Allows multiple null values Allows multiple null values

Requires Proper hashCode() and equals() for keys Keys must be Comparable or provide Comp

Use when Fast lookups, order doesn't matter Need sorted keys or range operations

7. The Queue Interface


The Queue interface represents a collection designed for holding elements prior to
processing. Queues typically order elements in a FIFO (first-in-first-out) manner. The most
common implementation is LinkedList (which implements Queue), along
with PriorityQueue and ArrayDeque.
The key operations are:
 offer(element) or add(element): Inserts an element (if possible)
 poll(): Retrieves and removes the head of the queue (returns null if empty)
 remove(): Retrieves and removes the head (throws exception if empty)
 peek(): Retrieves but does not remove the head (returns null if empty)
 element(): Retrieves but does not remove the head (throws exception if empty)
java
import [Link].*;

public class QueueDemo {


public static void main(String[] args) {
// Using LinkedList as a Queue
Queue<String> taskQueue = new LinkedList<>();

// Adding tasks to the queue


[Link]("Task 1: Login");
[Link]("Task 2: Process order");
[Link]("Task 3: Send email");
[Link]("Task 4: Generate report");

[Link]("Queue: " + taskQueue);

// Peek at the head without removing


String nextTask = [Link]();
[Link]("Next task to process: " + nextTask);

// Process all tasks (FIFO order)


[Link]("\nProcessing tasks:");
while (![Link]()) {
String task = [Link]();
[Link]("Processing: " + task);
}

[Link]("Queue after processing: " + taskQueue); // Empty

// PriorityQueue - orders elements by priority (natural order or Comparator)


PriorityQueue<Integer> priorityQueue = new PriorityQueue<>();
[Link](50);
[Link](10);
[Link](30);
[Link](20);
[Link](40);

[Link]("\nPriorityQueue (head is smallest): " + priorityQueue);

[Link]("Processing by priority: ");


while (![Link]()) {
[Link]([Link]() + " ");
}
// Output: 10 20 30 40 50 (smallest first)
[Link]();

// PriorityQueue with custom comparator (largest first)


PriorityQueue<Integer> maxHeap = new PriorityQueue<>([Link]());
[Link](50);
[Link](10);
[Link](30);
[Link](20);
[Link](40);

[Link]("Processing largest first: ");


while (![Link]()) {
[Link]([Link]() + " ");
}
// Output: 50 40 30 20 10
[Link]();
}
}

8. Utility Methods: The Collections Class


The [Link] class (note the plural 's') provides a set of static utility methods that
operate on collections. These methods perform common tasks such as sorting, searching,
reversing, shuffling, and creating synchronized or unmodifiable collections.
java
import [Link].*;

public class CollectionsUtilityDemo {


public static void main(String[] args) {
// Creating a list
List<Integer> numbers = new ArrayList<>();
[Link](30);
[Link](10);
[Link](50);
[Link](20);
[Link](40);

[Link]("Original: " + numbers);

// Sorting
[Link](numbers);
[Link]("Sorted: " + numbers);

// Reverse order
[Link](numbers);
[Link]("Reversed: " + numbers);

// Shuffling (random order)


[Link](numbers);
[Link]("Shuffled: " + numbers);

// Sorting again for binary search


[Link](numbers);
int index = [Link](numbers, 30);
[Link]("Index of 30: " + index);

// Finding min and max


int min = [Link](numbers);
int max = [Link](numbers);
[Link]("Min: " + min + ", Max: " + max);

// Filling all elements with a value


[Link](numbers, 99);
[Link]("After fill(99): " + numbers);

// Creating a list with repeated elements


List<String> repeated = [Link](5, "Hello");
[Link]("nCopies: " + repeated);

// Creating an unmodifiable view (read-only)


List<Integer> unmodifiable = [Link](numbers);
[Link]("Unmodifiable: " + unmodifiable);
// [Link](100); // Throws UnsupportedOperationException

// Creating a synchronized collection (thread-safe)


List<Integer> syncList = [Link](new ArrayList<>());
// Now syncList can be safely accessed by multiple threads

// Disjoint (check if two collections have no common elements)


List<Integer> list1 = [Link](1, 2, 3);
List<Integer> list2 = [Link](4, 5, 6);
List<Integer> list3 = [Link](3, 4, 5);

[Link]("list1 and list2 disjoint? " + [Link](list1, list2)); // true


[Link]("list1 and list3 disjoint? " + [Link](list1, list3)); // false

// Frequency of an element
List<String> colors = [Link]("red", "blue", "red", "green", "red");
int redCount = [Link](colors, "red");
[Link]("'red' appears " + redCount + " times");
}
}

9. Iterating Through Collections


The Collections Framework provides several ways to iterate through elements.
Understanding these options helps you write clean, efficient code.
9.1 Traditional for loop with index (List only)
java
List<String> names = [Link]("Alice", "Bob", "Charlie");
for (int i = 0; i < [Link](); i++) {
[Link]([Link](i));
}
9.2 Enhanced for loop (for-each)
This works with any Iterable (all collections) and is the most common and readable approach.
java
List<String> names = [Link]("Alice", "Bob", "Charlie");
for (String name : names) {
[Link](name);
}
9.3 Iterator
An Iterator provides a uniform way to traverse any collection. It also allows you to safely
remove elements during iteration.
java
import [Link].*;

public class IteratorDemo {


public static void main(String[] args) {
List<String> names = new ArrayList<>();
[Link]("Alice");
[Link]("Bob");
[Link]("Charlie");
[Link]("David");

// Using Iterator
Iterator<String> iterator = [Link]();
while ([Link]()) {
String name = [Link]();
[Link](name);

// Safe removal during iteration


if ([Link]("Bob")) {
[Link](); // Removes "Bob" from the list
}
}

[Link]("After removal: " + names); // [Alice, Charlie, David]

// Without iterator, removing during a for-each loop causes


ConcurrentModificationException
// The following code is ILLEGAL:
// for (String name : names) {
// if ([Link]("Charlie")) {
// [Link](name); // Throws ConcurrentModificationException!
// }
// }
}
}
9.4 forEach method with lambda (Java 8+)
java
List<String> names = [Link]("Alice", "Bob", "Charlie");
[Link](name -> [Link](name));
// Or with method reference
[Link]([Link]::println);

10. Practical Example: Putting It All Together


The following example demonstrates using multiple collection types together to solve a
realistic problem: processing student grades.
java
import [Link].*;

public class GradeProcessor {


public static void main(String[] args) {
// Store students and their grades (Map of student ID to list of grades)
HashMap<Integer, ArrayList<Integer>> studentGrades = new HashMap<>();

// Adding data for students


[Link](101, new ArrayList<>([Link](85, 90, 78, 92)));
[Link](102, new ArrayList<>([Link](88, 76, 95, 89)));
[Link](103, new ArrayList<>([Link](70, 85, 80, 75)));
[Link](104, new ArrayList<>([Link](95, 92, 98, 96)));

// Calculate average for each student and store in a TreeMap (sorted by ID)
TreeMap<Integer, Double> averages = new TreeMap<>();

for ([Link]<Integer, ArrayList<Integer>> entry : [Link]()) {


int studentId = [Link]();
ArrayList<Integer> grades = [Link]();

double sum = 0;
for (int grade : grades) {
sum += grade;
}
double average = sum / [Link]();
[Link](studentId, average);
}

[Link]("Student Averages (sorted by ID):");


for ([Link]<Integer, Double> entry : [Link]()) {
[Link]("Student %d: %.2f%n", [Link](), [Link]());
}

// Find students with average >= 90 (Honor Roll)


Set<Integer> honorRoll = new TreeSet<>(); // Sorted set

for ([Link]<Integer, Double> entry : [Link]()) {


if ([Link]() >= 90) {
[Link]([Link]());
}
}

[Link]("\nHonor Roll Students (average >= 90): " + honorRoll);

// Find the overall class average


double totalSum = 0;
for (double avg : [Link]()) {
totalSum += avg;
}
double classAverage = totalSum / [Link]();
[Link]("Class Average: %.2f%n", classAverage);

// Using a PriorityQueue to process students by highest average first


PriorityQueue<[Link]<Integer, Double>> topStudents =
new PriorityQueue<>((a, b) -> [Link]([Link](), [Link]()));

[Link]([Link]());

[Link]("\nTop students (highest average first):");


while (![Link]()) {
[Link]<Integer, Double> entry = [Link]();
[Link]("Student %d: %.2f%n", [Link](), [Link]());
}
}
}

11. Summary and Best Practices


The Java Collections Framework provides a rich set of data structures that every Java
programmer must master. Here are the key takeaways and best practices:
Choosing the Right Collection:

When you need... Use...

An ordered list with duplicates, indexed ArrayList (default) or LinkedList (frequent insert/delete at
access ends/middle)

Unique elements, order doesn't matter HashSet

Unique elements, sorted order required TreeSet

Key-value lookups, order doesn't matter HashMap

Key-value lookups, sorted keys required TreeMap

FIFO processing Queue (using LinkedList or ArrayDeque)

Priority-based processing PriorityQueue


Best Practices:
1. Program to the interface, not the implementation. Declare your variables using the
interface type (List<String> list = new ArrayList<>();) rather than the concrete type.
This makes your code more flexible because you can change the implementation later
without changing the rest of your code.
2. Always use generics. Specify the type of elements the collection will hold. This
provides compile-time type safety and eliminates the need for casting.
3. Override hashCode() and equals() for custom objects used in HashSet or as keys
in HashMap. Failure to do so will cause unexpected behavior.
4. Use the enhanced for loop (for-each) for iteration unless you need to modify the
collection during iteration (in which case use an explicit Iterator).
5. Choose the right implementation for your performance needs. Understand the
time complexity of operations for each collection type.
6. Use the utility methods in Collections for common tasks like sorting, searching, and
creating synchronized views.
7. Be aware of null handling. ArrayList and HashMap allow
nulls; TreeSet and TreeMap do not allow null keys.
8. Use [Link]() to create read-only views of your collections
when you need to prevent modification.
The Java Collections Framework is an essential tool that will appear in virtually every Java
program you write. Mastering these interfaces and implementations will significantly
improve your productivity and the quality of your code. In the next lecture, we will build
upon these concepts as we explore file I/O and streams, where collections are often used to
store data read from files before processing.

You might also like