CS61B Textbook 2025
  • Contributors
  • DISCLAIMER
  • 1. Introduction
    • 1.1 Your First Java Program
    • 1.2 Java Workflow
    • 1.3 Basic Java Features
    • 1.4 Exercises
  • 2. Defining and Using Classes
  • 3. References, Recursion, and Lists
  • 4. SLLists
  • 5. DLLists
  • 6. Arrays
  • 7. Testing
  • 8. ArrayList
  • 9. Inheritance I: Interface and Implementation Inheritance
    • 9.1 The Problem of Generality
    • 9.2 Hypernyms, Hyponyms, and the Implements Keyword
    • 9.3 Overriding, Interface Inheritance
    • 9.4 Implementation Inheritance, default
    • 9.5 Implementation vs. Interface Inheritance
    • 9.6 Abstract Data Types
  • 10. Inheritance II: Subtype Polymorphism, Comparators, Comparables, Generic Functions
    • 10.1 Polymorphism vs. Function Passing
    • 10.2 Comparables and Comparators
    • 10.3 Writing a Max Function
    • 10.4 Summary
  • 11. There is no chapter 11.
  • 12. Inheritance III: Iterators, Object Methods
    • 12.1 Lists and Sets in Java
    • 12.2 Exceptions
    • 12.3 Iteration
    • 12.4 Object Methods
    • 12.5 Chapter Summary
    • 12.6 Exercises
  • 13. Asymptotics I
    • 13.1 An Introduction to Asymptotic Analysis
    • 13.2 Runtime Characterization
    • 13.3 Checkpoint: An Exercise
    • 13.4 Asymptotic Behavior
    • 13.6 Simplified Analysis Process
    • 13.7 Big-Theta
    • 13.8 Big-O
    • 13.9 Summary
    • 13.10 Exercises
  • 14. Disjoint Sets
    • 14.1 Introduction
    • 14.2 Quick Find
    • 14.3 Quick Union
    • 14.4 Weighted Quick Union (WQU)
    • 14.5 Weighted Quick Union with Path Compression
    • 14.6 Exercises
  • 15. Asymptotics II
    • 15.1 For Loops
    • 15.2 Recursion
    • 15.3 Binary Search
    • 15.4 Mergesort
    • 15.5 Summary
    • 15.6 Exercises
  • 16. ADTs and BSTs
    • 16.2 Binary Search Trees
    • 16.3 BST Definitions
    • 16.4 BST Operations
    • 16.5 BSTs as Sets and Maps
    • 16.6 Summary
    • 16.7 Exercises
  • 17. B-Trees
    • 17.1 BST Performance
    • 17.2 Big O vs. Worst Case
    • 17.3 B-Tree Operations
    • 17.4 B-Tree Invariants
    • 17.5 B-Tree Performance
    • 17.6 Summary
    • 17.7 Exercises
  • 18. Red Black Trees
    • 18.1 Rotating Trees
    • 18.2 Creating LLRB Trees
    • 18.3 Inserting LLRB Trees
    • 18.4 Runtime Analysis
    • 18.5 Summary
    • 18.6 Exercises
  • 19. Hashing I
    • 19.1 Introduction to Hashing: Data Indexed Arrays
      • 19.1.1 A first attempt: DataIndexedIntegerSet
      • 19.1.2 A second attempt: DataIndexedWordSet
      • 19.1.3 A third attempt: DataIndexedStringSet
    • 19.2 Hash Code
    • 19.3 "Valid" & "Good" Hashcodes
    • 19.4 Handling Collisions: Linear Probing and External Chaining
    • 19.5 Resizing & Hash Table Performance
    • 19.6 Summary
    • 19.7 Exercises
  • 20. Hashing II
    • 20.1 Hash Table Recap, Default Hash Function
    • 20.2 Distribution By Other Hash Functions
    • 20.3 Contains & Duplicate Items
    • 20.4 Mutable vs. Immutable Types
  • 21. Heaps and Priority Queues
    • 21.1 Priority Queues
    • 21.2 Heaps
    • 21.3 PQ Implementation
    • 21.4 Summary
    • 21.5 Exercises
  • 22. Tree Traversals and Graphs
    • 22.1 Tree Recap
    • 22.2 Tree Traversals
    • 22.3 Graphs
    • 22.4 Graph Problems
  • 23. Graph Traversals and Implementations
    • 23.1 BFS & DFS
    • 23.2 Representing Graphs
    • 23.3 Summary
    • 23.4 Exercises
  • 24. Shortest Paths
    • 24.1 Introduction
    • 24.2 Dijkstra's Algorithm
    • 24.3 A* Algorithm
    • 24.4 Summary
    • 24.5 Exercises
  • 25. Minimum Spanning Trees
    • 25.1 MSTs and Cut Property
    • 25.2 Prim's Algorithm
    • 25.3 Kruskal's Algorithm
    • 25.4 Chapter Summary
    • 25.5 MST Exercises
  • 26. Prefix Operations and Tries
    • 26.1 Introduction to Tries
    • 26.2 Trie Implementation
    • 26.3 Trie String Operations
    • 26.4 Summary
    • 26.5 Exercises
  • 27. Software Engineering I
    • 27.1 Introduction to Software Engineering
    • 27.2 Complexity
    • 27.3 Strategic vs Tactical Programming
    • 27.4 Real World Examples
    • 27.5 Summary, Exercises
  • 28. Reductions and Decomposition
    • 28.1 Topological Sorts and DAGs
    • 28.2 Shortest Paths on DAGs
    • 28.3 Longest Path
    • 28.4 Reductions and Decomposition
    • 28.5 Exercises
  • 29. Basic Sorts
    • 29.1 The Sorting Problem
    • 29.2 Selection Sort & Heapsort
    • 29.3 Mergesort
    • 29.4 Insertion Sort
    • 29.5 Summary
    • 29.6 Exercises
  • 30. Quicksort
    • 30.1 Partitioning
    • 30.2 Quicksort Algorithm
    • 30.3 Quicksort Performance Caveats
    • 30.4 Summary
    • 30.5 Exercises
  • 31. Software Engineering II
    • 31.1 Complexity II
    • 31.2 Sources of Complexity
    • 31.3 Modular Design
    • 31.4 Teamwork
    • 31.5 Exerises
  • 32. More Quick Sort, Sorting Summary
    • 32.1 Quicksort Flavors vs. MergeSort
    • 32.2 Quick Select
    • 32.3 Stability, Adaptiveness, and Optimization
    • 32.4 Summary
    • 32.5 Exercises
  • 33. Software Engineering III
    • 33.1 Candy Crush, SnapChat, and Friends
    • 33.2 The Ledger of Harms
    • 33.3 Your Life
    • 33.4 Summary
    • 33.5 Exercises
  • 34. Sorting and Algorithmic Bounds
    • 34.1 Sorting Summary
    • 34.2 Math Problems Out of Nowhere
    • 34.3 Theoretical Bounds on Sorting
    • 34.4 Summary
    • 34.5 Exercises
  • 35. Radix Sorts
    • 35.1 Counting Sort
    • 35.2 LSD Radix Sort
    • 35.3 MSD Radix Sort
    • 35.4 Summary
    • 35.5 Exercises
  • 36. Sorting and Data Structures Conclusion
    • 36.1 Radix vs. Comparison Sorting
    • 36.2 The Just-In-Time Compiler
    • 36.3 Radix Sorting Integers
    • 36.4 Summary
    • 36.5 Exercises
  • 37. Software Engineering IV
    • 37.1 The end is near
  • 38. Compression and Complexity
    • 38.1 Introduction to Compression
    • 38.2 Prefix-free Codes
    • 38.3 Shannon-Fano Codes
    • 38.4 Huffman Coding Conceptuals
    • 38.5 Compression Theory
    • 38.6 LZW Compression
    • 38.7 Summary
    • 38.8 Exercises
  • 39. Compression, Complexity, P = NP
    • 39.1 Models of Compression
    • 39.2 Optimal Compression, Kolmogorov Complexity
    • 39.3 Space/Time-Bounded Compression
    • 39.4 P = NP
    • 39.5 Exercises
