Skip to content

Java Collections Framework

Question

Explain Java's Collections Framework. What are the main interfaces and their implementations? How do you choose the right collection type for different use cases? What are the best practices for working with collections?

Answer

The Java Collections Framework provides a unified architecture for representing and manipulating collections of objects. It includes interfaces, implementations, and algorithms for working with collections of data.

Main Interfaces

  1. Collection Interface

    public class CollectionBasics {
        public static void collectionOperations() {
            // List - Ordered collection
            List<String> list = new ArrayList<>();
            list.add("First");
            list.add("Second");
            list.add(0, "Zero");  // Insert at specific position
    
            // Set - Unique elements
            Set<Integer> set = new HashSet<>();
            set.add(1);
            set.add(1);  // Duplicate ignored
    
            // Queue - FIFO/LIFO
            Queue<String> queue = new LinkedList<>();
            queue.offer("First");
            queue.offer("Second");
            String first = queue.poll();
    
            // Deque - Double-ended queue
            Deque<Integer> deque = new ArrayDeque<>();
            deque.addFirst(1);
            deque.addLast(2);
        }
    }
    

  2. Map Interface

    public class MapBasics {
        public static void mapOperations() {
            // HashMap - General purpose map
            Map<String, Integer> hashMap = new HashMap<>();
            hashMap.put("One", 1);
            hashMap.put("Two", 2);
    
            // TreeMap - Sorted map
            Map<String, Integer> treeMap = new TreeMap<>();
            treeMap.put("Zebra", 1);
            treeMap.put("Apple", 2);  // Automatically sorted
    
            // LinkedHashMap - Insertion order
            Map<String, Integer> linkedMap = new LinkedHashMap<>();
            linkedMap.put("First", 1);
            linkedMap.put("Second", 2);  // Maintains insertion order
        }
    }
    

Collection Implementations

  1. List Implementations

    public class ListImplementations {
        public static void listTypes() {
            // ArrayList - Dynamic array
            List<String> arrayList = new ArrayList<>();
            arrayList.add("Item");  // Fast random access
    
            // LinkedList - Doubly linked list
            List<String> linkedList = new LinkedList<>();
            linkedList.add("Item");  // Fast insertion/deletion
    
            // Vector - Thread-safe ArrayList
            List<String> vector = new Vector<>();
            vector.add("Item");  // Synchronized
    
            // Stack - LIFO
            Stack<String> stack = new Stack<>();
            stack.push("Item");
            String popped = stack.pop();
        }
    }
    

  2. Set Implementations

    public class SetImplementations {
        public static void setTypes() {
            // HashSet - Hash table
            Set<String> hashSet = new HashSet<>();
            hashSet.add("Item");  // Fast add/remove
    
            // TreeSet - Sorted set
            Set<String> treeSet = new TreeSet<>();
            treeSet.add("Item");  // Maintains order
    
            // LinkedHashSet - Insertion order
            Set<String> linkedSet = new LinkedHashSet<>();
            linkedSet.add("Item");  // Maintains insertion order
        }
    }
    

Collection Operations

  1. Basic Operations

    public class CollectionOperations {
        public static void operations() {
            List<String> list = new ArrayList<>();
    
            // Add elements
            list.add("Item");
            list.addAll(Arrays.asList("One", "Two"));
    
            // Remove elements
            list.remove("Item");
            list.removeAll(Arrays.asList("One", "Two"));
    
            // Check elements
            boolean contains = list.contains("Item");
            boolean isEmpty = list.isEmpty();
            int size = list.size();
    
            // Clear collection
            list.clear();
        }
    }
    

  2. Bulk Operations

    public class BulkOperations {
        public static void bulkOperations() {
            Set<Integer> set1 = new HashSet<>(Arrays.asList(1, 2, 3));
            Set<Integer> set2 = new HashSet<>(Arrays.asList(3, 4, 5));
    
            // Union
            Set<Integer> union = new HashSet<>(set1);
            union.addAll(set2);
    
            // Intersection
            Set<Integer> intersection = new HashSet<>(set1);
            intersection.retainAll(set2);
    
            // Difference
            Set<Integer> difference = new HashSet<>(set1);
            difference.removeAll(set2);
    
            // Subset
            boolean isSubset = set1.containsAll(set2);
        }
    }
    

