Java Collections Framework: Interfaces, Data Structures, and Generics
Java Collections Framework
1. Collection Interface
1.1 Arrays vs Collections
Similarities:
- Both are containers for storing multiple elements
Differences:
-
li>Array size is fixed, while collections can dynamically resize
- Arrays can store both primitive data types and objects
- Collections only store objects (primitives must use their wrapper classes)
1.2 Collection Class Hierarchy
1.3 Collection Interface Overview
The Collection interface is the root interface in the collection hierarchy. It represents a group of objects, known as elements. The Java Development Kit (JDK) doesn't provide any direct implementations of this interface; instead, it offers more specific subinterfaces like Set and List.
To create a Collection instance, use polymorphism with a concrete implementation class such as ArrayList:
Collection<String> collection = new ArrayList<>();
1.4 Common Collection Methods
| Method | Description |
|---|---|
| boolean add(E e) | Adds an element to the collection |
| boolean remove(Object o) | Removes a specified element from the collection |
| boolean removeIf(Predicate<? super E> filter) | Removes elements that match the given condition |
| void clear() | Removes all elements from the collection |
| boolean contains(Object o) | Checks if the collection contains a specified element |
| boolean isEmpty() | Checks if the collection is empty |
| int size() | >Returns the number of elements in the collection
1.5 Collection Iteration
The Iterator is a special traversal mechanism for collections. You obtain an iterator using the iterator() method:
Iterator<String> iterator = collection.iterator();
Iterator methods:
- boolean hasNext(): Checks if there are more elements to iterate
- E next(): Returns the next element and advances the iterator
Example of iterating through a collection:
Collection<String> names = new ArrayList<>();
names.add("Alice");
names.add("Bob");
names.add("Charlie");
Iterator<String> it = names.iterator();
while (it.hasNext()) {
String name = it.next();
System.out.println(name);
}
Removing elements during iteration:
ArrayList<String> items = new ArrayList<>();
items.add("apple");
items.add("banana");
items.add("banana");
items.add("orange");
Iterator<String> it = items.iterator();
while(it.hasNext()) {
String item = it.next();
if("banana".equals(item)) {
it.remove(); // Removes the current element
}
}
System.out.println(items); // Output: [apple, orange]
1.6 Enhanced For Loop
Introduced in Java 5, the enhanced for loop simplifies array and collection iteration. It internally uses an Iterator.
Only classes implementing the Iterable interface can use this loop.
Syntax:
for (DataType variable : collectionOrArray) {
// Use variable to access current element
}
Example:
ArrayList<String> fruits = new ArrayList<>();
fruits.add("apple");
fruits.add("banana");
fruits.add("cherry");
for (String fruit : fruits) {
System.out.println(fruit);
}
2. List Interface
2.1 List Overview and Characteristics
The List interface extends Collection and represents an ordered collection (also known as a sequence). Users have precise control over where each element is inserted. Elements can be accessed by their integer index (position).
Characteristics:
-
li>Ordered - maintains insertion order
- Duplicate elements allowed li>Indexed - supports positional access
2.2 List-Specific Methods
| Method | Description |
|---|---|
| void add(int index, E element) | Inserts an element at the specified position |
| E remove(int index) | Removes the element at the specified position and returns it |
| E set(int index, E element) | Replaces the element at the specified position and returns the replaced element |
| E get(int index) | Returns the element at the specified position |
3. Data Structures
3.1 Stack and Queue
Stack:
-
li>Last-In-First-Out (LIFO) structure
- Elements added last are removed first
Queue:
-
li>First-In-First-Out (FIFO) structure
li>Elements added first are removed first
3.2 Arrays and Linked Lists
Array:
-
li>Fast access by index (O(1) time complexity)
li>Slow insertion/deletion (O(n) time complexity)
Linked List:
-
li>Slow access by index (O(n) time complexity)
li>Fast insertion/deletion (O(1) time complexity)
4. List Implementations
4.1 ArrayList vs LinkedList
ArrayList:
-
li>Backed by an array
li>Fast random access
li>Slow insertion/deletion in the middle
LinkedList:
-
li>Backed by a doubly-linked list
li>Fast insertion/deletion
li>Slow random access
4.2 LinkedList-Specific Methods
| Method | Description |
|---|---|
| void addFirst(E e) | li>Inserts the specified element at the beginning of this list|
| void addLast(E e) | li>Appends the specified element to the end of this list|
| E getFirst() | li>Returns the first element in this list|
| E getLast() | li>Returns the last element in this list|
| E removeFirst() | li>Removes and returns the first element from this list|
| E removeLast() | li>Removes and returns the last element from this list
5. Generics
5.1 Generic Overview
Generics, introduced in Java 5, provide compile-time type safety. They allow you to define classes, interfaces, and methods with type parameters.
Benefits:
-
li>Catch type errors at compile time rather than runtime
li>Eliminate the need for explicit type casting
Generic syntax:
-
li><Type>: Specifies a single type parameter
li><Type1, Type2, ...>: Specifies multiple type parameters
5.2 Generic Classes
Definition syntax:
class ClassName<Type> { }
Example:
class Box<T> {
private T content;
public void setContent(T content) {
this.content = content;
}
public T getContent() {
return content;
}
}
Usage:
Box<String> stringBox = new Box<>();
stringBox.setContent("Hello Generics");
System.out.println(stringBox.getContent());
Box<Integer> intBox = new Box<>();
intBox.setContent(123);
System.out.println(intBox.getContent());
5.3 Generic Methods
Definition syntax:
<Type> ReturnType methodName(Type param) { }
Example:
<nclass Utility {
public <T> void printArray(T[] array) {
for (T element : array) {
System.out.print(element + " ");
}
System.out.println();
}
}</code>
Usage:
Utility utility = new Utility();
Integer[] intArray = {1, 2, 3, 4, 5};
String[] strArray = {"A", "B", "C"};
utility.printArray(intArray);
utility.printArray(strArray);
5.4 Generic Interfaces
Definition syntax:
interface InterfaceName<Type> { }
Example:
interface Container<T> {
void add(T item);
T get(int index);
}
Implementation approaches:
- Define implementation with same generic type:
class ListContainer<T> implements Container<T> {
private List<T> items = new ArrayList<>();
@Override
public void add(T item) {
items.add(item);
}
@Override
public T get(int index) {
return items.get(index);
}
}
- Define implementation with specific type:
class StringContainer implements Container<String> {
private List<String> items = new ArrayList<>();
@Override
public void add(String item) {
items.add(item);
}
@Override
public String get(int index) {
return items.get(index);
}
}
5.5 Type Wildcards
Unbounded Wildcard (?):
-
li>Represents an unknown type
li>Can accept any type but can't add elements
Upper Bounded Wildcard (? extends Type):
-
li>Accepts the specified type or its subtypes
Lower Bounded Wildcard (? super Type):
-
li>Accepts the specified type or its supertypes
Example usage:
public class WildcardDemo {
// Unbounded wildcard
public static void printList(List<?> list) {
for (Object elem : list) {
System.out.print(elem + " ");
}
System.out.println();
}
// Upper bounded wildcard
public static double sumOfList(List<? extends Number> list) {
double sum = 0.0;
for (Number num : list) {
sum += num.doubleValue();
}
return sum;
}
// Lower bounded wildcard
public static void addNumbers(List<? super Integer> list) {
for (int i = 1; i <= 5; i++) {
list.add(i);
}
}
}
Difference between wildcards and type parameters:
-
li>Type parameters (T) must be declared before the return type
li>Wildcards (?) can be used directly without declaration
// Type parameter approach
private static <T> void processList(List<T> list) { }
// Wildcard approach
private static void processList(List<?> list) { }