Unit 5: Inheritance

Table of Contents

  1. Inheritance
  2. Super
    1. Subclass Constructors
    2. Geometric Object
  3. Overriding Methods
    1. @Override
    2. To String
    3. Equals
    4. Overload vs Override

Inheritance

Inheritance is something in Java that allows a class to extend or get all attributes and behaviors from another class and then add onto it. Inheritance describes an is-a relationship. For example, we could have a class named Animal which the Chicken class extends. This makes sense because all chickens are animals, and thus have the characteristics and behaviors of animals in addition to whatever characteristics and behaviors that are unique to chickens.

Note: A class can only extend one other class. When a subclass extends a superclass, it inherits all of the fields and methods from its superclass.

To inherit from a class (referred to as the parent class or superclass), you use the extends keyword. (The class that extends another class is referred to as the child class or subclass.)

Animal Class

public class Animal {
    ...
}

Chicken Class

public class Chicken extends Animal {
    ...
}

Super

Analyze the Animal and Chicken classes below.

Animal Class

public class Animal {
    String name = "";
    int hunger = 50;

    public Animal() {
    }

    public Animal(String name) {
        this.name = name;
    }

    public void makeNoise() {
        System.out.println("Makes noise");
    }

    public void eat() {
        hunger -= 10;
    }

    public void move() {
        hunger += 10;
    }

    public boolean isHungry() {
        return hunger > 50;
    }
}

Chicken Class

public class Chicken extends Animal {
    int numberOfFeathers = 5_000;

    public Chicken() {
    }

    public Chicken(String name) {
        super(name);
    }

    public Chicken(String name, int numberOfFeathers) {
        super(name);
        this.numberOfFeathers = numberOfFeathers;
    }
}

You may have noticed in the code that there is a keyword super. Similar to the this keyword, super refers to the instance of the superclass. To call a superclass constructor, you would use super(args) (if there are any arguments to be given).

You can reference superclass variables and methods using super as well. Just use dot notation. For example, I could use super.eat() in the Chicken class. In this case, you can’t use super.name because name is a private field. (Instead, you would use the getter method.)

Note: You may have noticed that we set the default number of feathers for a Chicken to be 5_000. This is the same as 5000. The _ is there to make it easier to read long numbers, and is valid Java syntax.

Subclass Constructors

Subclass constructors implicitly call the default superclass constructor. That is, these two versions of declaring the Chicken constructor are equivalent.

public Chicken() {
}
public Chicken() {
    super();
}

You have the option to explicitly call a superclass constructor with argument(s) if you want to customize how the subclass constructor works. For example, this is done in the 1- and 2-argument Chicken constructors.

public Chicken(String name) {
    super(name);
}

public Chicken(String name, int numberOfFeathers) {
    super(name);
    this.numberOfFeathers = numberOfFeathers;
}

If you would like to learn about constructor chaining, you can take a look at ConstructorChaining.java

Geometric Object

You can also use inheritance to model relationships between shapes. Let’s create a class called GeometricObject which models a general shape and a subclass called Circle which inherits from GeometricObject.

GeometricObject Class

public class GeometricObject {
    private double x; // x coordinate of the center
    private double y; // y coordinate of the center
    private String color;
    private boolean filled;

    /** Constructs a default {@code GeometricObject} */
    public GeometricObject() {
        x = 0;
        y = 0;
        color = "white";
        filled = false;
    }

    /**
     * Constructs a {@code GeometricObject} with center at (x, y) and given color
     * @param x  x coordinate of center of this {@code GeometricObject}
     * @param y  y coordinate of center of this {@code GeometricObject}
     * @param color  color of this {@code GeometricObject}
     * @param filled  whether this {@code GeometricObject} is filled with the given color
     */
    public GeometricObject(double x, double y, String color, boolean filled) {
        this.x = x;
        this.y = y;
        this.color = color;
        this.filled = filled;
    }