Best Practices

  1. Collection Selection

    public class CollectionSelection {
        // Good - Use interface type
        private List<String> items = new ArrayList<>();
    
        // Good - Use immutable collections when possible
        private final Set<String> constants = Set.of("A", "B", "C");
    
        // Good - Use appropriate initial capacity
        private List<String> largeList = new ArrayList<>(1000);
    
        // Bad - Using raw types
        private List rawList;  // Avoid raw types
    
        // Bad - Using concrete type
        private ArrayList<String> concreteList;  // Use interface type
    }
    

  2. Thread Safety

    public class ThreadSafety {
        // Good - Use thread-safe collections
        private final List<String> threadSafeList = Collections.synchronizedList(new ArrayList<>());
    
        // Good - Use concurrent collections
        private final Queue<String> concurrentQueue = new ConcurrentLinkedQueue<>();
    
        // Good - Use CopyOnWriteArrayList for read-heavy scenarios
        private final List<String> copyOnWriteList = new CopyOnWriteArrayList<>();
    
        // Bad - Non-thread-safe collection in multi-threaded context
        private List<String> unsafeList = new ArrayList<>();  // Not thread-safe
    }
    

Common Use Cases

  1. Caching

    public class Cache {
        private final Map<String, Object> cache = new ConcurrentHashMap<>();
    
        public Object get(String key) {
            return cache.get(key);
        }
    
        public void put(String key, Object value) {
            cache.put(key, value);
        }
    
        public void remove(String key) {
            cache.remove(key);
        }
    }
    

  2. Event Queue

    public class EventQueue {
        private final Queue<Event> events = new ConcurrentLinkedQueue<>();
    
        public void addEvent(Event event) {
            events.offer(event);
        }
    
        public Event processNextEvent() {
            return events.poll();
        }
    
        public boolean hasEvents() {
            return !events.isEmpty();
        }
    }
    

Testing

  1. Collection Testing
    @Test
    public void testCollections() {
        // Test List operations
        List<String> list = new ArrayList<>();
        list.add("Item");
        assertEquals(1, list.size());
        assertTrue(list.contains("Item"));
    
        // Test Set uniqueness
        Set<Integer> set = new HashSet<>();
        set.add(1);
        set.add(1);
        assertEquals(1, set.size());
    
        // Test Map operations
        Map<String, Integer> map = new HashMap<>();
        map.put("Key", 1);
        assertEquals(1, map.get("Key"));
        assertTrue(map.containsKey("Key"));
    }
    

Common Pitfalls

  1. Concurrent Modification

    public class ConcurrentModification {
        // Bad - Concurrent modification
        public void badIteration(List<String> list) {
            for (String item : list) {
                list.remove(item);  // Throws ConcurrentModificationException
            }
        }
    
        // Good - Using iterator
        public void goodIteration(List<String> list) {
            Iterator<String> iterator = list.iterator();
            while (iterator.hasNext()) {
                String item = iterator.next();
                iterator.remove();
            }
        }
    
        // Good - Using removeIf
        public void modernIteration(List<String> list) {
            list.removeIf(item -> item.startsWith("A"));
        }
    }
    

  2. Collection Type Selection

    public class CollectionSelectionPitfalls {
        // Bad - Using ArrayList for frequent insertions
        private List<String> badList = new ArrayList<>();  // O(n) insertion
    
        // Good - Using LinkedList for frequent insertions
        private List<String> goodList = new LinkedList<>();  // O(1) insertion
    
        // Bad - Using HashSet when order matters
        private Set<String> badSet = new HashSet<>();  // No order guarantee
    
        // Good - Using LinkedHashSet when order matters
        private Set<String> goodSet = new LinkedHashSet<>();  // Maintains order
    }
    

  3. Memory Management

    public class MemoryManagement {
        // Bad - Keeping references to large collections
        private static List<byte[]> largeData = new ArrayList<>();
    
        // Good - Using weak references for caches
        private final Map<String, WeakReference<Object>> cache = new WeakHashMap<>();
    
        // Good - Clearing collections when no longer needed
        public void cleanup() {
            largeData.clear();
            System.gc();  // Suggest garbage collection
        }
    }