return to lecture notes index

15-100 Lecture 11 (Friday, February 4, 2005)

Inheritance

So what do you think of when you hear the word inheritance? Money that you receive from your parents as a birthright, maybe your genetic code. Inheritance exists in Java too. Remember back to the beginning of the semester when we discussed parent (super, base) classes and their subclasses (children, derived classes). Inheritance works very similar in Java to the way it works with birthrights. When a subclass is made the subclass inherits all of the parent class's methods.

All classes are a subclass of the class Object. This provides a uniform interface across all Objects and thus all classes. When you create a class it automatically inherits several methods from the object class. Let's look at two methods in Object:


class Object{
   public String toString(){
      //magic performed here
   }
   public boolean equals(Object o){
      //more magic performed here
   }
}

Now these two inherited methods aren't too useful in the grand scheme of things. The inherited equals() performs the == operation until you override the method by writing your own version of it in the subclass you are working on. (Don't remember what == does? look at the bottom of the lecture notes for a refresher.)

The toString() is equally as useful. The inherited toString prints out the name of the subclass and then a series of numbers and letters which tell where the reference variable, holding the instance of the class you want to go toString(), is pointing to (aka where the object is being stored in the computer's memory). Typically it would be nice if the toString provided the critical information about our object's state. (This usually requires the method to return a String containing most of the information that the object's instance variables are holding.)

Bicycle Example
class Bicycle {
  //notice how these variables are self documenting, you know what is being 
  //stored and what units it is stored in.
  private String color;
  private int wheelDiameterInches;
  private double priceDollars;

  //the constructor
  public Bicycle (String color, int wheelDiameterInches, double priceDollars) {
    this.color= color;
    this.wheelDiameterInches = wheelDiameterInches;
    this.priceDollars = priceDollars;
  }

  //replacing the generic toString() that all objects inherit without us writing 
  //a toString in Bicycle this would print something like "Bicycle@47b480".  
  //Note that it will now be nicely formatted when printed out.  
  public String toString() {
    return "Bicycle: " + color + ", " +wheelDiameterInches + "in, $" + priceDollars;
  }

  //notice that this is the same signature of as the equals() method in class Object
  //we have to do this in order to successfully override the inherited method
  //Why do we have to pass in Object o?  Because we have to match the signature
  //of the inherited method.  We can then implicitly cast o as a (Bicycle).
  public boolean equals (Object o){
    //we are type casting "(Bicycle)" o.  saying we know that o can be any object
    //but we, as the programmer, are saying only Bicycles will be passed into this method.
    //This casting will cause the program to die early if a non-Bicycle object is passes
    //in as a parameter...ie we won't be trying to compare a bicycle to a tiger.
    //The error thrown when this happens? A ClassCastException.
    Bicycle b = (Bicycle) o;
    //What's with the "!"...it negates the boolean that is returned...ie it flips
    //a boolean from true to false or from false to true
    if (!color.equals(o.color)){
      return false;
    }
    if ( priceDollars != o.priceDollars)
      return false;
    }
    
		//this is the same logic as the if statement above
    //Pop quiz: why are we using the == instead of .equals()?
    //Don't know?  check out the last section of the notes
    if (!(wheelDiameterInches == wheelDiameterInches)){
      return false;
    }
  }

  public int compareTo (Bicycle b) {
    return (int)((this.price - b.price)*100); //multiply by 100 to not lose cents
  }

  //this is going to be the tester
  public static void main (String [] args) {
    Bicycle flyer = ("Red", 24, 50.00);
    System.out.println(flyer);
  }
}
Programming the smart way

Kesden wisdom suggests...test as you program. Aka as you write each new method implement it into your tester.java. This means if you find a bug while testing, you have a very small section of code in which it must be (typically only one method long). This means you don't have to search your whole program for the error. Thus early testing gives you a quicker and easier debugging process.

== and .equals()

There's a big difference between == and equals(). A programmer needs to know which comparison to use and when. The == always does the same job. It looks at the values that two variables are holding and returns a boolean saying whether the two values are equal. This works well enough for the primitive types (such as int, double, char, long, etc.) since a variable for a primitive holds the primitive's value. However when you start using the == operator with Objects (such as Strings) you need to remember that a variable "holding" a String doesn't actually hold the String's value. Instead they hold a reference to the String and where it is in the computer's memory. For this reason the line ("Hi"=="Hi"); would evaluate to false. That line makes two instances of a String that says "Hi". Even thought the state of the Strings are the same the instances are different and thus == will say that these two Strings aren't the same String.

So how do you compare two Strings to see if they have the same state or same words "inside" of them? That is what the .equals() is for. It looks at the Object's state and says whether the two states are the same.

Since the inherited .equals() for Objects uses the == comparison you can see why it is useful to always redefine this inherited method.