    /**
     * Returns the x coordinate of the center
     * of this {@code GeometricObject}
     * @return the x coordinate of the center
     * of this {@code GeometricObject}
     */
    public double getX() {
        return x;
    }

    /**
     * Sets the x coordinate of the center
     * of this {@code GeometricObject}
     * @param x  new x coordinate
     */
    public void setX(double x) {
        this.x = x;
    }

    /**
     * Returns the y coordinate of the center
     * of this {@code GeometricObject}
     * @return the y coordinate of the center
     * of this {@code GeometricObject}
     */
    public double getY() {
        return y;
    }

    /**
     * Sets the y coordinate of the center
     * of this {@code GeometricObject}
     * @param y  new y coordinate
     */
    public void setY(double y) {
        this.y = y;
    }

    /**
     * Returns the color of this {@code GeometricObject}
     * @return the color of this {@code GeometricObject}
     */
    public String getColor() {
        return color;
    }

    /**
     * Sets the color of the geometric object
     * @param color  new color
     */
    public void setColor(String color) {
        this.color = color;
    }

    /**
     * Returns whether the geometric object is filled
     * @return {@code true} if this {@code GeometricObject} is filled
     */
    public boolean isFilled() {
        return filled;
    }

    /**
     * Sets the whether or not the geometric object is filled
     * @param filled  new status of filled
     */
    public void setFilled(boolean filled) {
        this.filled = filled;
    }
}

Note: The {@code some text here} content in the JavaDoc comments format the text inside braces as code. You can also use <code>some text here</code> to do this.

Circle Class

public class Circle extends GeometricObject {
    private double radius;

    /** Constructs a default {@code Circle} object */
    public Circle() {
        super();
        radius = 0;
    }

    /**
     * Constructs a {@code Circle} object with given radius
     * @param radius  radius of this {@code Circle} object
     */
    public Circle(double radius) {
        super();
        this.radius = radius;
    }

    /**
     * Constructs a {@code Circle} object with given radius, center (x, y), color, and filled value
     * @param radius  radius of this {@code Circle} object
     * @param x  x coordinate of the center of this {@code Circle} object
     * @param y  y coordinate of the center of this {@code Circle} object
     * @param color  color of this {@code Circle} object
     * @param filled  whether this {@code Circle} object is filled with the given color
     */
    public Circle(
        double radius,
        double x,
        double y,
        String color,
        boolean filled
    ) {
        super(x, y, color, filled);
        this.radius = radius;
    }

    /**
     * Returns the radius of this {@code Circle} object
     * @return the radius of this {@code Circle} object
     */
    public double getRadius() {
        return radius;
    }

    /**
     * Sets this {@code Circle} object's radius to given radius
     * @param radius  new radius
     */
    public void setRadius(double radius) {
        if (radius >= 0) {
            this.radius = radius;
        }
    }

    /**
     * Returns the diameter
     * @return the diameter
     */
    public double getDiameter() {
        return radius * 2;
    }

    /**
     * Returns the area of this {@code Circle} object
     * @return the area of this {@code Circle} object
     */
    public double getArea() {
        return Math.PI * radius * radius;
    }

    /**
     * Returns the perimeter of this {@code Circle} object
     * @return the perimeter of this {@code Circle} object
     */
    public double getPerimeter() {
        return 2 * Math.PI * radius;
    }

    // for use during TestCircle.java
    @Override
    public String toString() {
        return "Circle radius " + radius;
    }
}

Note: In the source code on GitHub, it is called the CircleInherit class instead of the Circle class.

Overriding Methods

You may have noticed the @Override annotation above the toString method in the Circle class. What does override mean? What does the @Override do? And what is the toString method?

Overriding a method occurs when a subclass changes the implementation of a method defined in the superclass. Let’s look at the Animal and Chicken classes again. (Some fields and methods have been omitted for brevity.)

Animal Class

public class Animal {
    ...

    public void makeNoise() {
        System.out.println("Makes noise");
    }

    ...
}

Chicken Class

public class Chicken extends Animal {
    ...

