Weeks
JavaWeek 6 · Collections, Iterators and Maps
Week 6 Notes

Java,
Week 6

Programming Concepts using Java, Week 6: the benefits of indirection, the Collection interface and iterators, concrete collections (List, Set, Queue, Deque, PriorityQueue) and maps (HashMap, TreeMap, LinkedHashMap). Every Java example in these notes was really compiled and run.

6 chapters19 worked examples~51 min read
IndirectionCollectionIteratorList / Set / QueueMapLRU cacheInterview prep

Chapters

  1. 01The Benefits of IndirectionLecture 1
  2. 02CollectionLecture 2
  3. 03Concrete CollectionsLecture 3
  4. 04MapsLecture 4
  5. 05Interview Question BankInterview
  6. 06Week 6 Summary SheetSummary
01
Lecture 1

The Benefits of Indirection

Weeks 4 and 5 gave us interfaces and generics. Week 6 puts them to work in the library Java ships with: the Collections framework. This first lecture explains the idea underneath it all, indirection: putting an interface between the code that uses a data structure and the code that implements it.

1.1Abstract data types

Abstract data typeAn abstract data type separates the public interface (what you may do) from the private implementation (how it is done). A generic queue shows the idea:
  • add(E element) puts an element at the rear,
  • E remove() takes one from the front,
  • int size() says how many there are.

Behind that interface the implementer can choose a circular array or a linked list, and the public interface does not change.

circular array: one allocation, bounded sizev4v5v1v2v3wraps aroundheadtaillinked list: grows as neededv0v1vn-2vn-1. . .nullheadtail
Figure 1. Two implementations of the same queue: a circular array and a linked list

The interface hides the choice, but the two are not equivalent. They differ in things the interface does not say:

AspectCircular arrayLinked list
Efficiencybetter: one-time storage allocationa node is allocated for every element
Flexibilitybounded size: it can get fullbetter: grows as long as memory lasts

1.2Two implementations, built for real

The slides only show the headers ({ ... }), so here are working versions. A circular array stores the head index and a count, and the position of the next free slot wraps around with %:

CircularArrayQueue.java
public class CircularArrayQueue<E> implements Queue<E> {
    private Object[] items;
    private int head = 0;
    private int count = 0;

    public CircularArrayQueue(int capacity) {
        items = new Object[capacity];             // one-time storage allocation
    }

    public void add(E element) {
        if (count == items.length) {
            throw new IllegalStateException("queue is full");   // bounded size
        }
        items[(head + count) % items.length] = element;
        count++;
    }

    @SuppressWarnings("unchecked")
    public E remove() {
        if (count == 0) throw new IllegalStateException("queue is empty");
        E e = (E) items[head];
        items[head] = null;
        head = (head + 1) % items.length;
        count--;
        return e;
    }

    public int size() { return count; }
}
Main.java
public class Main {
    public static void main(String[] args) {
        CircularArrayQueue<Integer> q = new CircularArrayQueue<Integer>(3);
        q.add(1); q.add(2); q.add(3);        // array is full
        System.out.println(q.remove());      // head moves on
        q.add(4);                            // the tail wraps around to slot 0
        System.out.println(q.remove() + " " + q.remove() + " " + q.remove());
    }
}
Output
1
2 3 4

The array was full when the 4 arrived? No: remove() had freed slot 0 first, and the new element went around the end into it. That reuse is why it is called circular. The price is a fixed capacity: with all slots taken, add has nowhere to put the element.

1.3Users choose the implementation: the problem

If the two classes are separate (CircularArrayQueue<E> and LinkedListQueue<E>), the user decides by declaring variables of the concrete class:

Main.java
public class Main {
    public static void main(String[] args) {
        CircularArrayQueue<String> q = new CircularArrayQueue<String>(3);
        q = new LinkedListQueue<String>();    // the variable was declared with the concrete class
    }
}
Error
Main.java:4: error: incompatible types: LinkedListQueue<String> cannot be converted to CircularArrayQueue<String>
        q = new LinkedListQueue<String>();    // the variable was declared with the concrete class
            ^
1 error

The declaration is welded to the class. Later we may need a queue of dates that can grow, and then we must change the declaration of dateq, every function header that takes it, every auxiliary variable, and so on.

1.4Adding indirection: a Queue interface

Instead, create a Queue<E> interface and let the concrete classes implement it. Variables are then declared with the interface type, and only the instantiation names a concrete class:

no indirectionCircularArrayQueue<Date> dateqCircularArrayQueue (fixed)want a LinkedListQueue instead?edit the declaration, every functionheader, every helper variable ...with the interfaceQueue<Date> dateqinterface Queue<E>CircularArrayQueueLinkedListswapping = change only the new ... line
Figure 2. Without the interface the concrete type spreads through the program; with it, only one line names the class
Queue.java
public interface Queue<E> {
    void add(E element);
    E remove();
    int size();
}
Main.java
public class Main {
    public static void main(String[] args) {
        Queue<String> q = new LinkedListQueue<String>();    // declared by the interface
        q.add("a");
        q.add("b");
        q.add("c");
        System.out.println(q.remove() + q.remove() + " left: " + q.size());
    }
}
Output
ab left: 1

Now see the benefit. The first run uses the array queue with room for only three; the fifth add fails:

Main.java
public class Main {
    public static void main(String[] args) {
        Queue<Integer> q = new CircularArrayQueue<Integer>(3);   // the ONE line to change
        for (int i = 1; i <= 5; i++) {
            q.add(i);
        }
        System.out.println(q.size());
    }
}
Error
Exception in thread "main" java.lang.IllegalStateException: queue is full
	at CircularArrayQueue.add(CircularArrayQueue.java:12)
	at Main.main(Main.java:5)

To get a queue that can grow, change one line, the instantiation. Nothing else in the program is touched:

Main.java
public class Main {
    public static void main(String[] args) {
        Queue<Integer> q = new LinkedListQueue<Integer>();       // changed: nothing else touched
        for (int i = 1; i <= 5; i++) {
            q.add(i);
        }
        System.out.println(q.size());
    }
}
Output
5

The same holds for functions. A method whose parameter is a Queue<String> accepts any implementation, today's and ones written next year:

Main.java
public class Main {
    static void fill(Queue<String> q) {                 // works for any implementation
        q.add("x");
        q.add("y");
        System.out.println(q.getClass().getSimpleName() + " has " + q.size());
    }

    public static void main(String[] args) {
        fill(new CircularArrayQueue<String>(4));
        fill(new LinkedListQueue<String>());
    }
}
Output
CircularArrayQueue has 2
LinkedListQueue has 2

What the compiler checks

An interface cannot be instantiated (Week 4), and a class that claims to implement it must provide every method:

Main.java
public class Main {
    public static void main(String[] args) {
        Queue<String> q = new Queue<String>();
    }
}
Error
Main.java:3: error: Queue is abstract; cannot be instantiated
        Queue<String> q = new Queue<String>();
                          ^
1 error
Broken.java
public class Broken<E> implements Queue<E> {
    public void add(E element) { }
    public E remove() { return null; }
}
Error
Broken.java:1: error: Broken is not abstract and does not override abstract method size() in Queue
public class Broken<E> implements Queue<E> {
       ^
1 error
Extra: a name clash. Java already has a java.util.Queue. The Queue above is the lecture's own interface in the default package; the examples in the next lectures use the real one with an explicit import java.util.*. Do not mix the two in one file.

1.5Indirection in real life

The lecture's analogy: an organisation gives senior staff an office car.

StepArrangementIndirection?
1Each official has an assigned car. What if it breaks down?none: concrete
2A pool of office cars: use any that is available.a level of indirection
3Do not want to maintain a pool? Contract a taxi service.another level
4Do not want to negotiate tenders? Reimburse the taxi bills.another level
"Fundamental theorem of software engineering"

"All problems in computer science can be solved by another level of indirection." (Butler Lampson, Turing Award 1992.) The usual tail of the joke: ...except the problem of too many levels of indirection (extra).

1.6Lecture summary

