Lecture. Generics, Collections, Hashing and Hash Tables
[comp1110-2026s2-lec07b] - September 24, 2026
- Fabian Muehlboeck
Lecture. Generics, Collections, Hashing and Hash Tables
[comp1110-2026s2-lec07b]- September 24, 2026
- Fabian Muehlboeck
Warning.
- September 24, 2026
- Fabian Muehlboeck
We started with hashing and hash tables at the end of this lecture, but the relevant notes will be in Lecture 08A.Warning.
- September 24, 2026
- Fabian Muehlboeck
1. Making Our Types Generic
[comp1110-8M] - Fabian Muehlboeck
1. Making Our Types Generic
[comp1110-8M]- Fabian Muehlboeck
Construction 1.1. The Object type
[comp1110-4A] - Liam O'Connor, Fabian Muehlboeck
Construction 1.1. The Object type
[comp1110-4A]- Liam O'Connor, Fabian Muehlboeck
In Java, Object is the root of the class hierarchy: every class is a subtype of Object. This means a variable of type Object can hold a value of any class: a String, an Integer, a Person, and so on.
The Object type, among other things, defines a .equals() method, so values of type Object can be compared for equality, but very few other operations are available. Many classes provide override .equals() to perform a deep comparison, as we have seen for Strings.
The downside is that the compiler knows very little about the value stored in an Object variable: to use it as its original type, an explicit cast is needed.
Example 1.2. The GuestList interface (Object version)
[comp1110-49] - Liam O'Connor, Fabian Muehlboeck
Example 1.2. The GuestList interface (Object version)
[comp1110-49]- Liam O'Connor, Fabian Muehlboeck
The original GuestList interface used String throughout. Replacing String with Object allows any type of value to be stored, since every Java class is a subtype of Object:
Our implementations (both strict and lazy) can be easily adapted by
replacing the type String with Object throughout.
Because Object already specifies the equals method, we've gained a lot of flexibility for essentially no cost, which is great!
The tricky part comes when we are also supposed to return values stored in our list.
Consider our previous extension of the above interface where we also return a list of everyone who is coming, this time with Object:
This method now returns an array of Objects, when our testing code would presumably expect an array of Strings.
We can adjust our testing code to expect Object arrays, and we can explicitly cast the elements of the array to our expected type.
However, this approach sacrifices type safety. Nothing prevents a caller from adding a mix of unrelated types to the same list, and retrieving a value requires an explicit cast (e.g. (String) list.coming()[0]) that can fail at runtime with a ClassCastException. The compiler cannot catch such errors.
Construction 1.3. Casts
[comp1110-4B] - Liam O'Connor
Construction 1.3. Casts
[comp1110-4B]- Liam O'Connor
A cast tells the compiler to treat a value as a more specific type than it currently knows about. The syntax is to write the target type in parentheses before the expression:
Casts are necessary when retrieving values from a collection that stores Object, since the compiler only knows the value is an Object, not what type it originally was when created.
However, if the cast is wrong (for example, casting a String to an Integer), Java throws an exception called ClassCastException at runtime. The compiler cannot catch this mistake. This is one of the key motivations for type parameters, which avoid the need for casts entirely.
Construction 1.4. Type parameters
[comp1110-48] - Liam O'Connor, Fabian Muehlboeck
Construction 1.4. Type parameters
[comp1110-48]- Liam O'Connor, Fabian Muehlboeck
A type parameter allows a class or interface to be written once and used with many different types, while retaining type safety (unlike Object). We write the type parameter in angle brackets after the class or interface name, and use it as a placeholder for a concrete type throughout. Referring to our GuestList example:
When using the interface, the caller fills in the placeholder with a concrete type:
The compiler then treats every occurrence of T as String for that particular instance of the class. This means list.add(42) is a compile-time error, since 42 is not a String. Whereas using Object, such mistakes are only caught at run time.
This feature is also known as generics in Java, or more generally as parametric polymorphism.
Example 1.5. Lazy Array Implementation of GuestList (generic version)
[comp1110-4C] - Liam O'Connor, Fabian Muehlboeck
Example 1.5. Lazy Array Implementation of GuestList (generic version)
[comp1110-4C]- Liam O'Connor, Fabian Muehlboeck
Updating ArrayGuestList to match the generic interface requires adding the type parameter <T> to the class declaration and replacing Object with T in the method signatures. The internal storage remains Object[], however, due to type erasure:
Type erasure is also the reason why the coming method needs an additional argument.
Because we are expected to return an array of T, but still cannot create one, we need to be given an object that can create such an array for us, given a size.
Function is a generic interface with one method (called apply) that accepts two type arguments - the first one is the argument type of apply, the second is the return type.
In this case, any object satisfying Function<Integer, T[] has an apply method that takes an integer (meant to be the intended size of the array) and returns an array (hopefully of the given size).
A user of coming can of course pass any object satisfying the interface, but for any concrete array type, say, String[], we can simply write:
This special notation refers to the constructors of a given type, which for arrays always take an integer for the size of the array. We may talk about why this works at the end of the semester.
Warning 1.6. Type erasure
[comp1110-4D] - Liam O'Connor, Fabian Muehlboeck
Warning 1.6. Type erasure
[comp1110-4D]- Liam O'Connor, Fabian Muehlboeck
Java generics are implemented via type erasure: at runtime, the type parameter T is erased to Object, so the expression new T[n] is illegal: the Java runtime has no idea what T is.
Any generic class that needs an array internally must therefore either allocate an Object[] and cast values out of it appropriately, or somehow get an object that knows what to create:
The casts inserted here work in practice as long as only values of type T are inserted — and this should be maintained through encapsulation — but it is a genuine limitation of how Java implements generics.
1.7. Subtyping with Generics
[comp1110-8O] - Fabian Muehlboeck
1.7. Subtyping with Generics
[comp1110-8O]- Fabian Muehlboeck
Generics introduced a new kind of type: instantiated generic types.
These are class types (which we have seen before), but now with type arguments, e.g. List<String>.
In Java, subtyping is largely based on the inheritance hierarchy.
With generics, that is still true, and in most cases, the additional requirement is that the type arguments between two types are the same.
For example, in the Java Collections API, there are concrete classes ArrayList<T> and LinkedList<T>, which both
implement the generic list interface List<T>, which in turn extends the generic interface Iterable<T>, which is a supertype of almost all collection interfaces and classes.
Without type arguments, it therefore holds that ArrayList and LinkedList are subtypes of List, and List is a subtype of Iterable (and so are, transitively, ArrayList and LinkedList).
This gets preserved with type arguments if the type arguments are the same.
Therefore:
LinkedList<String>is a subtype ofList<String>is a subtype ofIterable<String>ArrayList<String>is a subtype ofList<String>is a subtype ofIterable<String>LinkedList<Object>is a subtype ofList<Object>is a subtype ofIterable<Object>ArrayList<Object>is a subtype ofList<Object>is a subtype ofIterable<Object>
LinkedList<String>is not a subtype ofList<Object>List<String>is not a subtype ofIterable<Object>ArrayList<Object>is not a subtype ofList<String>List<Object>is not a subtype ofIterable<String>- ...
The reason for this in principle is Behavioural Subtyping.
Take, for example, LinkedList<String> and List<Object>.
From the outer types, we know that if at all, LinkedList<String> would be a subtype of List<Object>.
This works fine for reading from the list:
Since a LinkedList<String> stores String values, and objs.get is supposed to return an Object,
and every String is an Object, this code is not problematic (though illegal).
The problem here comes in when we write to the list:
A List<Object> should be able to contain arbitrary objects, so the call to add with a Person value is fine.
But that would be a problem if the underlying list object is a List<String>, which is not supposed to contain Person values.
The same argument works the other way around (that LinkedList<Object> is not a subtype of List<String>) - this time,
the problem is reading from the list, as one would expect that a List<String> returns a String, but a
LinkedList<Object> cannot guarantee that.
Warning.
- Fabian Muehlboeck
This argument would technically also apply to arrays.
However, Java (and other languages) violates type safety here by treating Warning.
- Fabian Muehlboeck
String[] as a subtype of Object[].
You can write code that relies on this so long as you only read from those arrays - trying to write a value of a wrong type to them will throw an exception at run time.
You still cannot treat an Object[] as an array of some other type (e.g. String[]).
This will fail at the point where you cast the array to that other array type, even if you never read from the array.
This is why we could not simply cast the array in implementing the coming function in the GuestList example.
2. Java Collections
[comp1110-8N] - Fabian Muehlboeck
2. Java Collections
[comp1110-8N]- Fabian Muehlboeck
2.1. The Java collections API
[comp1110-4E] - Liam O'Connor, Fabian Muehlboeck
2.1. The Java collections API
[comp1110-4E]- Liam O'Connor, Fabian Muehlboeck
Java provides a rich set of built-in data structures in the java.util package, collectively known as the collections API. Now that we have built GuestList from scratch, we can see how the same ideas appear in the standard library.
The GuestList interface is essentially a set, and Java provides a Set<T> interface for exactly this purpose. The TreeSet<T> implementation of Set<T> stores elements in sorted order using a binary tree structure:
The collections API also provides:
List<T>— an ordered sequence, withArrayList<T>(array-based with amortised expansion, like ourArrayGuestList) andLinkedList<T>(linked list-based) as implementations. See the List documentation.Map<K,V>— a mapping from keys to values, with e.g.TreeMap<K,V>as a tree-based implementation. See the Map documentation.
In practice, you would use these standard library types rather than writing your own. The exercise of building GuestList was to understand what is happening under the hood.
2.2. Factory methods in the collections API
[comp1110-4H] - Liam O'Connor, Fabian Muehlboeck
2.2. Factory methods in the collections API
[comp1110-4H]- Liam O'Connor, Fabian Muehlboeck
The Java collections API provides convenient factory methods for creating collections with a fixed set of elements. These are static methods named of() on the collection interfaces:
These are particularly handy in tests where you need a quick collection to work with without constructing it element by element.
Warning.
- Liam O'Connor, Fabian Muehlboeck
Warning.
- Liam O'Connor, Fabian Muehlboeck
Note that collections created with of() are immutable: calling add() or remove() on them will throw an UnsupportedOperationException at runtime. If you need a mutable collection, construct one explicitly using e.g. new TreeSet<>(Set.of(...)).
Warning.
- September 24, 2026
- Fabian Muehlboeck
We started with hashing and hash tables at the end of this lecture, but the relevant notes will be in Lecture 08A.Warning.
- September 24, 2026
- Fabian Muehlboeck