Mastering Maps, Concurrent Collections, and Advanced Java Collections
In the previous weeks articles, we explored the List, Set, and Queue interfaces and their implementations. We understood their structures, performance characteristics, and real-world use cases. Now, let’s dive into the Map interface, which is used to store key-value pairs, and explore advanced topics like concurrent collections, custom sorting, and Stream API integration. This article will help you master the more advanced aspects of the Java Collection Framework.
1. Introduction to the Map Interface
The Map interface represents a collection of key-value pairs. Unlike List, Set, and Queue, the Map interface does not extend the Collection interface because it operates on pairs of objects (keys and values) rather than individual elements.
Key Features of Map
2. Map Implementations
The Map interface has several implementations, each with its own use cases and performance characteristics.
2.1 HashMap
Access/Insertion/Deletion: O(1) (average case), but O(n) in the worst case due to hash collisions.
Example: Using HashMap
import java.util.HashMap;
import java.util.Map;
public class HashMapExample {
public static void main(String[] args) {
Map<String, Integer> fruitPrices = new HashMap<>();
fruitPrices.put("Apple", 50);
fruitPrices.put("Banana", 20);
fruitPrices.put("Cherry", 30);
System.out.println("Fruit Prices: " + fruitPrices);
}
}
2.2 LinkedHashMap
Access/Insertion/Deletion: O(1) (average case), but slightly more memory-intensive than HashMap.
Example: Using LinkedHashMap
import java.util.LinkedHashMap;
import java.util.Map;
public class LinkedHashMapExample {
public static void main(String[] args) {
Map<String, Integer> orderedFruitPrices = new LinkedHashMap<>();
orderedFruitPrices.put("Apple", 50);
orderedFruitPrices.put("Banana", 20);
orderedFruitPrices.put("Cherry", 30);
System.out.println("Ordered Fruit Prices: " + orderedFruitPrices);
// Output: {Apple=50, Banana=20, Cherry=30}
}
}
2.3 TreeMap
Access/Insertion/Deletion: O(log n)
TreeMap does not allow null keys, but allows null values.
Example: Using TreeMap
import java.util.TreeMap;
import java.util.Map;
public class TreeMapExample {
public static void main(String[] args) {
Map<String, Integer> sortedFruitPrices = new TreeMap<>();
sortedFruitPrices.put("Banana", 20);
sortedFruitPrices.put("Apple", 50);
sortedFruitPrices.put("Cherry", 30);
System.out.println("Sorted Fruit Prices: " + sortedFruitPrices);
// Output: {Apple=50, Banana=20, Cherry=30}
}
}
3. Concurrent Collections
Concurrent collections are designed for use in multi-threaded environments. They provide thread-safe operations without the need for external synchronization.
3.1 ConcurrentHashMap
Access/Insertion/Deletion: O(1) (average case)
Uses segmented locking (pre-Java 8) and CAS-based updates (Java 8+) for improved concurrency.
Example: Using ConcurrentHashMap
import java.util.concurrent.ConcurrentHashMap;
import java.util.Map;
public class ConcurrentHashMapExample {
public static void main(String[] args) {
Map<String, Integer> concurrentFruitPrices = new ConcurrentHashMap<>();
concurrentFruitPrices.put("Apple", 50);
concurrentFruitPrices.put("Banana", 20);
System.out.println("Concurrent Fruit Prices: " + concurrentFruitPrices);
// Output: {Apple=50, Banana=20}
}
}
Recommended by LinkedIn
3.2 CopyOnWriteArrayList
Access: O(1)
Insertion/Deletion: O(n) (due to array copying on modification)
Trade-off: It is efficient for read-heavy use cases but not suitable for frequent modifications due to copying overhead.
Example: Using CopyOnWriteArrayList
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.List;
public class CopyOnWriteArrayListExample {
public static void main(String[] args) {
List<String> threadSafeFruits = new CopyOnWriteArrayList<>();
threadSafeFruits.add("Apple");
threadSafeFruits.add("Banana");
System.out.println("Thread-Safe Fruits: " + threadSafeFruits);
// Output: [Apple, Banana]
}
}
4. Custom Sorting
Java provides two ways to sort collections: Comparable and Comparator.
4.1 Comparable
Example: Using Comparable
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
class Fruit implements Comparable<Fruit> {
String name;
int price;
Fruit(String name, int price) {
this.name = name;
this.price = price;
}
@Override
public int compareTo(Fruit other) {
return this.price - other.price;
}
@Override
public String toString() {
return name + "=" + price;
}
}
public class ComparableExample {
public static void main(String[] args) {
List<Fruit> fruits = new ArrayList<>();
fruits.add(new Fruit("Apple", 50));
fruits.add(new Fruit("Banana", 20));
fruits.add(new Fruit("Cherry", 30));
Collections.sort(fruits);
System.out.println("Sorted Fruits: " + fruits);
// Output: [Banana=20, Cherry=30, Apple=50]
}
}
4.2 Comparator
Example: Using Comparator
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
class Fruit {
String name;
int price;
Fruit(String name, int price) {
this.name = name;
this.price = price;
}
@Override
public String toString() {
return name + "=" + price;
}
}
public class ComparatorExample {
public static void main(String[] args) {
List<Fruit> fruits = new ArrayList<>();
fruits.add(new Fruit("Apple", 50));
fruits.add(new Fruit("Banana", 20));
fruits.add(new Fruit("Cherry", 30));
Comparator<Fruit> priceComparator = (f1, f2) -> f1.price - f2.price;
Collections.sort(fruits, priceComparator);
System.out.println("Sorted Fruits: " + fruits);
// Output: [Banana=20, Cherry=30, Apple=50]
}
}
5. Stream API with Collections
The Stream API (introduced in Java 8) allows you to process collections in a functional style.
Example: Using Stream API
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class StreamAPIExample {
public static void main(String[] args) {
List<String> fruits = Arrays.asList("Apple", "Banana", "Cherry");
// Filter and collect
List<String> filteredFruits = fruits.stream()
.filter(f -> f.startsWith("A"))
.collect(Collectors.toList());
System.out.println("Filtered Fruits: " + filteredFruits);
// Output: [Apple]
}
}
6. Performance Considerations
Here’s a quick comparison of the performance of common Map implementations:
7. Real-World Use Cases
8. Conclusion
In this article, we explored the Map interface, concurrent collections, and advanced topics like custom sorting and the Stream API. These concepts are essential for writing efficient and scalable Java applications. With this knowledge, you can confidently choose the right collection for your use case and leverage advanced features to optimize your code.
To further enhance your Java skills, consider exploring:
For a complete learning journey, follow me: Eugene Koshy