  • Use interfaces to flexibly choose between multiple concrete implementations.
  • Interfaces add a level of indirection.
  • Declare with the interface type; only the instantiation names the concrete class.
Next · Lecture 2Collection
02
Lecture 2

Collection

Most languages ship built-in collective types: arrays, lists, dictionaries. Early Java had a pile of such classes, each a separate concrete choice. This lecture shows the cure: organise them by functionality as a hierarchy of abstract interfaces and concrete implementations, with indirection in between.

2.1Why a hierarchy

Java originally had Vector, Stack, Hashtable, BitSet ... You chose the one you needed, and changing the choice meant many updates (the Lecture 1 problem again). These legacy classes still exist (extra); new code uses the framework below.

Collection<E>List<E>Set<E>Queue<E>Deque<E>AbstractListAbstractSetAbstractQueueArrayListLinkedListHashSetTreeSetPriorityQueueArrayDeque *amber = abstract classes with default code. * ArrayDeque extends AbstractCollection directly.LinkedList is a List and a Deque. HashMap and TreeMap are not Collections.
Figure 3. The framework: interfaces on top, abstract classes with default code, concrete classes at the bottom

2.2The Collection interface

CollectionThe Collection interface abstracts the properties of grouped data: arrays, lists, sets, ... but not key-value structures like dictionaries (those are maps, Lecture 4).
  • add(): add to the collection
  • iterator(): get an object that implements Iterator; use it to loop through the elements

The Iterator<E> of Week 4, now in its real form: next(), hasNext(), remove() (the names are hasNext/next, not Week 4's teaching names has_next/get_next):

Main.java
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

public class Main {
    public static void main(String[] args) {
        Collection<String> cstr = new ArrayList<String>();
        cstr.add("one");
        cstr.add("two");
        cstr.add("three");

        Iterator<String> iter = cstr.iterator();
        while (iter.hasNext()) {
            String element = iter.next();
            System.out.println(element.toUpperCase());
        }
    }
}
Output
ONE
TWO
THREE

Java later added the for-each loop, which implicitly creates an iterator and runs through it. It is the same loop, shorter:

Main.java
import java.util.ArrayList;
import java.util.Collection;

public class Main {
    public static void main(String[] args) {
        Collection<String> cstr = new ArrayList<String>();
        cstr.add("one");
        cstr.add("two");

        for (String element : cstr) {           // the compiler builds the iterator for us
            System.out.println(element);
        }
    }
}
Output
one
two

2.3Generic functions on collections

Because ArrayList, HashSet and the rest are all Collections, one generic function serves them all (Week 5's type parameters at work):

Main.java
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;

public class Main {
    public static <E> boolean contains(Collection<E> c, Object obj) {
        for (E element : c) {
            if (element.equals(obj)) {          // every E is an Object: equals() always exists
                return true;
            }
        }
        return false;
    }

    public static void main(String[] args) {
        Collection<String> list = new ArrayList<String>(List.of("a", "b", "c"));
        Collection<Integer> set = new HashSet<Integer>(List.of(10, 20, 30));
        System.out.println(contains(list, "b"));
        System.out.println(contains(set, 25));
        System.out.println(contains(set, 20));
    }
}
Output
true
false
true

The lecture asks: how does element.equals(obj) work? E is an arbitrary type variable, so what guarantees an equals method? Every class inherits equals from Object (Week 3), and a type variable's upper bound is Object (Week 5). So the call is always legal. Whether it compares the way you want depends on the class overriding equals.

2.4Removing elements: Iterator.remove()

Iterator.remove() removes the element that was last returned by next(). Picture the iterator as standing between elements, never on one:

startabcdstanding before the first elementnext()abcdpassed a: last returned = aremove()bcda is gone: nothing to remove nowremove()bcdIllegalStateExceptionorange bar = the iterator: it stands between elements, never on one
Figure 4. remove() deletes what next() just passed; a second remove() with no next() in between is an error
  • When the iterator starts, it stands before the first element: "basically over nothing".
  • The first next() passes the first element and stands right before the second.
  • Now remove() deletes the first element (the last one next() accessed).
  • Calling remove() again immediately is an error: no new element was passed.
Source slip. The lecture's loop deletes with element.remove(), calling remove on the element (a String), which has no such method. It must be the iterator's iter.remove():
Main.java
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

public class Main {
    public static void main(String[] args) {
        Collection<String> cstr = new ArrayList<String>();
        Iterator<String> iter = cstr.iterator();
        while (iter.hasNext()) {
            String element = iter.next();
            if (element.length() == 4) {
                element.remove();               // the slide's line
            }
        }
    }
}
Error
Main.java:12: error: cannot find symbol
                element.remove();               // the slide's line
                       ^
  symbol:   method remove()
  location: variable element of type String
1 error
Main.java
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        Collection<String> cstr = new ArrayList<String>(List.of("tea", "coffee", "milk", "cola"));
        Iterator<String> iter = cstr.iterator();
        while (iter.hasNext()) {
            String element = iter.next();
            if (element.length() == 4) {        // delete the element if it has some property
                iter.remove();                  // removes the element next() just returned
            }
        }
        System.out.println(cstr);
    }
}
Output
[tea, coffee]

The two failure cases, run for real. Removing twice in a row, and removing before any next(), both throw IllegalStateException:

Main.java
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> names = new ArrayList<String>(List.of("a", "b", "c"));
        Iterator<String> iter = names.iterator();
        iter.next();
        iter.remove();
        iter.remove();                          // no next() in between
    }
}
Error
Exception in thread "main" java.lang.IllegalStateException
	at java.base/java.util.ArrayList$Itr.remove(ArrayList.java:1062)
	at Main.main(Main.java:11)
Main.java
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> names = new ArrayList<String>(List.of("a", "b", "c"));
        Iterator<String> iter = names.iterator();
        iter.remove();                          // standing before the first element
    }
}
Error
Exception in thread "main" java.lang.IllegalStateException
	at java.base/java.util.ArrayList$Itr.remove(ArrayList.java:1062)
	at Main.main(Main.java:9)

To remove consecutive elements, interleave a next(). To remove the very first element, access it first:

Main.java
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> names = new ArrayList<String>(List.of("a", "b", "c", "d"));
        Iterator<String> iter = names.iterator();

        iter.next();        // a
        iter.remove();
        iter.next();        // b: interleave a next() before the second remove()
        iter.remove();
        System.out.println(names);
    }
}
Output
[c, d]
Related (Week 4). Removing from the list itself inside a for-each loop, instead of through the iterator, breaks the loop with ConcurrentModificationException:
Main.java
import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> names = new ArrayList<String>(List.of("a", "b", "c"));
        for (String s : names) {
            if (s.equals("a")) names.remove(s);         // changes the list behind the iterator's back
        }
    }
}
Error
Exception in thread "main" java.util.ConcurrentModificationException
	at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1095)
	at java.base/java.util.ArrayList$Itr.next(ArrayList.java:1049)
	at Main.main(Main.java:7)

2.5The rest of Collection

Collection defines a much larger set of abstract methods than add and iterator:

MethodWhat it does
int size()number of elements
boolean isEmpty()true when there are none
boolean contains(Object obj)is an equal element present?
boolean containsAll(Collection<?> c)is every element of c present?
boolean addAll(Collection<? extends E> from)add elements from a compatible collection
boolean remove(Object obj)remove one matching element
boolean removeAll(Collection<?> c)remove every element that is present in c
boolean equals(Object other)compare with another collection
Main.java
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        Collection<String> c = new ArrayList<String>();
        System.out.println(c.isEmpty());
        c.add("x"); c.add("y"); c.add("z");
        System.out.println(c.size() + " " + c.contains("y"));
        System.out.println(c.containsAll(List.of("x", "z")));

        c.addAll(List.of("p", "q"));                    // copy in another collection
        System.out.println(c);

        c.remove("y");                                  // Collection.remove(Object): one value
        c.removeAll(List.of("p", "q"));                 // remove every element that is in the argument
        System.out.println(c);
    }
}
Output
true
3 true
true
[x, y, z, p, q]
[x, z]
Source slip. The listing runs two methods together: int size() boolean isEmpty(); is missing a semicolon after size(). As written it does not compile:
Main.java
public interface Main {
    int size() boolean isEmpty();
}
Error
Main.java:2: error: ';' expected
    int size() boolean isEmpty();
              ^
1 error

Two points the lecture makes. addAll(from) takes a Collection<? extends E>: the wildcard from Week 5 lets you copy a list of Integer into a list of Number, but not the reverse. And Collection.remove(Object) is quite different from Iterator.remove(): one looks for a value, the other deletes the last-returned element.

Main.java
import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<Integer> ints = List.of(1, 2);
        List<Number> numbers = new ArrayList<Number>();
        numbers.addAll(ints);                   // addAll(Collection<? extends E>): Integers are Numbers
        System.out.println(numbers);

        List<Integer> target = new ArrayList<Integer>();
        target.addAll(numbers);                 // the wrong way round
    }
}
Error
Main.java:12: error: incompatible types: List<Number> cannot be converted to Collection<? extends Integer>
        target.addAll(numbers);                 // the wrong way round
                      ^
Note: Some messages have been simplified; recompile with -Xdiags:verbose to get full output
1 error

2.6The AbstractCollection class

To implement Collection a class must write all those methods, which is tedious. The lecture's cure is default implementations. They live not in the interface but in an abstract class, AbstractCollection<E> implements Collection<E>, that builds everything it can on top of iterator() and size(), for example:

AbstractCollection.java (the idea)
public abstract Iterator<E> iterator();   // abstract: depends on the representation

public boolean contains(Object obj) {          // default: built on iterator()
    for (E element : this) {
        if (element.equals(obj)) return true;
    }
    return false;
}

A concrete class now extends AbstractCollection, defines iterator() for its own representation (and size()), and may override contains() if it can do better. Here a tiny array-backed Bag writes just those two and gets contains, containsAll, isEmpty, toString and for-each free:

Bag.java
import java.util.AbstractCollection;
import java.util.Iterator;

public class Bag<E> extends AbstractCollection<E> {
    private Object[] items = new Object[10];
    private int n = 0;

    public void put(E e) { items[n++] = e; }

    public int size() { return n; }

    public Iterator<E> iterator() {                 // the only representation-specific method besides size()
        return new Iterator<E>() {
            private int pos = 0;
            public boolean hasNext() { return pos < n; }
            @SuppressWarnings("unchecked")
            public E next() { return (E) items[pos++]; }
        };
    }
}
Main.java
import java.util.List;

public class Main {
    public static void main(String[] args) {
        Bag<String> bag = new Bag<String>();
        bag.put("red");
        bag.put("green");
        bag.put("blue");

        System.out.println(bag.contains("green"));      // inherited: built on iterator()
        System.out.println(bag.containsAll(List.of("red", "blue")));
        System.out.println(bag.isEmpty());              // inherited: built on size()
        System.out.println(bag);                        // inherited toString()
        for (String s : bag) {                          // for-each works: it is Iterable
            System.out.print(s.length() + " ");
        }
        System.out.println();
    }
}
Output
true
true
false
[red, green, blue]
3 5 4 

The defaults only go as far as the missing pieces allow. This Bag never overrode add, so the inherited add refuses:

Main.java
public class Main {
    public static void main(String[] args) {
        Bag<String> bag = new Bag<String>();
        bag.add("x");                                   // add() was never overridden
    }
}
Error
Exception in thread "main" java.lang.UnsupportedOperationException
	at java.base/java.util.AbstractCollection.add(AbstractCollection.java:253)
	at Main.main(Main.java:4)
Extra: optional operations. This is the framework's convention: methods such as add and Iterator.remove are optional; a collection that does not support one throws UnsupportedOperationException. The same happens for a plain iterator (its default remove says "unsupported") and for immutable collections like List.of:
Main.java
import java.util.Iterator;