    @Override
    public void makeNoise() {
        System.out.println("Cluck!");
    }
}

TestChicken Class

public class TestChicken {
    public static void main(String[] args) {
        Animal myAnimal = new Animal();
        myAnimal.makeNoise();

        Chicken myChicken = new Chicken();
        myChicken.makeNoise();
    }
}

Output

Makes noise
Cluck!

Notice that the method signatures in both classes are exactly the same. However, the method bodies are different - we call that a different implementation because they do different things.

@Override

The @Override annotation is an optional thing you can put directly above method signatures in order to indicate that the method you are writing overrides another method. This is useful not only so that you and other people on your programming team know that it overrides a method, but also helps you avoid errors during compile. If the Java compiler detects an @Override annotation on a method that doesn’t actually override anything, it will give you an error.

To String

The toString method is used to get the object in String form. All classes have a toString method, even if you don’t realize it. This is because all classes automatically extend the built-in Object class, which defines common fields and methods for all classes in Java.

This is the method signature for the toString method:

public String toString()

By default, the toString method basically returns a String containing information about where the object is in memory, which isn’t very helpful to humans. That’s why we often override the toString method so that it displays something readable.

Note: The toString method is called implicitly whenever you attempt to print an object.

To see an example of the toString method in action, analyze the following code.

Point Class

public class Point {
    public double x;
    public double y;

    public Point() {
        x = 0;
        y = 0;
    }

    public Point(double x, double y) {
        this.x = x;
        this.y = y;
    }

    @Override
    public String toString() {
        return "(" + x + ", " + y + ")";
    }
}

TestPoint Class

public class TestPoint {
    public static void main(String[] args) {
        Point pt = new Point(5, 6);
        System.out.println(pt);
    }
}

Output

(5, 6)

Equals

Another commonly overriden method that originates from the Object class is the equals method. This method is used to check if two objects have the same content. It should return true if the given object and this object are equal in content, and return false otherwise.

You might be thinking, wait, why do we need a method for that when we already have the equality operator (==)? Well, we need a method for this because the equality operator actually checks if two things reference the same location in memory. For things like primitives, it happens to be that this means that they’re equal. However, for objects, even if two objects are the exact same type and have the exact same fields, they won’t be “equal” according to the equality operator.

If you’re interested in seeing a demonstration of this concept, you can take a look at CompareObjects.java.

Another reason why having an equals method is important is because it allows the developer to decide under what circumstances 2 objects are equal. How do we tell if 2 Circles are equal? We could compare area, circumference, radius, diameter, center point, color, etc. Having an equals method in the Circle class allows us to specify exactly what we mean when we say 2 Circles are equal.

This is the method signature for the equals method:

public boolean equals(Object obj)

Let’s see the equals method in action using the Circle class. Some fields and methods are omitted for brevity.

public class Circle extends GeometricObject {
    ...

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Circle) {
            return ((Circle) obj).getArea() == this.getArea();
        }
        return false;
    }
}

Time to break down what the code above is actually doing. First, we have the @Override annotation to tell the Java compiler that we are overriding a method. Next, we match the equals method signature that is in the Object class in order to override it.

Next, we check if the given Object is an instance of Circle using the instanceof keyword. If this is not the case, we definitely know the two objects are not equal, so we return false.

However, if the object is an instance of Circle, we check if they have the same area. Notice that we need to cast obj to a Circle in order to use the getArea method. If the 2 Circles have the same area, we consider them equal and return true. Otherwise, we return false.

Overload vs Override

Programmers often confuse overloading and overriding methods. Sometimes they use the terms interchangeably. However, they are definitely not the same thing! Here are some pointers to help you remember the difference.

Overloading Overriding
Same method name, different parameter list EXACT same method signature
Can happen in same class or subclasses Happens in subclasses
Preserves general functionality Changes method implementation
Can overload a method many times in the same class Can only override once per class
Doesn’t have an annotation Has the @Override annotation

To view a demonstration of some of these concepts, you can take a look at OverloadVersusOverride.java.