Using the Java Class Checklist

Thomas J. Kennedy

Contents:

Throughout this semester we have discussed the cross-language class checklist throughout each module. We started with C++ as the initial language, then discussed Java, briefly discussed Python, and introduced Rust.

This discussion will focus on the Java Class Checklist, specifically in how to use the Java Class Checklist as a guide to write a complete Java Class.

1 The Initial Class

Suppose you are working on a team, and one group member (the team smart aleck) has written a quick-and-dirty Cookbook class.

/**
 * A collection of Recipes. This class handles all logic
 * for a cookbook--e.g.,
 *   - Adding recipes
 *   - Printing recipes
 */
public class Cookbook {
    private static final int MAX_RECIPES = 100;

    private Recipe[] recipes;

    /**
     * Create a Cookbook that can contain at most MAX_RECIPES
     * recipes
     */
    public Cookbook()
    {
        //...
    }

    /**
     * Create a Cookbook that can contain at most _r_
     * recipes
     */
    public Cookbook(int r)
    {
        //...
    }

    /**
     * Add a Recipe
     *
     * @param toAdd new Recipe
     */
    public void addRecipe(Recipe toAdd)
    {
        //...
    }
}

You are responsible for the Cookbook class code review. What changes does Cookbook need to be considered complete?

Before reading the remainder of this document, take a few moments to think about what is missing.


2 Initial Observations

We know based on the Cross-Language Class Checklist that Cookbook is incomplete.

What observations can we make?

3 Starting with toString

The toString method is the most natural function (i.e., it has a clear purpose). There will always be a need to output a custom object. In C++ this is handled by operator<< paired with display. In Java output (more aptly generating a human readable string) is handled by toString

    /**
     * List each Recipe, seperating them with a blank line followed by "---"
     * and a second blank line.
     */
    @Override
    public String toString()
    {
        //...
    }

Note the @Override decorator. The toString method is provided by the Java Object base class (which outputs the memory address of the object). I need to mark Cookbook.toString as overridden.

Normally I would forgo Javadoc documentation for an override function. However, the documentation must always establish the rules of a function. In this case, I need to know what toString will output.

4 Tackling equals and hashCode

The Java equals function is equivalent to the C++ operator==. The two serve the same purpose (i.e., to check two objects for equivalence).

    /**
     * Compare two recipes based on the recipes they contain, ignoring the
     * the order of the recipes.
     *
     * @param rhs object against which to compare
     *
     * @return true if both this and rhs are Cookbooks with the same recipes
     */
    @Override
    public boolean equals(Object rhs)
    {
        //...
    }

In C++, operator< is usually paired with operator== based on requirements of the C++ STL. Java is similar. The equals and hashCode functions are all-but-required to be paired together. Both functions must follow the same rules. In this case, both equals and hashCode are based purely on the Recipe objects contained in a Cookbook.

    /**
     * Compute the hashcode by adding all recipe hashcodes together.
     *
     * @return integer hashcode
     */
    @Override
    public int hashCode()
    {
        //...
    }

5 Interface Completeness

The initial Cookbook class has an addRecipe function. If the class allows Recipes to be added, removal of Recipes should probably be possible too.

    /**
     * Remove a Recipe
     *
     * @param toRemove index of Recipe to remove
     */
    public void removeRecipe(int toRemove)
    {
        //...
    }

6 Big-3 and clone

In Java the C++ Big-3 become a single Java clone function. I need to be able to create a deep copy. Adding clone requires two changes. First, I need to modify the initial class line:

public class Cookbook
    implements Cloneable

The implements Cloneable indicates that this class override the clone method:

    /**
     * Create a deep copy.
     */
    @Override
    public Cookbook clone()
    {
        //...
    }

This clone method takes the place of a Copy Constructor.

7 Iterators

A Cookbook has multiple Recipes. What design pattern allows us to iterator over a collection? The Iterator Pattern.

Similar to clone, we must implement an interface. This time the interface is templated:

public class Cookbook
    implements Cloneable, Iterable<Recipe>

The Iterable interface requires that an iterator function be provided.

    /**
     * This depends on implementation decisions (e.g., selected data structure).
     */
    @Override
    public Iterator<Recipe> iterator()
    {
        //...
    }

8 Putting the Pieces Together

We have discussed the flaws in the initial Cookbook class. Putting the code snippets together is a good opportunity to practice a little Java. I will leave writing the full, complete, and corrected Cookbook class as an exercise to the reader.