public class Main {
    public static void main(String[] args) {
        Iterator<String> it = new Iterator<String>() {
            int i = 0;
            public boolean hasNext() { return i < 2; }
            public String next() { return "v" + i++; }
        };
        it.next();
        it.remove();                // remove() is optional: the default says unsupported
    }
}
Error
Exception in thread "main" java.lang.UnsupportedOperationException: remove
	at java.base/java.util.Iterator.remove(Iterator.java:102)
	at Main.main(Main.java:11)
Main.java
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> fixed = List.of("a", "b");
        fixed.add("c");
    }
}
Error
Exception in thread "main" java.lang.UnsupportedOperationException
	at java.base/java.util.ImmutableCollections.uoe(ImmutableCollections.java:142)
	at java.base/java.util.ImmutableCollections$AbstractImmutableCollection.add(ImmutableCollections.java:147)
	at Main.main(Main.java:6)
Note on the lecture's wording. The notes say "a correct solution: provide default implementations in the interface itself", and then use an abstract class. Since Java 8 an interface can carry default methods (Week 4), and Iterator does; the collection classes still use AbstractCollection because it can also hold state and a constructor, which an interface cannot.

2.7Lecture summary

  • The Collection interface captures abstract properties of collections: add an element, create an iterator, ...
  • Use the for-each loop to avoid the explicit iterator.
  • Write generic functions that operate on collections.
  • Collection defines many more abstract functions; implementing each is tedious, so AbstractCollection gives defaults.
  • Concrete collections extend AbstractCollection.
Next · Lecture 3Concrete Collections
03
Lecture 3

Concrete Collections

A bare Collection says nothing about order, duplicates, or how elements may be added and removed. Sub-interfaces capture exactly those extra properties: List, Set and Queue. Each has an abstract class with default code and concrete classes underneath.

QuestionInterface that answers it
Are the elements ordered?List
Are duplicates allowed?Set (no duplicates)
Are there constraints on adding and removing?Queue, Deque

3.1The List interface

An ordered collection can be accessed in two ways: through an iterator or by position (random access). List adds the positional methods:

Main.java
import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("b");                    // Collection.add: append
        list.add("c");
        list.add(0, "a");                 // List.add(index, e): insert at a position
        System.out.println(list);

        System.out.println(list.get(1));  // by position
        System.out.println(list.set(1, "B"));   // set returns the old element
        String removed = list.remove(2);  // remove(int) returns the removed element
        System.out.println(removed + " " + list);
    }
}
Output
[a, b, c]
b
b
c [a, B]
Source slip. The slide lists void remove(int index). The real method is E remove(int index): it returns the element it removed, which the example above uses. Likewise ListIterator.previous() is listed as void previous() but returns the previous element.

ListIterator

  • ListIterator extends Iterator.
  • void add(E element): insert an element before the current position.
  • E previous() goes to the previous element; boolean hasPrevious() checks that going back is legal.
Main.java
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;

public class Main {
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>(List.of("a", "c", "d"));
        ListIterator<String> it = list.listIterator();

        it.next();                                  // passes "a"
        it.add("b");                                // inserted BEFORE the current position
        System.out.println(list);

        it.next();                                  // passes "c"
        System.out.println(it.hasPrevious());       // legal to go backwards?
        String back = it.previous();                // previous() returns the element, not void
        System.out.println(back);

        it.set("C");                                // replaces what previous() just returned
        System.out.println(list);
    }
}
Output
[a, b, c, d]
true
c
[a, b, C, d]

3.2Random access: not equally cheap

In an array the location of element i can be computed. In a linked list you must start at the beginning and traverse i links.

ArrayList: position i is computed01234567get(5): one jump, address = base + 5 x sizeLinkedList: walk i links from the start012345get(5): five hops. A loop calling get(i) for every i rescans from the start each time.RandomAccess is a tagging interface: ArrayList has it, LinkedList does not.
Figure 5. An array jumps to position i; a linked list walks there

The tagging interface RandomAccess (it has no methods) tells you whether a List supports fast random access, so you can choose the algorithm:

Main.java
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.RandomAccess;

public class Main {
    static int sum(List<Integer> c) {
        int total = 0;
        if (c instanceof RandomAccess) {            // a tagging interface: no methods
            for (int i = 0; i < c.size(); i++) {    // random access algorithm
                total += c.get(i);
            }
        } else {
            for (int x : c) {                       // sequential access algorithm
                total += x;
            }
        }
        return total;
    }

    public static void main(String[] args) {
        List<Integer> a = new ArrayList<Integer>(List.of(1, 2, 3));
        List<Integer> l = new LinkedList<Integer>(List.of(1, 2, 3));
        System.out.println((a instanceof RandomAccess) + " " + (l instanceof RandomAccess));
        System.out.println(sum(a) + " " + sum(l));
    }
}
Output
true false
6 6

The consequence for the loop everyone writes. For a LinkedList the methods from AbstractList are still available (get(i) works), but the loop with get(i) starts a fresh scan from the start to element i in every iteration. This run compares them (the booleans, not the raw times, since times vary):

Main.java
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

public class Main {
    static long viaGet(List<Integer> list) {
        long t = System.nanoTime();
        long s = 0;
        for (int i = 0; i < list.size(); ++i) s += list.get(i);     // a fresh scan to i each time
        return System.nanoTime() - t;
    }

    static long viaIterator(List<Integer> list) {
        long t = System.nanoTime();
        long s = 0;
        for (int x : list) s += x;                                  // one pass
        return System.nanoTime() - t;
    }

    public static void main(String[] args) {
        List<Integer> linked = new LinkedList<Integer>();
        List<Integer> array = new ArrayList<Integer>();
        for (int i = 0; i < 30000; i++) { linked.add(i); array.add(i); }

        long linkedGet = viaGet(linked), linkedIter = viaIterator(linked), arrayGet = viaGet(array);
        System.out.println("LinkedList: get(i) loop slower than iterator? " + (linkedGet > 20 * linkedIter));
        System.out.println("ArrayList get(i) faster than LinkedList get(i)? " + (arrayGet * 20 < linkedGet));
    }
}
Output
LinkedList: get(i) loop slower than iterator? true
ArrayList get(i) faster than LinkedList get(i)? true
Rule

Use for-each (an iterator) on any list, or get(i) only when the list is a RandomAccess one. The loop for (i...) list.get(i) on a linked list is quadratic.

3.3AbstractList and the concrete lists

  • AbstractCollection is a usable version of Collection; correspondingly AbstractList extends AbstractCollection and inherits its defaults.
  • AbstractSequentialList extends AbstractList: a further sub-class for lists without random access.
  • LinkedList<E> extends AbstractSequentialList: the usual flexible linked list, efficient to add and remove at arbitrary positions, no random access.
  • ArrayList<E> extends AbstractList: a flexible-size array that supports random access.
Main.java
import java.util.AbstractCollection;
import java.util.AbstractList;
import java.util.AbstractSequentialList;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;

public class Main {
    public static void main(String[] args) {
        System.out.println(AbstractCollection.class.isAssignableFrom(AbstractList.class));
        System.out.println(AbstractList.class.isAssignableFrom(AbstractSequentialList.class));
        System.out.println(AbstractSequentialList.class.isAssignableFrom(LinkedList.class));
        System.out.println(AbstractList.class.isAssignableFrom(ArrayList.class));
        System.out.println(AbstractSequentialList.class.isAssignableFrom(ArrayList.class));
        System.out.println(Collection.class.isAssignableFrom(java.util.HashMap.class));
    }
}
Output
true
true
true
true
false
false

The six lines say: AbstractList is a AbstractCollection (true), AbstractSequentialList is an AbstractList (true), a LinkedList is an AbstractSequentialList (true), an ArrayList is an AbstractList (true) but not an AbstractSequentialList (false); and a HashMap is not a Collection (false), which is Lecture 4.

Source slip. The slides call AbstractList "the AbstractList interface". It is an abstract class, like AbstractCollection.

Two versions of add()

  • add() from Collection appends to the end and returns a boolean: "did the add update the collection?"
  • A set may not change (a duplicate); a list always accepts.
  • add() from ListIterator inserts before the current position and returns void.
Main.java
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;

public class Main {
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        Set<String> set = new HashSet<String>();

        System.out.println(list.add("x"));         // Collection.add returns boolean: did the collection change?
        System.out.println(list.add("x"));         // a list always accepts
        System.out.println(set.add("x"));
        System.out.println(set.add("x"));          // a set refuses a duplicate: false

        ListIterator<String> it = list.listIterator();
        it.add("y");                               // ListIterator.add returns void
        System.out.println(list);
    }
}
Output
true
true
true
false
[y, x, x]
Main.java
import java.util.ArrayList;
import java.util.ListIterator;

public class Main {
    public static void main(String[] args) {
        ListIterator<String> it = new ArrayList<String>().listIterator();
        boolean b = it.add("y");
    }
}
Error
Main.java:7: error: incompatible types: void cannot be converted to boolean
        boolean b = it.add("y");
                          ^
1 error

3.4The Set interface

A set is a collection without duplicates. The Set interface has the same methods as Collection but a more constrained behaviour:

  • add() has no effect and returns false if the element already exists.
  • equals() is true if the contents match, disregarding order.
  • Use a Set to enforce "no duplicates" in your data.
Main.java
import java.util.HashSet;
import java.util.Set;
import java.util.TreeSet;

public class Main {
    public static void main(String[] args) {
        Set<String> hs = new HashSet<String>();
        hs.add("banana"); hs.add("apple"); hs.add("cherry");
        System.out.println(hs.add("apple") + " " + hs.size());   // duplicate: no effect, false

        Set<String> ts = new TreeSet<String>(hs);                // same contents, sorted
        System.out.println(ts);
        System.out.println(hs.equals(ts));                       // equal contents, order ignored
        System.out.println(hs);                                  // HashSet order: unspecified
    }
}
Output
false 3
[apple, banana, cherry]
true
[banana, apple, cherry]

