Skip to content

Java Collections Framework: Arrays, Lists, and Maps

Question

Explain the Java Collections Framework, including: - Arrays and their characteristics - List implementations (ArrayList, LinkedList) - Queue implementations - Set implementations - Map implementations (HashMap, TreeMap) - When to use each collection type - Performance characteristics and best practices

Answer

Arrays

  1. Basic Array Characteristics

    // Fixed-size array
    int[] numbers = new int[5];
    String[] names = {"Alice", "Bob", "Charlie"};
    
    // Multi-dimensional array
    int[][] matrix = new int[3][3];
    
    // Array operations
    numbers[0] = 1;                    // Set element
    int length = numbers.length;       // Get length
    Arrays.sort(numbers);              // Sort array
    Arrays.fill(numbers, 0);           // Fill array
    

  2. Array vs ArrayList

    // Array (fixed size)
    int[] array = new int[5];
    array[0] = 1;
    array[1] = 2;
    
    // ArrayList (dynamic size)
    ArrayList<Integer> list = new ArrayList<>();
    list.add(1);
    list.add(2);
    list.remove(0);  // Remove element
    list.add(3);     // Add element
    

Lists

  1. ArrayList Implementation

    public class ArrayListExample {
        public static void main(String[] args) {
            ArrayList<String> list = new ArrayList<>();
    
            // Adding elements
            list.add("First");
            list.add("Second");
            list.add(1, "Inserted");  // Insert at index
    
            // Accessing elements
            String first = list.get(0);
    
            // Removing elements
            list.remove("First");     // Remove by object
            list.remove(0);           // Remove by index
    
            // Checking existence
            boolean exists = list.contains("Second");
    
            // Iterating
            for (String item : list) {
                System.out.println(item);
            }
    
            // Using streams
            list.stream()
                .filter(s -> s.length() > 5)
                .forEach(System.out::println);
        }
    }
    

  2. LinkedList Implementation

    public class LinkedListExample {
        public static void main(String[] args) {
            LinkedList<Integer> list = new LinkedList<>();
    
            // Adding elements
            list.addFirst(1);     // Add at beginning
            list.addLast(2);      // Add at end
            list.add(1, 3);       // Add at index
    
            // Removing elements
            list.removeFirst();   // Remove first
            list.removeLast();    // Remove last
    
            // Queue operations
            list.offer(4);        // Add to end
            list.poll();          // Remove and return first
            list.peek();          // View first without removing
        }
    }
    

