Return to lecture notes index

15-100 Lecture 16 (Friday, October 7, 2005)

Quick Quiz

  1. Given the partial class specification below, please add a toString method that adheres to the usual conventions and produces output as below:

             Greg Kesden, 138 units
             Mark Stehlik, 154 units
             

             public String toString () {
               return name + ", " numberOfUnits + " units";
             }
             


  2. Consider the class as provided, before you solved Question #1 (above). Would it compile and run without error? If not, why not, what error results, and when (compile time or run time)? Why? If it does compile and run, what is the output? Why?

    It would compile and run. The output would be fairly useless. Each println() would proiduce Student@XXXXX, where XXXXXX would be a long number uniquely identifying the instance. This is because our class would be inheriting the toString() method from Object, but not overriding it with somethign specialized for a Student.

  class Student {
    private String name;
    private int numberOfUnits;

    public Student (String name, int numberOfUnits) {
      this.name = name;
      this.numberOfUnits = numberOfRUits;
    }

    // Skipping the meat of the class

    public static void main (String[] args) {

      Student greg = new Student ("Greg Kesden", 138);
      Student mark = new Student ("Mark Stehlik", 154);

      System.out.println (greg); // Remember, println() calls toString()
      System.out.println (mark);
    }

  }
  

An equals() Method

Last meeting, we created a Student class and began discussing the equals() method. Today, we'll actually write that method. The basic strategy is going to be as follows. We'll walk through each and every property of the class, comparing "this" instance to the instance that is passed in, by reference, as an argument. For each property, if the two instances don't match, we'll immediately return false -- they cannot be the same if they differ in any property. After considering each property, if none differed, we'll come to the conclusion that they must be the same, and return true.

As a quick reminder, our Student class had instance variables as follows:

  private String name; //"Last, First M", e.g. "Kesden, Gregory M"
  private String major;
  private String year;  //Freshman, Sophmore, Junior, Senior, Senior-plus, Masters, Doctoral, Other
  private int units;
  private double qpa;
  

So, let's write the equals class. Remember, The signature of the method must exactly match the one we inherit from the Object class, or we won't be overriding it.

  public boolean equals (Object o) {
    Student s = (Student) o;

    if (!this.name.equals(s.name)) return false;
    if (!this.major.equals(s.major)) return false;
    if (!this.year.equals(s.year)) return false;
    if (this.units != s.units) return false;
    if (this.qpa != s.qpa) return false;

    return true;
  }
  

What's That Funny Cast?

Let's consider the first line of the equals() method above:

  Student s = (Student) o;
  

So, do we need this line? Yes. First, without casting the generic Object to a Student, we cannot access the properties that are specific to the Student type. Second, it is cheap insurance in case someone does something stupid, like pass in a Chicken. The problem with an Object is that it can be anything. And, if what they passed in was a different type, but happened to have the same properties, we could end up comparing, for example, apples and oranges based on weight, color uniformity, and ripeness. Not good if we should be comparing only apples and apples.

So, why not change the type of the argument, then? Perhaps as follows? This way, only a Student can get passed in:

  public boolean equals (Student s) 
  

The problem here is that this almost works. It keeps non-students from entering our equals method. But, unfortunately, Java will keep searching the table and will eventually bump into the generic version, accepting an Object, within the Object class. This will, no doubt, return false -- and our program will continue on under false pretenses.

So, to be rigorous in validating types, we keep the argument the same, so that we do override the Object class's equals() method. But, we perform the cast, to enforce type check, on the first line.

If the Object is really a Student, the cast is fine. If not, a ClassCastException (error) is thrown and our program stops. This might sound bad, but it really isn't -- or, at least, it isn't as bad as it could be. We discover the problem much earlier than we would if it kept running. I'd much rather know about the problem exactly as the wrong type is passed into the equals() method, than much later when (hopefully) someone notices the incorrect results.

This is defensive programming!