Set implementations are designed for efficient membership tests. A list must scan a sequence to find an element. A set instead maps the value to its position (a hash function) or arranges values in a 2D structure (a balanced search tree). As usual, concrete sets extend AbstractSet, which extends AbstractCollection.

HashSet: map the value to a slot012345pearfigplumcollisionh("fig") = 2: check one slot, fastTreeSet: balanced search treefigapplepearbananacherryplumin-order = sorted; O(log n)
Figure 6. HashSet maps a value to a slot; TreeSet keeps a sorted search tree
HashSet: a hash table
  • Underlying storage is an array; map value v to position h(v).
  • If h(v) is free, store v there. Otherwise a collision: there are different strategies to handle it.
  • Membership is fast: look at position h(v).
  • Unordered, yet iterator() visits every element exactly once, in unspecified order.
TreeSet: a tree
  • Values are ordered: it maintains a sorted collection.
  • The iterator visits elements in sorted order.
  • Insertion is more complex than in a hash table: time O(log n) for n elements.

Hashing is only as good as equals() and hashCode()

The slides stop at "map a value to a position". The position comes from hashCode(), and the contract (Week 3) is: equal objects must have equal hash codes. A class that overrides equals but not hashCode breaks the set silently (extra):

Point.java
public class Point {
    private final int x, y;
    public Point(int x, int y) { this.x = x; this.y = y; }

    @Override
    public boolean equals(Object o) {
        if (!(o instanceof Point)) return false;
        Point p = (Point) o;
        return x == p.x && y == p.y;
    }
    // hashCode() NOT overridden
}
Main.java
import java.util.HashSet;
import java.util.Set;

public class Main {
    public static void main(String[] args) {
        Set<Point> set = new HashSet<Point>();
        set.add(new Point(1, 2));
        set.add(new Point(1, 2));                      // equal to the first one
        System.out.println(set.size());
        System.out.println(set.contains(new Point(1, 2)));
    }
}
Output
2
false

The two equal points hashed to different slots, so the set never compared them. Adding hashCode() fixes it:

Point.java
public class Point {
    private final int x, y;
    public Point(int x, int y) { this.x = x; this.y = y; }

    @Override
    public boolean equals(Object o) {
        if (!(o instanceof Point)) return false;
        Point p = (Point) o;
        return x == p.x && y == p.y;
    }

    @Override
    public int hashCode() { return 31 * x + y; }       // equal points give equal hashes
}
Output
1
true