Powered by GitBook
On this page
  • Comparables
  • Comparators
  • Using a Comparator
  • Minor Improvements
  • Bonus Content
  • Comparables vs. Comparators
  1. 10. Inheritance II: Subtype Polymorphism, Comparators, Comparables, Generic Functions

10.2 Comparables and Comparators

Previous10.1 Polymorphism vs. Function PassingNext10.3 Writing a Max Function

Last updated 3 months ago

Comparables

As a motivating example, consider the following Java code, where we want to try to find the largest dog in a list of dogs.

List<Dog> dogs = new ArrayList<>();
dogs.add(new Dog("Grigometh", 200));
dogs.add(new Dog("Pelusa", 5));
dogs.add(new Dog("Clifford", 9000));
Dog maxDog = Collections.max(dogs);

This code almost works, but there's one problem, Java doesn't know how to compare dogs. This will result in a fairly incomprehensible error message that "no instance(s) of type variable(s) T exist so that Dog conforms to Comparable<? super T>".

Unlike Python, where we defined a __gt__ method to allow for comparison, Java doesn't have operator overloading. Instead, we must utilize implementation inheritance. Specifically, we'll implement the Comparable interface, given below:

public interface Comparable<T> {
    int compareTo(T o);
}

As we saw earlier in class, the compareTo method returns a negative integer, zero, or a positive integer as the first argument is less than, equal to, or greater than the second. If you're curious you can also look at the source for Comparator.java, which can be seen at .

After implementing the Comparable interface, we end up with the code below:

public class Dog implements Comparable<Dog> {
   ...
   @Override
   public int compareTo(Dog uddaDog) {
       if (size > uddaDog.size) {
           return 1;
       }
       if (size < uddaDog.size) {
           return -1;
       }
       return 0;
   }
}

After implementing this interface, Collections.max(dogs) will now work.

While the code we wrote above works, it's a bit clumsy. An alternate approach is shown below:

public class Dog implements Comparable<Dog> {
   ...
   @Override
   public int compareTo(Dog uddaDog) {
       return this.size - uddaDog.size;
   }
}

Here, we save some lines of code by simply subtracting the sizes. Returning a difference between two numbers is a common way to implement compareTo methods in Java.

The flavor of polymorphism we’ve just employed is sometimes called subtype polymorphism.

The idea here is that a supertype (Comparable) specifies the capability (in this case, comparison). Then a subtype (Dog) overrides the supertype’s abstract method. At runtime, Java decides what to do based on the type of the object that is invoking the method.

To test your understanding of these ideas, try answering these two questions in the lecture slides:

Comparators

The term "Natural Order" is sometimes used to refer to the ordering implied by a Comparable's compareTo method. Our compareTo method thinks of the size as the natural for the dogs.

Sometimes we want to consider other ordering. For example, we might want to sort the dogs by name.

In pPython, we achieve this goal using function passing. For example, in the code below, we pass name_len as a key function to get_the_max.

def get_the_max(x, key):
  max_value = x[0]
  for item in x:
    if key(item) > key(max_value):
      max_value = item
  return max_value

def name_len(dog):
   return len(dog.name)

max_dog=get_the_max(doglist, name_len)

By contrast, Java code doesn't typically use function passing for handling alernate orders. Instead, we rely again on subtype polymorphism.

Specifically, we can implement the Comparator interface, given below:

public interface Comparator<T> {
    int compare(T o1, T o2);
}

An example Comparator that compares dogs based on name is given below:

public static class NameComparator implements Comparator<Dog> {
   @Override
   public int compare(Dog a, Dog b) {
       return a.name.compareTo(b.name);
   }
}

To test your understanding of these ideas, consider this problem. What is the output of the code below?

Dog a = new Dog("Frank", 1);
Dog b = new Dog("Zeke", 1);
Comparator<Dog> nc = new Dog.NameComparator();
System.out.println(nc.compare(a, b));

Is it a positive number? Negative number? Or zero?

Using a Comparator

The code below shows an example of how we can pass a Comparator to Collections.max.

List<Dog> dogs = new ArrayList<>();
dogs.add(new Dog("Grigometh", 200));
dogs.add(new Dog("Pelusa", 5));
dogs.add(new Dog("Clifford", 9000));
Dog maxNameDog = Collections.max(dogs, new Dog.NameComparator());

This is similar to how we can pass a key function to get_the_max in Python.

def length_of_name(dog):
   return len(dog.name)

dogs = [Dog("Grigometh", 10),
        Dog("Pelusa", 5),
        Dog("Clifford", 9000)]
max_dog = get_the_max(dogs, length_of_name)

Notice the difference here. In Java, we package our comparison function inside of a Comparator object, i.e. we rely on subtype polymorphism.

By contrast, in Python, we pass a comparison function to get_the_max, i.e. we rely on function passing.

Minor Improvements

The NameComparator instantiation is awkward and aesthetically unpleasant (to me). It seems strange to have to instantiate a class just to pass a comparison function.

One fix is to add a static variable reference to a pre-instantiated NameComparator. We do this in our Dog class.

public class Dog {
   ...
   public static final Comparator<Dog> NAME_COMPARATOR = new NameComparator();
}

This allows us to pass Dog.NAME_COMPARATOR to Collections.max without having to instantiate a new NameComparator object. This results in code that looks like this:

List<Dog> dogs = new ArrayList<>();
dogs.add(new Dog("Grigometh", 200));
dogs.add(new Dog("Pelusa", 5));
dogs.add(new Dog("Clifford", 9000));
Dog maxNameDog = Collections.max(dogs, Dog.NAME_COMPARATOR);

Is this actually better? You be the judge!

Bonus Content

It is also possible to implement a Comparator using a lambda expression. This is shown below:

List<Dog> dogs = new ArrayList<>();
dogs.add(new Dog("Grigometh", 200));
dogs.add(new Dog("Pelusa", 5));
dogs.add(new Dog("Clifford", 9000));
Comparator<Dog> dc = (a, b) -> a.name.compareTo(b.name);
Dog maxNameDog = Collections.max(dogs, dc);

This is a bit more concise, but it's also a bit more difficult to read. We won't expect you to learn lambda expression in this class.

Comparables vs. Comparators

The Comparable interface specifies that a “natural order” exists. Instances of the class can compare themselves to other objects. Only one such order is possible.

The Comparator interface is used to compare extrinsically (by other classes). May have many such classes, each specifying one such order.

this link
Question 1
Question 2