Queues

  1. Queue Implementations
    public class QueueExample {
        public static void main(String[] args) {
            // PriorityQueue (natural ordering)
            PriorityQueue<Integer> pq = new PriorityQueue<>();
            pq.offer(3);
            pq.offer(1);
            pq.offer(2);
            while (!pq.isEmpty()) {
                System.out.println(pq.poll());  // Prints: 1, 2, 3
            }
    
            // ArrayDeque (double-ended queue)
            ArrayDeque<String> deque = new ArrayDeque<>();
            deque.addFirst("First");
            deque.addLast("Last");
            deque.offerFirst("New First");
            deque.offerLast("New Last");
    
            // BlockingQueue (thread-safe)
            BlockingQueue<Integer> bq = new LinkedBlockingQueue<>();
            bq.offer(1);
            try {
                bq.put(2);  // Blocks if queue is full
                int item = bq.take();  // Blocks if queue is empty
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    

Sets

  1. Set Implementations
    public class SetExample {
        public static void main(String[] args) {
            // HashSet (unordered, O(1) operations)
            HashSet<String> hashSet = new HashSet<>();
            hashSet.add("Apple");
            hashSet.add("Banana");
            hashSet.add("Apple");  // Duplicate ignored
    
            // TreeSet (sorted, O(log n) operations)
            TreeSet<Integer> treeSet = new TreeSet<>();
            treeSet.add(3);
            treeSet.add(1);
            treeSet.add(2);
            // Prints: 1, 2, 3
            treeSet.forEach(System.out::println);
    
            // LinkedHashSet (maintains insertion order)
            LinkedHashSet<String> linkedSet = new LinkedHashSet<>();
            linkedSet.add("First");
            linkedSet.add("Second");
            linkedSet.add("Third");
            // Maintains order: First, Second, Third
            linkedSet.forEach(System.out::println);
        }
    }
    

Maps

  1. HashMap Implementation

    public class HashMapExample {
        public static void main(String[] args) {
            HashMap<String, Integer> map = new HashMap<>();
    
            // Adding key-value pairs
            map.put("One", 1);
            map.put("Two", 2);
    
            // Accessing values
            Integer value = map.get("One");
    
            // Checking existence
            boolean containsKey = map.containsKey("One");
            boolean containsValue = map.containsValue(1);
    
            // Removing entries
            map.remove("One");
    
            // Iterating
            for (Map.Entry<String, Integer> entry : map.entrySet()) {
                System.out.println(entry.getKey() + ": " + entry.getValue());
            }
    
            // Using compute methods
            map.computeIfAbsent("Three", k -> 3);
            map.computeIfPresent("Two", (k, v) -> v * 2);
        }
    }
    

  2. TreeMap Implementation

    public class TreeMapExample {
        public static void main(String[] args) {
            // Natural ordering
            TreeMap<String, Integer> map = new TreeMap<>();
            map.put("C", 3);
            map.put("A", 1);
            map.put("B", 2);
            // Prints: A=1, B=2, C=3
            map.forEach((k, v) -> System.out.println(k + "=" + v));
    
            // Custom ordering
            TreeMap<String, Integer> reverseMap = new TreeMap<>(
                Collections.reverseOrder());
            reverseMap.put("C", 3);
            reverseMap.put("A", 1);
            reverseMap.put("B", 2);
            // Prints: C=3, B=2, A=1
            reverseMap.forEach((k, v) -> System.out.println(k + "=" + v));
        }
    }
    

Performance Characteristics

  1. Time Complexity

    Collection Type    | Add    | Remove | Get    | Contains
    ------------------|--------|--------|--------|----------
    ArrayList         | O(1)*  | O(n)   | O(1)   | O(n)
    LinkedList        | O(1)   | O(1)   | O(n)   | O(n)
    HashSet           | O(1)*  | O(1)*  | O(1)*  | O(1)*
    TreeSet           | O(log n)| O(log n)| O(log n)| O(log n)
    HashMap           | O(1)*   | O(1)*  | O(1)*  | O(1)*
    TreeMap           | O(log n)| O(log n)| O(log n)| O(log n)
    

  2. Space Complexity

    Collection Type    | Space Complexity
    ------------------|------------------
    ArrayList         | O(n)
    LinkedList        | O(n)
    HashSet           | O(n)
    TreeSet           | O(n)
    HashMap           | O(n)
    TreeMap           | O(n)
    

Best Practices

  1. Collection Selection

    // Use ArrayList when:
    // - Random access is needed
    // - Size is known or small
    ArrayList<String> list = new ArrayList<>();
    
    // Use LinkedList when:
    // - Frequent insertions/deletions in middle
    // - Queue/Stack operations needed
    LinkedList<String> linkedList = new LinkedList<>();
    
    // Use HashSet when:
    // - Unique elements needed
    // - Order doesn't matter
    HashSet<String> set = new HashSet<>();
    
    // Use TreeSet when:
    // - Sorted unique elements needed
    TreeSet<String> treeSet = new TreeSet<>();
    
    // Use HashMap when:
    // - Key-value pairs needed
    // - Order doesn't matter
    HashMap<String, Integer> map = new HashMap<>();
    
    // Use TreeMap when:
    // - Sorted key-value pairs needed
    TreeMap<String, Integer> treeMap = new TreeMap<>();
    

  2. Thread Safety

    // Synchronized collections
    List<String> syncList = Collections.synchronizedList(new ArrayList<>());
    Map<String, Integer> syncMap = Collections.synchronizedMap(new HashMap<>());
    
    // Concurrent collections
    ConcurrentHashMap<String, Integer> concurrentMap = new ConcurrentHashMap<>();
    CopyOnWriteArrayList<String> concurrentList = new CopyOnWriteArrayList<>();
    

  3. Initial Capacity

    // Specify initial capacity for better performance
    ArrayList<String> list = new ArrayList<>(1000);
    HashMap<String, Integer> map = new HashMap<>(1000, 0.75f);
    

Testing

public class CollectionsTest {
    @Test
    public void testArrayList() {
        ArrayList<String> list = new ArrayList<>();
        list.add("Test");
        assertEquals("Test", list.get(0));
        assertEquals(1, list.size());
    }

    @Test
    public void testHashMap() {
        HashMap<String, Integer> map = new HashMap<>();
        map.put("Key", 1);
        assertEquals(Integer.valueOf(1), map.get("Key"));
        assertTrue(map.containsKey("Key"));
    }

    @Test
    public void testSet() {
        HashSet<String> set = new HashSet<>();
        set.add("Test");
        set.add("Test");  // Duplicate
        assertEquals(1, set.size());  // Only one unique element
    }
}

Conclusion

  • Choose collections based on:
  • Access patterns
  • Size requirements
  • Ordering needs
  • Thread safety requirements
  • Consider performance implications
  • Use appropriate initial capacities
  • Consider thread safety when needed
  • Use collections that best match your use case