A TreeSet needs a different capability: an ordering. Elements must be Comparable (Week 4's idea), or you pass a Comparator:

Main.java
import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;

public class Main {
    public static void main(String[] args) {
        Set<Job> plain = new TreeSet<Job>();
        plain.add(new Job("print", 2));            // no way to order a Job
    }
}
Error
Exception in thread "main" java.lang.ClassCastException: class Job cannot be cast to class java.lang.Comparable (Job is in unnamed module of loader 'app'; java.lang.Comparable is in module java.base of loader 'bootstrap')
	at java.base/java.util.TreeMap.compare(TreeMap.java:1604)
	at java.base/java.util.TreeMap.addEntryToEmptyMap(TreeMap.java:811)
	at java.base/java.util.TreeMap.put(TreeMap.java:820)
	at java.base/java.util.TreeMap.put(TreeMap.java:569)
	at java.base/java.util.TreeSet.add(TreeSet.java:259)
	at Main.main(Main.java:8)
Main.java
import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;

public class Main {
    public static void main(String[] args) {
        Set<Job> byPriority = new TreeSet<Job>(Comparator.comparingInt(j -> j.priority));
        byPriority.add(new Job("print", 2));
        byPriority.add(new Job("email", 1));
        byPriority.add(new Job("backup", 3));
        System.out.println(byPriority);            // visited in sorted order
    }
}
Output
[email, print, backup]

3.5The Queue interface

A queue is ordered: remove from the front, insert at the rear. Queue supports:

PurposeStrict (flags an error)Gentle (returns false / null)
insert at the rearboolean add(E e)boolean offer(E e)
remove the frontE remove()E poll()
inspect the head, no updateE element()E peek()
Queue: insert at the rear, remove at the frontabcdremove / pollelement / peekadd / offererror vs gentle: add, remove, element throw; offer, poll, peek return false / nullDeque: both endsabcdaddFirst offerFirstpollFirst peekFirstgetFirstaddLast offerLastpollLast peekLastgetLastPriorityQueue: remove() returns the highest priority item, not the oldest
Figure 7. Queue operations come in a strict and a gentle form; a Deque works at both ends

"If the queue is full, add() flags an error; if empty, remove() flags an error." The gentle versions return false or null instead. The empty-queue case, run for real:

Main.java
import java.util.ArrayDeque;
import java.util.Queue;

public class Main {
    public static void main(String[] args) {
        Queue<String> q = new ArrayDeque<String>();
        System.out.println(q.offer("a") + " " + q.offer("b"));
        System.out.println(q.peek() + " " + q.element());      // inspect the head, no update
        System.out.println(q.poll() + " " + q.remove());       // remove from the front
        System.out.println(q.poll() + " " + q.peek());         // empty: null, not an exception
        q.remove();                                            // empty: this one throws
    }
}
Error
true true
a a
a b
null null
Exception in thread "main" java.util.NoSuchElementException
	at java.base/java.util.ArrayDeque.removeFirst(ArrayDeque.java:361)
	at java.base/java.util.ArrayDeque.remove(ArrayDeque.java:522)
	at Main.main(Main.java:11)

The full-queue case needs a bounded queue (an ArrayDeque just grows). ArrayBlockingQueue has a fixed capacity:

Main.java
import java.util.Queue;
import java.util.concurrent.ArrayBlockingQueue;

public class Main {
    public static void main(String[] args) {
        Queue<String> q = new ArrayBlockingQueue<String>(2);   // a bounded queue
        q.add("a");
        q.add("b");
        System.out.println(q.offer("c"));                      // gentle: false
        q.add("c");                                            // strict: error
    }
}
Error
false
Exception in thread "main" java.lang.IllegalStateException: Queue full
	at java.base/java.util.AbstractQueue.add(AbstractQueue.java:98)
	at java.base/java.util.concurrent.ArrayBlockingQueue.add(ArrayBlockingQueue.java:329)
	at Main.main(Main.java:10)

Deque: a double-ended queue

  • addFirst, addLast, offerFirst, offerLast
  • pollFirst, pollLast, getFirst, getLast, peekFirst, peekLast
Main.java
import java.util.ArrayDeque;
import java.util.Deque;

public class Main {
    public static void main(String[] args) {
        Deque<Integer> dq = new ArrayDeque<Integer>();
        dq.addFirst(2);
        dq.addLast(3);
        dq.offerFirst(1);
        System.out.println(dq);
        System.out.println(dq.getFirst() + " " + dq.peekLast());
        System.out.println(dq.pollFirst() + " " + dq.pollLast() + " " + dq);

        dq.push(7);                                            // a Deque is also a stack
        dq.push(8);
        System.out.println(dq.pop() + " " + dq.pop());
    }
}
Output
[1, 2, 3]
1 3
1 3 [2]
8 7
Source slip. The slide lists boolean addFirst(E) and boolean addLast(E). They return void; the offer... versions are the ones that return boolean:
Main.java
import java.util.ArrayDeque;
import java.util.Deque;

public class Main {
    public static void main(String[] args) {
        Deque<Integer> dq = new ArrayDeque<Integer>();
        boolean b = dq.addFirst(1);                            // the slide says boolean
    }
}
Error
Main.java:7: error: incompatible types: void cannot be converted to boolean
        boolean b = dq.addFirst(1);                            // the slide says boolean
                               ^
1 error

PriorityQueue and the concrete queues

  • PriorityQueue: remove() returns the highest priority item.
  • LinkedList implements Queue (it is a list and a queue).
  • ArrayDeque: a circular array deque (Lecture 1's idea).
Main.java
import java.util.Collections;
import java.util.PriorityQueue;

public class Main {
    public static void main(String[] args) {
        PriorityQueue<Integer> pq = new PriorityQueue<Integer>();
        pq.add(5); pq.add(1); pq.add(4); pq.add(2); pq.add(3);
        System.out.println(pq);                                // internal heap order, NOT sorted
        System.out.print("remove(): ");
        while (!pq.isEmpty()) System.out.print(pq.remove() + " ");   // smallest first
        System.out.println();

        PriorityQueue<Integer> max = new PriorityQueue<Integer>(Collections.reverseOrder());
        max.add(5); max.add(1); max.add(4);
        System.out.println(max.remove());                      // now the largest is "highest priority"
    }
}
Output
[1, 2, 4, 5, 3]
remove(): 1 2 3 4 5 
5

With natural ordering "highest priority" means the smallest value. Printing a PriorityQueue shows the internal heap order, which is not sorted; only remove() gives sorted output. A Comparator reverses it.

Main.java
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

public class Main {
    public static void main(String[] args) {
        LinkedList<String> ll = new LinkedList<String>();
        Queue<String> asQueue = ll;                    // a LinkedList is a Queue ...
        List<String> asList = ll;                      // ... and a List

        asQueue.offer("first");
        asQueue.offer("second");
        System.out.println(asList.get(1));
        System.out.println(asQueue.poll());
    }
}
Output
second
first

3.6Lecture summary

  • Different kinds of collection are specified by sub-interfaces: List, Set, Queue.
  • List allows random access and a more functional ListIterator.
  • Set forbids duplicates; Queue restricts add and remove.
  • Each interface has a version under AbstractCollection; concrete classes extend AbstractList, AbstractSet, AbstractQueue.
Next · Lecture 4Maps
04
Lecture 4

Maps

Collections group values. A map stores key-value pairs, like a dictionary: look up a value by its key. Java keeps maps in their own interface, Map<K,V>, outside the Collection hierarchy.

Main.java
import java.util.Collection;
import java.util.HashMap;

public class Main {
    public static void main(String[] args) {
        Collection<String> c = new HashMap<String, Integer>();
    }
}
Error
Main.java:6: error: incompatible types: HashMap<String,Integer> cannot be converted to Collection<String>
        Collection<String> c = new HashMap<String, Integer>();
                               ^
1 error

4.1The Map interface

  • Two type parameters: K for keys, V for values.
  • V get(Object key) fetches the value for a key; V put(K key, V value) updates the value for a key.
  • The keys form a set: only one entry per key.
  • Putting a fresh value under an existing key overwrites the old value, and put returns the previous value (or null).
Main.java
import java.util.HashMap;
import java.util.Map;

public class Main {
    public static void main(String[] args) {
        Map<String, Integer> ages = new HashMap<String, Integer>();
        System.out.println(ages.put("Asha", 21));          // no earlier value: null
        System.out.println(ages.put("Asha", 22));          // returns the PREVIOUS value; overwrites
        ages.put("Ravi", 30);

        System.out.println(ages.get("Asha"));
        System.out.println(ages.get("Meena"));             // absent key: null
        System.out.println(ages.size());                   // one entry per key
        System.out.println(ages.containsKey("Ravi") + " " + ages.containsValue(99));
    }
}
Output
null
21
22
null
2
true false
Source slip. The slide lists two methods both named containsKey: containsKey(Object key) and containsKey(Object value). The second must be containsValue. As written the interface cannot even compile (same signature twice):
MyMap.java
public interface MyMap<K, V> {
    V get(Object key);
    boolean containsKey(Object key);
    boolean containsKey(Object value);
}
Error
MyMap.java:4: error: method containsKey(Object) is already defined in interface MyMap
    boolean containsKey(Object value);
            ^
1 error

4.2Updating a map: the initialisation problem

Key-value stores accumulate quantities: word frequencies in a text, total runs in a tournament. The problem: update the value if the key exists; otherwise create a new entry. A naive put(k, get(k) + 1) fails for a missing key because get returns null, and unboxing it (Week 5) throws:

Main.java
import java.util.HashMap;
import java.util.Map;

public class Main {
    public static void main(String[] args) {
        Map<String, Integer> scores = new HashMap<String, Integer>();
        scores.put("bat", scores.get("bat") + 10);         // get() is null: unboxing a null
    }
}
Error
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "java.lang.Integer.intValue()" because the return value of "java.util.Map.get(Object)" is null
	at Main.main(Main.java:7)

Three tools solve it. The lecture's examples use an undeclared name bat; read it as a key, here the string "Asha".

ToolWhat it does
getOrDefault(key, 0)returns the value, or the default if the key is absent; then put(k, getOrDefault(k, 0) + n).
putIfAbsent(key, 0)initialises a missing key, then put(k, get(k) + n) is safe.
merge(key, n, Integer::sum)if the key is absent, store n; otherwise combine the current value with n using Integer::sum.
Main.java
import java.util.HashMap;
import java.util.Map;

public class Main {
    public static void main(String[] args) {
        Map<String, Integer> scores = new HashMap<String, Integer>();
        String bat = "Asha";

        // 1. getOrDefault
        scores.put(bat, scores.getOrDefault(bat, 0) + 10);
        // 2. putIfAbsent, then update
        scores.putIfAbsent(bat, 0);
        scores.put(bat, scores.get(bat) + 5);
        // 3. merge
        scores.merge(bat, 20, Integer::sum);
        scores.merge("Ravi", 20, Integer::sum);            // missing key: initialised to 20

        System.out.println(scores);
    }
}
Output
{Ravi=20, Asha=35}

The word-frequency problem from the lecture, with the first tool (the order printed is HashMap's, see 4.5):

Main.java
import java.util.HashMap;
import java.util.Map;

public class Main {
    public static void main(String[] args) {
        String text = "to be or not to be";
        Map<String, Integer> freq = new HashMap<String, Integer>();
        for (String w : text.split(" ")) {
            freq.put(w, freq.getOrDefault(w, 0) + 1);      // initialise-or-update in one line
        }
        System.out.println(freq);
    }
}
Output
{not=1, be=2, or=1, to=2}
Extra: Integer::sum. Integer::sum is a method reference (Java 8): shorthand for the function (a, b) -> Integer.sum(a, b), i.e. a + b. merge calls it only when the key already has a value.

4.3Extracting keys and values: views

Map<K,V>"Asha"21"Ravi"30"Meena"27keySet() -> Set<K>keys form a setvalues() -> Collection<V>may repeatentrySet() -> Set<Map.Entry<K,V>>key + value togetherthese are views: removing through them removes from the map
Figure 8. A map exposes three views: its keys as a Set, its values as a Collection, its pairs as a Set of entries
  • Set<K> keySet()
  • Collection<V> values()
  • Set<Map.Entry<K,V>> entrySet()

Keys form a Set (no repeats) while values form an arbitrary Collection (values may repeat). The key-value pairs form a set over a special type, Map.Entry. Java calls these views. Iterating a map means iterating a view; use entrySet() to get each key and its value without looking the map up again:

Main.java
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

public class Main {
    public static void main(String[] args) {
        Map<String, Integer> staff = new TreeMap<String, Integer>();
        staff.put("Asha", 50000);
        staff.put("Ravi", 60000);
        staff.put("Meena", 55000);

        Set<String> keys = staff.keySet();
        for (String key : keys) {
            System.out.println("key " + key);
        }

        for (Map.Entry<String, Integer> entry : staff.entrySet()) {   // key and value, no second lookup
            String k = entry.getKey();
            Integer v = entry.getValue();
            System.out.println(k + " earns " + v);
        }

        System.out.println(staff.values());
    }
}
Output
key Asha
key Meena
key Ravi
Asha earns 50000
Meena earns 55000
Ravi earns 60000
[50000, 55000, 60000]

A view is live, not a copy: removing through keySet() or values() removes from the map, and Entry.setValue writes through (extra). A view that has no way to represent the change refuses it: a key set cannot add, because there is no value to pair with.

Main.java
import java.util.Map;
import java.util.TreeMap;

public class Main {
    public static void main(String[] args) {
        Map<String, Integer> m = new TreeMap<String, Integer>();
        m.put("a", 1); m.put("b", 2); m.put("c", 3);

        m.keySet().remove("a");                            // a view: removing here removes from the map
        for (Map.Entry<String, Integer> e : m.entrySet()) {
            e.setValue(e.getValue() * 10);                 // writes through to the map
        }
        System.out.println(m);

        m.values().remove(30);                             // by value
        System.out.println(m);
    }
}
Output
{b=20, c=30}
{b=20}
Main.java
import java.util.HashMap;
import java.util.Map;

public class Main {
    public static void main(String[] args) {
        Map<String, Integer> m = new HashMap<String, Integer>();
        m.keySet().add("k");                               // a key set cannot add on its own
    }
}
Error
Exception in thread "main" java.lang.UnsupportedOperationException
	at java.base/java.util.AbstractCollection.add(AbstractCollection.java:253)
	at Main.main(Main.java:7)
Extra: do not add keys while iterating. As with lists (Lecture 2), changing the map's structure during a loop over a view breaks the iterator:
Main.java
import java.util.HashMap;
import java.util.Map;

public class Main {
    public static void main(String[] args) {
        Map<String, Integer> m = new HashMap<String, Integer>();
        m.put("a", 1); m.put("b", 2);
        for (String k : m.keySet()) {
            m.put(k + "x", 0);                             // adding a key while iterating
        }
    }
}
Error
Exception in thread "main" java.util.ConcurrentModificationException
	at java.base/java.util.HashMap$HashIterator.nextNode(HashMap.java:1605)
	at java.base/java.util.HashMap$KeyIterator.next(HashMap.java:1628)
	at Main.main(Main.java:8)

4.4Concrete maps

put order: pear, apple, fig, bananaHashMapbananaapplepearfigno promised orderTreeMapapplebananafigpearsorted by keyLinkedHashMappearapplefigbananainsertion orderthe HashMap order shown is what one real run printed: it can differ between JDKs
Figure 9. The same four puts give three different iteration orders
HashMap
  • Works like HashSet: a hash table stores the keys and values.
  • No fixed order for the keys returned by keySet().
TreeMap
  • Like TreeSet: a balanced search tree stores the entries.
  • An iterator over keySet() processes the keys in sorted order.
LinkedHashMap
  • Remembers the order in which keys were inserted: the hash table entries are also connected as a (doubly) linked list.
  • Iterators over keySet() and values() go in insertion order. There is a similar LinkedHashSet.
Main.java
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TreeMap;

public class Main {
    static void fill(Map<String, Integer> m) {
        m.put("pear", 1); m.put("apple", 2); m.put("fig", 3); m.put("banana", 4);
    }

    public static void main(String[] args) {
        Map<String, Integer> hash = new HashMap<String, Integer>();
        Map<String, Integer> tree = new TreeMap<String, Integer>();
        Map<String, Integer> linked = new LinkedHashMap<String, Integer>();
        fill(hash); fill(tree); fill(linked);

        System.out.println("HashMap       " + hash.keySet());      // no promise
        System.out.println("TreeMap       " + tree.keySet());      // sorted
        System.out.println("LinkedHashMap " + linked.keySet());    // insertion order
    }
}
Output
HashMap       [banana, apple, pear, fig]
TreeMap       [apple, banana, fig, pear]
LinkedHashMap [pear, apple, fig, banana]

The HashMap line is what one real run printed; the language promises no order, and a different JDK may print another. The other two orders are guaranteed.

Main.java
import java.util.TreeMap;

public class Main {
    public static void main(String[] args) {
        TreeMap<Integer, String> t = new TreeMap<Integer, String>();
        t.put(30, "c"); t.put(10, "a"); t.put(20, "b");
        System.out.println(t);                             // keys in sorted order
        System.out.println(t.firstKey() + " " + t.lastKey());
        System.out.println(t.headMap(30));                 // keys strictly below 30
    }
}
Output
{10=a, 20=b, 30=c}
10 30
{10=a, 20=b}

Access order: a least-recently-used cache

A LinkedHashMap can use access order instead: every get() and put() moves that pair to the end of the list, so processing the entries goes in least recently used order. That is the basis of scheduling and caching.

Main.java
import java.util.LinkedHashMap;
import java.util.Map;

public class Main {
    public static void main(String[] args) {
        Map<String, Integer> m = new LinkedHashMap<String, Integer>(16, 0.75f, true);   // access order
        m.put("a", 1); m.put("b", 2); m.put("c", 3);
        System.out.println(m.keySet());

        m.get("a");                                        // a get moves the pair to the end
        System.out.println(m.keySet());
        m.put("b", 20);                                    // so does a put on an existing key
        System.out.println(m.keySet());                    // least recently used first
    }
}
Output
[a, b, c]
[b, c, a]
[c, a, b]
startabcoldest to newestget("a")bcaa moves to the endput("d")cadbevictedred = least recently used (the front). Each get or put moves a pair to the end.
Figure 10. Access order: the front of the list is the next to be evicted

Extra: a complete cache needs one more step, evicting the oldest entry when full. Overriding removeEldestEntry does it:

LruCache.java
import java.util.LinkedHashMap;
import java.util.Map;

public class LruCache<K, V> extends LinkedHashMap<K, V> {
    private final int capacity;

    public LruCache(int capacity) {
        super(16, 0.75f, true);                            // access order
        this.capacity = capacity;
    }

    @Override
    protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
        return size() > capacity;                          // drop the least recently used
    }
}
Main.java
public class Main {
    public static void main(String[] args) {
        LruCache<String, Integer> cache = new LruCache<String, Integer>(3);
        cache.put("a", 1); cache.put("b", 2); cache.put("c", 3);
        cache.get("a");                                    // a is now the most recently used
        cache.put("d", 4);                                 // evicts b, the least recently used
        System.out.println(cache.keySet());
    }
}
Output
[c, a, d]

Null keys, mutable keys (extra)

Main.java
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;

public class Main {
    public static void main(String[] args) {
        Map<String, Integer> h = new HashMap<String, Integer>();
        h.put(null, 1);                                    // HashMap allows one null key
        System.out.println(h.get(null));

        Map<String, Integer> t = new TreeMap<String, Integer>();
        t.put(null, 1);                                    // TreeMap must compare keys
    }
}
Error
1
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "java.lang.Comparable.compareTo(Object)" because "k1" is null
	at java.base/java.util.TreeMap.compare(TreeMap.java:1604)
	at java.base/java.util.TreeMap.addEntryToEmptyMap(TreeMap.java:811)
	at java.base/java.util.TreeMap.put(TreeMap.java:820)
	at java.base/java.util.TreeMap.put(TreeMap.java:569)
	at Main.main(Main.java:12)

A map is only as reliable as its keys. If a key is changed after insertion so that its hash changes, the entry becomes unreachable:

Main.java
import java.util.HashMap;
import java.util.Map;

public class Main {
    public static void main(String[] args) {
        Map<Key, String> m = new HashMap<Key, String>();
        Key k = new Key(1);
        m.put(k, "value");
        k.id = 2;                                          // the key's hash changes after insertion
        System.out.println(m.get(k));
        System.out.println(m.get(new Key(1)));
        System.out.println(m.size());
    }
}
Output
null
null
1
Rule

Use immutable keys (String, Integer ...). If you write your own key class, override both equals and hashCode and do not mutate the fields they use.

4.5Which collection when?

NeedUseWhy
ordered, by position, mostly readingArrayListrandom access
many inserts and removals in the middleLinkedListno shifting; but no random access
no duplicates, fast membershipHashSethash table
no duplicates, sortedTreeSetsearch tree, O(log n)
first in, first outArrayDeque / LinkedListQueue
always take the best next itemPriorityQueuehighest priority first
key to value, no orderHashMaphash table
key to value, sorted keysTreeMapsearch tree
key to value, insertion order or LRULinkedHashMaplinked list threaded through

4.6Lecture summary

  • The Map interface captures key-value stores: get(), put(), containsKey(), containsValue() ...
  • Parameterised by two type variables, K for keys and V for values. Keys form a set.
  • Different ways to update an entry, depending on whether the key exists: getOrDefault(), putIfAbsent(), merge().
  • Extract keys as a Set, values as a Collection, pairs as a Set: keySet(), values(), entrySet(). Use these views to iterate.
  • Concrete implementations: HashMap, TreeMap, LinkedHashMap.
Next · InterviewInterview Question Bank
05
Interview

Interview Question Bank

What is likely to be asked from Week 6, with 30-second answers, deeper explanations, a lecture-or-extra tag on each question, and code you can run.

Week 6 is the Collections week, and collections questions are among the most common in Java interviews: ArrayList versus LinkedList, the three maps, hashing and equals/hashCode, removing while iterating, and counting with a map. Every answer starts with a 30-second version. Each question is tagged From the lecture or Extra.

1Definition

One clear sentence: what it is.

2Tiny example

Three or four lines of code.

3The failure it prevents

What goes wrong without it.

5.1Interview heat-map: what is most likely

Very likely7 topics
Program to an interface"Why declare List<String> x = new ArrayList<>() and not ArrayList<String> x?"Notes 1.3, 1.4
ArrayList vs LinkedList"When would you use LinkedList instead of ArrayList?"Notes 3.2, 3.3
HashMap vs TreeMap vs LinkedHashMap"What is the difference between HashMap, TreeMap and LinkedHashMap?"Notes 4.4
equals and hashCode in hash collections"Why must you override hashCode when you override equals?"Notes 3.4
Iterator.remove and ConcurrentModificationException"How do you remove elements while looping over a collection?"Notes 2.4
Map is not a Collection"Is a Map a Collection? How do you iterate it?"Notes 4.1, 4.3
Counting with a map"Count the frequency of words using a Map."Notes 4.2
Likely8 topics
HashSet vs TreeSet"HashSet or TreeSet? What do TreeSet elements need?"Notes 3.4
Queue: add vs offer"What is the difference between add and offer, remove and poll?"Notes 3.5
PriorityQueue"How does a PriorityQueue order its elements?"Notes 3.5
Deque as a stack"How do you implement a stack in Java?"Notes 3.5
AbstractCollection"What is AbstractCollection for?"Notes 2.6
RandomAccess"What is the RandomAccess interface?"Notes 3.2
LRU cache"Build an LRU cache."Notes 4.4
Immutable collections"What does List.of() return? Can you add to it?"Notes 2.6
Sometimes4 topics
Mutable keys"What happens if you change a key after putting it in a HashMap?"Notes 4.4
null in collections"Can a HashMap have a null key? A TreeMap?"Notes 4.4
Collection vs Collections"What is the difference between Collection and Collections?"Notes 2.2
Iterator vs ListIterator"What does ListIterator add to Iterator?"Notes 3.1

5.2Interfaces, lists and sets

Interview 1Why write List<String> x = new ArrayList<>() instead of declaring the variable as ArrayList<String>?
Lecture 1Very likelyNotes 1.3, 1.4From the lecture

Think of changing the implementation later.

Compiler says (concrete variable)
Main.java:4: error: incompatible types: LinkedListQueue<String> cannot be converted to CircularArrayQueue<String>
Prints (after changing one line)
5
Answer
Say this first (30 seconds)

The interface type is a level of indirection. Code that uses List depends only on what a list does, so the implementation can be swapped by changing one line (the new), with no change to variables, parameters or return types. Declared with the concrete class, the choice spreads through the whole program.

Go deeper
  • Same for parameters and return types: accept Collection<E> or List<E>, not ArrayList<E>.
  • Exception: use the concrete type when you need a method only it has (e.g. ensureCapacity on ArrayList).
Follow-up they may askWhat is the cost of indirection?
Interview 2When would you choose a LinkedList over an ArrayList?
Lecture 3Very likelyNotes 3.2, 3.3From the lecture

Compare random access and insertion in the middle.

ArrayListLinkedList
get(i)fast (computed)walks i links
insert / remove in the middleshifts the elements after itcheap once positioned
memoryone array (grows by copying)one node per element
also a Queue/Deque?noyes
Prints (the same loop, two lists)
LinkedList: get(i) loop slower than iterator? true  ArrayList get(i) faster than LinkedList get(i)? true
Prints (instanceof RandomAccess)
true false
Answer
Say this first (30 seconds)

ArrayList by default: a flexible array with fast random access. Choose LinkedList when you insert and remove at arbitrary positions often (through an iterator) and do not index. A linked list has no random access: get(i) walks i links, so a for (i...) get(i) loop is quadratic.

Go deeper
  • In practice ArrayList is faster for most workloads, even many inserts, because of memory locality (extra).
  • The marker interface RandomAccess lets generic code pick an algorithm.
Follow-up they may askHow would you remove while iterating a linked list?
Interview 3Why must a class override hashCode() when it overrides equals()?
Lecture 3Very likelyNotes 3.4Extra

A HashSet maps a value to a slot.

Prints (equals only: size, contains)
2  false
Prints (equals and hashCode)
1  true
Answer
Say this first (30 seconds)

A hash collection first uses hashCode() to pick a slot, and only then equals() within that slot. If two equal objects have different hash codes they land in different slots and are never compared: a set stores both, and contains misses. The contract: equal objects must have equal hash codes.

Go deeper
  • The reverse is allowed: unequal objects may share a hash code (a collision).
  • Do not use fields that change in hashCode if the object is a key (see the mutable-key question).
Follow-up they may askWhat does a good hashCode look like?
Interview 4When do you use a HashSet and when a TreeSet? What must a TreeSet's elements provide?
Lecture 3LikelyNotes 3.4From the lecture

Both forbid duplicates; they differ in order and cost.

Run time says (TreeSet of a non-Comparable)
Exception in thread "main" java.lang.ClassCastException: class Job cannot be cast to class java.lang.Comparable (Job is in unnamed module of loader 'app'; java.lang.Comparable is in module java.base of loader 'bootstrap')
Prints (with a Comparator)
[email, print, backup]
Answer
Say this first (30 seconds)

Both are sets: no duplicates, quick membership. HashSet: hash table, fastest, no order, needs equals/hashCode. TreeSet: balanced tree, O(log n), iterates in sorted order, needs an ordering: elements that are Comparable or a Comparator passed to the constructor; otherwise ClassCastException.

Go deeper
  • Two elements are "the same" for a TreeSet when the comparison returns 0, not when equals is true (extra).
Follow-up they may askWhat is a LinkedHashSet for?
Interview 5What is the RandomAccess interface, and why does it have no methods?
Lecture 3LikelyNotes 3.2From the lecture

A tag, not a capability you call.

Prints (instanceof results, then the two sums)
true false  6 6
Answer
Say this first (30 seconds)

It is a tagging (marker) interface: it only declares that a list supports fast random access. Code tests c instanceof RandomAccess and picks an index loop or an iterator loop. ArrayList carries the tag; LinkedList does not.

Go deeper
  • Other marker interfaces: Serializable, Cloneable (extra).
Follow-up they may askWhat replaces marker interfaces in modern Java?
Interview 6What does ListIterator add to Iterator?
Lecture 3SometimesNotes 3.1From the lecture

Only lists have it, and it moves both ways.

Prints
[a, b, c, d]
true
c
[a, b, C, d]
Answer
Say this first (30 seconds)

Only lists give one. It extends Iterator with add (insert before the current position), set (replace the last returned), and backwards movement: hasPrevious(), previous() (which returns the element, though the slide lists void).

Go deeper
  • Do not confuse ListIterator.add (void, position) with Collection.add (boolean, append).
Follow-up they may askWhat does the iterator "stand" on?
Interview 7What is AbstractCollection for?
Lecture 2LikelyNotes 2.6From the lecture

Collection has many methods; implementing all is tedious.

Prints (a Bag that wrote only iterator() and size())
true
true
false
[red, green, blue]
3 5 4 
Run time says (add never overridden)
Exception in thread "main" java.lang.UnsupportedOperationException
Answer
Say this first (30 seconds)

It is an abstract class that implements Collection and writes default versions of most methods on top of iterator() and size() (for example contains, containsAll, toString). A concrete collection extends it, supplies the representation-specific parts, and may override defaults for speed. This is the template method idea.

Go deeper
  • Optional methods such as add throw UnsupportedOperationException until overridden.
  • An interface cannot do this job alone for state and constructors, even with default methods.
Follow-up they may askHow is it different from Collections (with an s)?
Interview 8What is the difference between Collection and Collections?
Lecture 2SometimesNotes 2.2Extra

One letter, two different kinds of thing.

Main.java
List<Integer> nums = new ArrayList<>(List.of(3, 1, 2));
Collections.sort(nums);                  // a static helper from the class
System.out.println(Collections.max(nums));
Answer
Say this first (30 seconds)

Collection is the interface at the top of the framework (this lecture). Collections is a utility class of static helper methods that operate on collections: sort, max, reverse, unmodifiableList ... The same split exists for Arrays (helpers) versus array types.

Go deeper
  • Static methods can live in an interface since Java 8 (Week 4), but the library kept this older design.
Follow-up they may askWhich interface do Collections.sort lists have to satisfy?

5.3Iterators, queues and stacks

Interview 9How do you remove elements while looping over a collection?
Lecture 2Very likelyNotes 2.4From the lecture

Which remove, and on what?

Prints (iter.remove())
[tea, coffee]
Run time says (list.remove inside for-each)
Exception in thread "main" java.util.ConcurrentModificationException
Run time says (remove twice)
Exception in thread "main" java.lang.IllegalStateException
Answer
Say this first (30 seconds)

Use the iterator's remove(): it deletes the element next() last returned. Calling list.remove() inside a for-each changes the list behind the iterator's back, and the iterator fails fast with ConcurrentModificationException. Calling the iterator's remove() twice without a next() in between, or before the first next(), throws IllegalStateException.

Go deeper
  • Since Java 8: collection.removeIf(x -> ...) does it in one line (extra).
  • "Fail-fast" is a best-effort bug detector, not a thread-safety guarantee.
Follow-up they may askWhy is removing the second-last element in a for-each sometimes silent?
Interview 10What is the difference between add and offer, and between remove and poll?
Lecture 3LikelyNotes 3.5From the lecture

Strict and gentle versions of the same operations.

Run time says (a full bounded queue)
false
Exception in thread "main" java.lang.IllegalStateException: Queue full
Run time says (remove on empty)
Exception in thread "main" java.util.NoSuchElementException
Answer
Say this first (30 seconds)

Each has a strict form that flags an error and a gentle form that returns a value. add on a full bounded queue throws IllegalStateException; offer returns false. remove() and element() on an empty queue throw NoSuchElementException; poll() and peek() return null. element/peek only look at the head.

Go deeper
  • Because poll returns null for "empty", do not store null in a queue (extra).
Follow-up they may askWhich queue classes are bounded?
Interview 11How does a PriorityQueue order its elements, and what does printing one show?
Lecture 3LikelyNotes 3.5From the lecture

remove() returns the highest priority item.

Prints
[1, 2, 4, 5, 3]
remove(): 1 2 3 4 5 
5
Answer
Say this first (30 seconds)

remove() / poll() always return the highest-priority element: by natural ordering the smallest, or whatever a Comparator says. It is not kept fully sorted: printing or iterating shows the internal heap order. Only repeated remove() gives sorted output.

Go deeper
  • Insert and remove cost O(log n); peeking the head is O(1) (extra).
Follow-up they may askHow would you get a max-heap?
Interview 12How do you use a stack in modern Java?
Lecture 3LikelyNotes 3.5Extra

There is an old Stack class. Is it the right one?

Prints
[1, 2, 3]  1 3  1 3 [2]  8 7
Answer
Say this first (30 seconds)

Use a Deque, typically ArrayDeque: push adds at the front, pop removes from the front, peek looks. The old Stack class is a legacy Vector subclass (Lecture 2's list of early classes) and is not recommended.

Go deeper
  • Slide slip: addFirst/addLast return void, not boolean.
Follow-up they may askWhy not LinkedList as a stack?
Interview 13What does List.of(...) return, and can you add to it?
Lecture 2LikelyNotes 2.6Extra

Optional operations again.

Run time says
Exception in thread "main" java.lang.UnsupportedOperationException
Answer
Say this first (30 seconds)

An immutable list: reading works, any change (add, remove, set) throws UnsupportedOperationException. It is a List by interface, but it chose not to support the optional write methods. Copy it into a new ArrayList if you need to change it.

Go deeper
  • Same family: Arrays.asList (fixed size) and Collections.unmodifiableList (a read-only view).
Follow-up they may askDoes List.of allow null?

5.4Maps

Interview 14What is the difference between HashMap, TreeMap and LinkedHashMap?
Lecture 4Very likelyNotes 4.4From the lecture

Think about what order keySet() gives.

HashMapTreeMapLinkedHashMap
storagehash tablebalanced search treehash table + linked list
iteration ordernone promisedsorted by keyinsertion (or access)
get / putfastO(log n)fast
null keyone allowedNullPointerExceptionone allowed
Prints (the same four puts)
HashMap       [banana, apple, pear, fig]
TreeMap       [apple, banana, fig, pear]
LinkedHashMap [pear, apple, fig, banana]
Answer
Say this first (30 seconds)

All three are Maps. HashMap uses a hash table: fast, no order. TreeMap uses a balanced search tree: keys come out sorted, operations cost O(log n). LinkedHashMap is a hash table also threaded by a linked list: keys come out in insertion order (or, with a flag, access order for an LRU cache).

Go deeper
  • The sets mirror this: HashSet, TreeSet, LinkedHashSet.
  • Keys of a TreeMap need an ordering: Comparable keys or a Comparator.
Follow-up they may askWhich one would you use for a leaderboard sorted by name?
Interview 15Is a Map a Collection? How do you iterate over one?
Lecture 4Very likelyNotes 4.1, 4.3From the lecture

A map holds pairs.

Compiler says
Main.java:6: error: incompatible types: HashMap<String,Integer> cannot be converted to Collection<String>
Prints (entrySet loop)
Asha earns 50000
Meena earns 55000
Answer
Say this first (30 seconds)

No. Collection groups single elements; a map holds key-value pairs, so Map<K,V> is a separate interface with two type parameters. You iterate a map through one of its views: keySet() (a Set<K>), values() (a Collection<V>), or best entrySet() (a Set<Map.Entry<K,V>>) to get key and value together without a second lookup.

Go deeper
  • Views are live: removing through them removes from the map.
  • Iterating keySet() and calling get(key) inside works but does a lookup per key.
Follow-up they may askWhat does Map.Entry.setValue do?
Interview 16Count how often each word occurs in a text using a Map.
Lecture 4Very likelyNotes 4.2From the lecture

Update if the key exists, otherwise create it.

Main.java
Map<String, Integer> freq = new HashMap<>();
for (String w : text.split(" ")) {
    freq.merge(w, 1, Integer::sum);          // or put(w, getOrDefault(w, 0) + 1)
}
Prints (getOrDefault version)
{not=1, be=2, or=1, to=2}
Run time says (get + 1 on a missing key)
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "java.lang.Integer.intValue()" because the return value of "java.util.Map.get(Object)" is null
Answer
Say this first (30 seconds)

Keep a Map<String,Integer> of counts and update it with merge(word, 1, Integer::sum) (store 1 if absent, otherwise add 1), or with put(w, getOrDefault(w, 0) + 1). The trap is put(w, get(w) + 1): for a new word get returns null and unboxing it throws NullPointerException.

Go deeper
  • Use a TreeMap for alphabetical output, a LinkedHashMap for first-appearance order.
Follow-up they may askHow do you print the three most frequent words?
Interview 17Build a least-recently-used (LRU) cache with the Java library.
Lecture 4LikelyNotes 4.4From the lecture

LinkedHashMap can remember access order.

LruCache.java
import java.util.LinkedHashMap;
import java.util.Map;

public class LruCache<K, V> extends LinkedHashMap<K, V> {
    private final int capacity;

    public LruCache(int capacity) {
        super(16, 0.75f, true);                            // access order
        this.capacity = capacity;
    }

    @Override
    protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
        return size() > capacity;                          // drop the least recently used
    }
}
Prints (capacity 3: a, b, c, get a, put d)
[c, a, d]
Answer
Say this first (30 seconds)

Extend LinkedHashMap with the access-order flag (super(16, 0.75f, true)), so each get or put moves a pair to the end, and override removeEldestEntry to return size() > capacity: the front, the least recently used, is evicted automatically.

Go deeper
  • Not thread-safe; wrap with Collections.synchronizedMap if shared (extra).
Follow-up they may askHow would you do it without LinkedHashMap?
Interview 18What happens if you change a key's fields after putting it in a HashMap?
Lecture 4SometimesNotes 4.4Extra

The entry was filed under the old hash.

Prints (get(k), get(new Key(1)), size)
null  null  1
Answer
Say this first (30 seconds)

The entry stays in the slot for the old hash. Looking it up with the changed key goes to the new slot and finds nothing; looking it up with an equal-to-the-old key reaches the right slot but equals now fails. The entry is lost yet still counted in size(). Use immutable keys.

Go deeper
  • Same danger for elements of a HashSet and for objects changed inside a TreeSet (extra).
Follow-up they may askWhy are String keys safe?
Interview 19Can a HashMap have a null key? A TreeMap?
Lecture 4SometimesNotes 4.4Extra

A tree has to compare keys.

Run time says
1
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "java.lang.Comparable.compareTo(Object)" because "k1" is null
Answer
Say this first (30 seconds)

HashMap allows one null key (and null values). TreeMap (natural ordering) throws NullPointerException, because it has to call compareTo on keys. Many other collections (ArrayDeque, List.of, PriorityQueue) reject null too.

Go deeper
  • A null return from get is ambiguous: absent key, or a key mapped to null? Use containsKey.
Follow-up they may askWhy does the null-key rule differ?
Next · SummaryWeek 6 Summary Sheet
06
Summary

Week 6 Summary Sheet

Week 6 on one page: a lecture-by-lecture glance, key terms, the pairs to tell apart, the errors you can now read, a complexity table, a checklist and corrections to the source notes.

6.1Week 6 at a glance

L1Indirection

An abstract data type separates interface from implementation. Declare with the interface (Queue<E>); only the instantiation names the class. Circular array: efficient, bounded. Linked list: flexible.

L2Collection

add, iterator, size, contains, addAll(Collection<? extends E>). For-each builds an iterator. Iterator.remove() deletes the last next(). AbstractCollection gives defaults.

L3Concrete collections

List ordered + positional (ArrayList random access, LinkedList not). Set no duplicates (HashSet hash, TreeSet sorted). Queue / Deque / PriorityQueue.

L4Maps

Map<K,V> is not a Collection: keys form a set. put returns the old value. Update with getOrDefault, putIfAbsent, merge. Iterate the views keySet, values, entrySet. HashMap, TreeMap, LinkedHashMap.

6.2Key terms, lecture by lecture

L1 · Indirection
abstract data type
Public interface separated from private implementation.
circular array
Array whose head and tail wrap around; bounded.
indirection
An interface between user code and implementation.
instantiation
The one place a concrete class is named.
L2 · Collection
Collection
Interface for grouped data (not key-value).
Iterator
hasNext, next, remove
for-each
Implicitly creates and runs an iterator.
AbstractCollection
Abstract class with default implementations.
L3 · Concrete collections
List
Ordered; access by iterator or position.
RandomAccess
Tagging interface: fast positional access.
Set
No duplicates; add returns false for one.
hash table
Maps value to a slot h(v); collisions possible.
Deque
Double-ended queue.
PriorityQueue
remove gives the highest priority.
L4 · Maps
Map.Entry
One key-value pair.
view
A live Set / Collection over the map.
merge
Insert, or combine with the old value.
access order
LinkedHashMap mode: get/put moves to the end (LRU).

6.3The pairs you must tell apart

List<E> xdeclare by interface
VS
ArrayList<E> xwelded to a class
Collectioninterface
VS
Collectionsstatic helper class
Iterator.remove()last returned element
VS
Collection.remove(Object)a given value
ArrayListrandom access
VS
LinkedListcheap middle edits
HashSethash, no order
VS
TreeSetsorted, O(log n)
addstrict: throws
VS
offergentle: false
remove() / element()throw on empty
VS
poll() / peek()null on empty
HashMapno order
VS
LinkedHashMapinsertion / access order
keySet()Set of keys
VS
entrySet()key and value together
getOrDefaultread with a default
VS
mergeinsert or combine

6.4Messages and errors you can now read

What went wrongWhat you will seeWhen
assigning a different implementation to a concrete variableerror: incompatible types: LinkedListQueue<String> cannot be converted to CircularArrayQueue<String>compile time
new on an interfaceerror: Queue is abstract; cannot be instantiatedcompile time
a class that skips an interface methoderror: Broken is not abstract and does not override abstract method size() in Queuecompile time
calling remove() on the elementerror: cannot find symbol symbol: method remove()compile time
addAll from a wider collectionerror: incompatible types: List<Number> cannot be converted to Collection<? extends Integer>compile time
two methods with the same signatureerror: method containsKey(Object) is already defined in interface MyMapcompile time
using the result of a void methoderror: incompatible types: void cannot be converted to booleancompile time
a Map where a Collection is expectederror: incompatible types: HashMap<String,Integer> cannot be converted to Collection<String>compile time
removing before or twice after next()java.lang.IllegalStateExceptionrun time
changing a list or map during a for-eachjava.util.ConcurrentModificationExceptionrun time
a bounded queue is fulljava.lang.IllegalStateException: Queue fullrun time
remove() or element() on an empty queuejava.util.NoSuchElementExceptionrun time
TreeSet of elements with no orderingjava.lang.ClassCastException: class Job cannot be cast to class java.lang.Comparablerun time
TreeMap with a null keyjava.lang.NullPointerException: Cannot invoke "java.lang.Comparable.compareTo(Object)"run time
map.get(k) + 1 on a missing keyjava.lang.NullPointerException: Cannot invoke "java.lang.Integer.intValue()"run time
add on a collection that never overrode itjava.lang.UnsupportedOperationExceptionrun time
add to List.of(...)java.lang.UnsupportedOperationExceptionrun time
adding to a key setjava.lang.UnsupportedOperationExceptionrun time

6.5Cost at a glance (extra)

Collectionget / containsaddremoveorder
ArrayListget O(1), contains O(n)end O(1) amortisedO(n) (shifts)insertion
LinkedListget O(n), contains O(n)ends O(1)O(1) at an iteratorinsertion
HashSet / HashMapO(1) expectedO(1) expectedO(1) expectednone
TreeSet / TreeMapO(log n)O(log n)O(log n)sorted
LinkedHashMapO(1) expectedO(1) expectedO(1) expectedinsertion / access
ArrayDequeends O(1)O(1)O(1)insertion
PriorityQueuepeek O(1)O(log n)O(log n)by priority

6.6Last-minute checklist

L1 · Indirection
  • variables typed by the interface
  • only new names the class
  • circular array: fast, bounded; linked: flexible
  • every abstract method implemented
L2 · Collection
  • remove through the iterator: iter.remove()
  • one next() per remove()
  • for-each + list.remove = ConcurrentModificationException
  • extend AbstractCollection for defaults
L3 · Concrete collections
  • list loops: iterator, not get(i) on a LinkedList
  • equals + hashCode together for hash collections
  • TreeSet needs Comparable or Comparator
  • offer / poll / peek are the gentle forms
L4 · Maps
  • Map is not a Collection
  • missing key: use getOrDefault / merge, not get + 1
  • iterate entrySet() for key and value
  • immutable keys

6.7Corrections made to the source notes

WhereThe source saysCorrect
Lecture 2, removingelement.remove()iter.remove() (checked by compiling)
Lecture 2, Collectionint size() boolean isEmpty();a semicolon after size() (checked by compiling)
Lecture 2, defaults"provide default implementations in the interface itself"the library uses the abstract class AbstractCollection (interfaces also may have defaults since Java 8)
Lecture 3, Listvoid remove(int index), void previous()E remove(int index), E previous()
Lecture 3, AbstractList"interface"an abstract class
Lecture 3, Dequeboolean addFirst(E), boolean addLast(E)void addFirst(E), void addLast(E) (checked by compiling)
Lecture 4, Mapboolean containsKey(Object value)boolean containsValue(Object value) (checked by compiling)
Lecture 4, updatingbatan undeclared name; use a key such as "Asha"
Revision overview
Indirectioninterface type
swap one line
circular vs linked
Collectionadd, iterator, size
for-each
AbstractCollection
IteratorhasNext, next
remove after next
fail-fast
Week 6Java Collections
List / QueueArrayList, LinkedList
offer, poll, peek
Deque, PriorityQueue
Setno duplicates
HashSet, TreeSet
equals + hashCode
Mapkey to value
merge, getOrDefault
Hash / Tree / Linked
Revision: all of Week 6 on one page
Back to top