Tutorials
Inheritance

Object oriented languages such as Java provide a mechanism for reusing code known as inheritance.

  • Inheritance allows you to take an existing class and specialize it.
  • The original class is called the super, base, or parent class (these terms are used interchangeably).
  • The class that specializes the base class is called the sub, derived, or child class (these terms are used interchangeably).
  • Inheritance is useful because it allows us to:
    • reuse existing code without modifying the original code
    • create a family of related classes
    • treat objects from all the related classes in the same way

Object Class

  • All classes in Java are descendants of the Object class.
  • The Object class defines a number of methods, e.g.,
    • equals() which can be used to see if two objects have the same value.
    • toString() which returns a String representation of the object.
  • Because every class is a descendant of the Object class, all other classes inherit these methods, i.e., it is possible to call toString() on an object from a class you wrote even if you didn't implement the toString() method.

Suppose we have the following class defined:

public class A {
  
}

Even though the class has no methods explicitly implemented, we can still do the following:

A instance = new A();
System.out.println(instance.toString());

Running this code would produce something like this:

packageName.A@82ba41
  • Calling toString() on the instance executes the implementation of toString() found in the Object class.
  • The string returned specifies the package name and class name, separated by a period, and then the memory address (in hexadecimal) where the object is stored.
  • Essentially, the Object class provides generic behavior for the toString() method... no matter what kind of object we have, we can always describe it with a string containing the package name, class name, and memory location where it is stored.
  • When we create a specialized class (one that inherits from Object), it may make sense to rewrite the functionality of toString() that was inherited from Object.
  • We can do this be overriding the definition of the toString() method like this:
public class A {
  public String toString() {
    return "This is an object from the silliest class I have ever seen";
  }
}

Running the code below results in This is an object from the silliest class I have ever seen.

A instance = new A();
System.out.println(instance.toString());
  • Calling toString() now executes the specialized version of the toString() method that has been customized for the subclass.

Class Declaration Syntax

  • Java assumes that we are inheriting from the Object class unless told otherwise.
  • The extends keyword is used explicitly identify a superclass.
  • Consider the following Shape class:
public class Shape {
  private Color color;
  private double xCoord;
  private double yCoord;
  
  public Shape() {
    color = Color.WHITE;
    xCoord = 0.0;
    yCoord = 0.0;
    System.out.println("Shape: constructor");
  }
  
  public void draw() {
    System.out.println("Shape: draw");
  }
  
  public void erase() {
    System.out.println("Shape: erase");
  }
  
  public void move() {
    xCoord += 5.0;
    yCoord += 5.0;
    System.out.println("Shape: move");
  }
  
  public void zoom(double magnification) {
    System.out.println("Shape: zoom");
  }
  
  public Color getColor() {
    System.out.println("Shape: getColor");
    return color;
  }
  
  public void setColor(Color color) {
    this.color = color;
    System.out.println("Shape: setColor");
  }
}
  • We can inherit from Shape to create a class that describes a particular type of shape. For example,
public class Circle extends Shape {
  private double radius;
  
  public Circle() {
    super();
    radius = 0.0;
    System.out.println("Circle: constructor");
  }
  
  public void draw() {
    System.out.println("Circle: draw");
  }
  
  public void erase() {
    super.erase();
    System.out.println("Circle: erase");
  }
  
  public void zoom(double magnification) {
    radius *= magnification;
    System.out.println("Circle: zoom");
  }
  
  public void setRadius(double radius) {
    this.radius = radius;
    System.out.println("Circle: setRadius");
  }
  
  public double getRadius() {
    System.out.println("Circle: getRadius");
    return radius;
  }
}
  • By extending Shape, our Circle class inherits the three fields from.
  • Because color, xCoord, and yCoord fields are declare as private, they are not visible in the Circle class, even though they exist as part of a Circle object.
  • Notice that the Circle constructor calls the Shape constructor by calling super(). If a superclass has multiple constructors, we can specify the desired constructor by passing the correct parameters to super().
  • The Circle class does not implement the getColor() and setColor() methods. The implementations provided by the Shape class are inherited, i.e., used, by the Circle class.
  • The Circle class does implement its own versions of draw(), erase(), and zoom(). Rewriting the functionality of a superclass's method is called overriding.
    • draw() and zoom() provide a completely new implementation for the Circle class
    • erase() makes use of the Shape's erase() implementation by calling super.erase() and then adds some additional functionality.
  • getRadius() and setRadius() are additional methods that are only available to the Circle class.

Can you figure out what the following program will display?

public static void main(String[] args) {
  Shape shape = new Shape();
  Circle circle = new Circle();
  shape.move();
  circle.move();
  shape.draw();
  circle.draw();
  shape.zoom();
  circle.zoom();
  shape.erase();
  circle.erase();
  circle.getRadius();
}

Note:

  • shape.getRadius() would produce a compiler error because getRadius() is not defined in the Shape class.
  • A reference to a Shape may point to a Circle object since a circle can do everything that a shape can do.
  • A reference to a Circle may not point to a Shape object since a circle can do more than a shape (like setRadius()).

The is-a Lingo

  • Often inheritance is discussed using is-a terminology.
  • The terminology is used to help identify when it is appropriate to use inheritance in object oriented design.
  • When designed correctly, a subclass object is a superclass object.
  • In our previous example, it makes sense to say that a Circle is a Shape.
  • Using this terminology may clarify the notes above:
    • A reference to a Shape may point to a Circle object since a circle is a shape.
    • A reference to a Circle may not point to a Shape since a shape isn't necessarily a circle.

Visibility Modifiers

  • We've already discussed the private and public visibility modifiers.
  • Attributes (fields or methods) that are declared public are accessible to anyone.
  • Attributes that are declared private are only accessible to the class.
  • Attributes that are declared protected are accessible to the class and any descendent class.

Last modified: Monday, 29-Jul-2024 06:54:43 EDT