- Understand the Collections Framework
- Learn arrays vs collections
- Explore main collection interfaces
Introduction to Collections
Arrays work fine when you know exactly how many items you need. But what if you don't? What if you need to add or remove elements frequently? Arrays have a fixed size, which becomes limiting fast.
Java's Collections Framework solves this. It's a set of interfaces and classes for working with groups of objects—dynamically.
What is the Collections Framework?
The Collections Framework provides:
- Dynamic sizing: Collections can grow and shrink as needed
- Rich functionality: Built-in methods for searching, sorting, inserting, and removing elements
- Type safety: Using generics, you can specify what type of objects a collection can hold
- Performance optimization: Different implementations optimized for different use cases
Think of it as having a Swiss Army knife for data storage instead of just a basic knife.
Arrays vs. Collections: Understanding the Difference
Let's compare arrays with collections to understand when to use each:
Arrays
// Fixed size - must be specified at creation
String[] fruits = new String[3];
fruits[0] = "Apple";
fruits[1] = "Banana";
fruits[2] = "Orange";
// Can't easily add more fruits!
Characteristics:
- Fixed size once created
- Can store primitives directly (int, double, etc.)
- Direct index access is very fast
- Lower-level, less functionality
Collections
// Dynamic size - can grow as needed
ArrayList<String> fruits = new ArrayList<>();
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Orange");
fruits.add("Grape"); // Easy to add more!
fruits.remove("Banana"); // Easy to remove!
Characteristics:
- Dynamic sizing
- Must store objects (use wrapper classes for primitives)
- Many built-in methods
- Higher-level abstraction with rich functionality
The Main Collection Interfaces
The Collections Framework is built around several key interfaces. Let's explore the main ones:
1. Collection (Root Interface)
The parent interface for most collection types. It defines basic operations like:
add(E e)- Add an elementremove(Object o)- Remove an elementsize()- Get the number of elementsclear()- Remove all elementscontains(Object o)- Check if element exists
2. List Interface
Ordered collection that allows duplicates
List<String> names = new ArrayList<>();
names.add("Alice");
names.add("Bob");
names.add("Alice"); // Duplicates allowed
// Order is maintained: [Alice, Bob, Alice]
Use List when:
- You need to maintain insertion order
- You want to allow duplicate elements
- You need to access elements by index
- Example: A shopping list, a playlist of songs
Common implementations:
ArrayList- Best for random access and iterationLinkedList- Best for frequent insertions/deletions
3. Set Interface
Collection that does NOT allow duplicates
Set<String> uniqueNames = new HashSet<>();
uniqueNames.add("Alice");
uniqueNames.add("Bob");
uniqueNames.add("Alice"); // Will not be added again
// Result: [Alice, Bob] (or [Bob, Alice] - order not guaranteed)
Use Set when:
- You need to ensure uniqueness
- Order doesn't matter (for HashSet)
- You want to check membership efficiently
- Example: A set of unique user IDs, a collection of unique tags
Common implementations:
HashSet- Fastest, no orderingTreeSet- Sorted orderLinkedHashSet- Maintains insertion order
4. Queue Interface
Collection for holding elements prior to processing
Queue<String> tasks = new LinkedList<>();
tasks.offer("Task 1");
tasks.offer("Task 2");
tasks.offer("Task 3");
String nextTask = tasks.poll(); // Retrieves and removes "Task 1"
Use Queue when:
- You need FIFO (First-In-First-Out) behavior
- You're implementing task scheduling
- Example: Print queue, customer service line
5. Map Interface (Not part of Collection hierarchy)
Stores key-value pairs
Map<String, Integer> ages = new HashMap<>();
ages.put("Alice", 25);
ages.put("Bob", 30);
int aliceAge = ages.get("Alice"); // Returns 25
Use Map when:
- You need to associate keys with values
- You want fast lookup by key
- Example: Dictionary, phone book, configuration settings
Common implementations:
HashMap- Fastest, no orderingTreeMap- Sorted by keysLinkedHashMap- Maintains insertion order
Choosing the Right Collection
Here's a quick decision guide:
- Need key-value pairs? → Use
Map(HashMap, TreeMap) - Need unique elements only? → Use
Set(HashSet, TreeSet) - Need to maintain order and allow duplicates? → Use
List(ArrayList, LinkedList) - Need FIFO processing? → Use
Queue(LinkedList, PriorityQueue)
Generics: Type Safety with Collections
One of the most powerful features of collections is generics. They allow you to specify what type of objects a collection can hold:
// Without generics (old way - not type-safe)
ArrayList list = new ArrayList();
list.add("String");
list.add(123); // Can mix types - dangerous!
// With generics (modern way - type-safe)
ArrayList<String> stringList = new ArrayList<>();
stringList.add("String");
// stringList.add(123); // Compile error - much safer!
Benefits of generics:
- Compile-time type checking - Errors caught early
- No casting needed - Code is cleaner
- Better readability - Intent is clear
Common Collection Operations
All collections share some common operations:
List<String> items = new ArrayList<>();
// Adding elements
items.add("Item1");
items.add("Item2");
// Checking size
int size = items.size(); // Returns 2
// Checking if empty
boolean isEmpty = items.isEmpty(); // Returns false
// Checking if contains
boolean hasItem = items.contains("Item1"); // Returns true
// Removing elements
items.remove("Item1");
// Clearing all elements
items.clear();
Performance Considerations
Different collections have different performance characteristics:
| Operation | ArrayList | LinkedList | HashSet | HashMap |
|---|---|---|---|---|
| Add (end) | O(1) | O(1) | O(1) | O(1) |
| Add (middle) | O(n) | O(1)* | N/A | N/A |
| Get by index | O(1) | O(n) | N/A | N/A |
| Contains | O(n) | O(n) | O(1) | O(1) |
| Remove | O(n) | O(1)* | O(1) | O(1) |
*If you have a reference to the node
Best Practices
Use interfaces for declarations:
List<String> names = new ArrayList<>(); // Good ArrayList<String> names = new ArrayList<>(); // Less flexibleInitialize with capacity if you know the size:
List<String> names = new ArrayList<>(100); // More efficientUse the most specific collection type:
- If you need uniqueness, use Set
- If you need ordering and duplicates, use List
- If you need key-value mapping, use Map
Consider immutability:
List<String> readOnlyList = Collections.unmodifiableList(myList);
Wrapping Up
The Collections Framework gives you the flexibility to work with groups of objects the way you actually need to—dynamically sized, type-safe, and with tons of built-in functionality. Arrays still have their place for fixed-size primitive collections, but for almost everything else, you'll reach for ArrayList, HashMap, HashSet, and their cousins.
In the next lessons, we'll get hands-on with the most common collections. You'll see how ArrayList makes working with lists dead simple, how HashMap turns lookups into one-liners, and how Sets guarantee uniqueness without you having to write any checking logic.
