S-111
  • Home
  • Lectures
  • Problem Sets
  • Sections
  • Syllabus
  • Schedule
  • Staff
  • Resources
  • Canvas
  • Ed Discussion
  • Gradescope

Section 13

  • Inheritance
  • Polymorphism

Inheritance

As we saw today in lecture, a class can extend another class. Consider the Animal, Cat and Hamster classes in the following Java source code files:

  • Animal.java
  • Cat.java
  • Hamster.java

Survey each class and take note of the class relationships.

  1. What does it mean to say that a subclass inherits the fields and methods of its superclass? 1

  2. The Cat class cannot directly access the fields it inherits from Animal. Why not? 2

  3. The subclass constructor typically calls the superclass constructor to initialize the inherited fields. Write a constructor for the Cat class above. It should take as parameters the cat’s name and a boolean indicating whether it is short-haired, and it should call the superclass’ constructor. 3

  4. To manipulate the inherited fields, the subclass can use the inherited accessor and mutator methods. Write a toString method for the Cat class above. It should return a string consisting of the cat’s name followed by either " (short-haired)" or " (long-haired)". To do so, it should call the superclass’ toString method. 4

  5. The subclass can override an inherited method, replacing it with a version that is more appropriate. Write an isSleeping method for the Cat class. It should reflect the fact that cats seem to sleep all of the time! 5

  6. A subclass method can call an overridden method from its superclass by using the keyword super instead of this. Write an isSleeping method for a Hamster class. The method should override the one inherited from Animal, but it should call the overriden method to do most of the work. You should assume that the sleeping behavior of hamsters is the exact opposite of the sleeping behavior specified by the Animal version of isSleeping, since hamsters are nocturnal. 6

  7. What is the difference between overriding and overloading a method? 7

The Hamster class still does not compile. The Java compiler produces the following error:

Hamster.java:10: error: constructor Animal in class Animal
  cannot be applied to given types;

public class Hamster extends Animal {
       ^
  required: String,int
  found: no arguments
  reason: actual and formal argument lists differ in length
1 error

Recall that when a subclass has no constructors, the Java compiler will automatically provide a default constructor with no parameters that attempts to call the superclass’ constructor with no parameters.

  1. Suppose we try to fix the above error by writing the following Hamster constructor:

    public Hamster() {
        this.eatsInsects = false;
    }
    

    Will this solve our problem? If not, make any appropriate changes to this constructor to make the program compile. 8

Polymorphism

Thanks to Java’s polymorphism features, we can do something like this:

ClassA myObject = new ClassB(...);

Where ClassB extends ClassA, or equivalently, ClassB is a subclass of ClassA. Specifying a more general type for myObject than the actual type of the object can be useful when writing a method that needs to take more than one type of object as a parameter, or when creating an array of objects of different but related types. However, this also limits us to using only the methods defined in the more general type ClassA.

For example, if we wanted to have an array containing different Hamster and Cat objects, but we didn’t want to keep them in separate arrays, we could define the array as follows:

Animal[] zoo = new Animal[10];

Then, any element of the array could be of type Animal or any subclass of Animal. In other words, this would be allowed:

zoo[0] = new Hamster();
zoo[1] = new Cat();

Consider the following class definitions:

public class A extends B {
    public String m1() {
        return "at";
    }
    public String m3() {
        return "an";
    }
}

public class B extends C {
    public String m1() {
        return "bat";
    }
    public String m2() {
        return this.m1();
    }
}

public class C {
    public String m1() {
        return "cat";
    }
    public String m2() {
        return "can";
    }
}
  1. Draw an inheritance hierarchy for these classes. Include the names of the methods that each class defines. 9

  2. Which of these assignments would be allowed, taking into account what polymorphism allows? 10

    1. B myObj = new A();
    2. B myObj = new C();
    3. C myObj = new A();
    4. A myObj = new B();
  3. Consider the following code fragment:

    B bee = new A();
    C cee = new B();
    

    What is the declared type of each object? What is the actual type of each object? 11

  4. At compile time, when determining if a non-static method call is valid, the compiler starts with the declared type of the called object and goes up the hierarchy as needed to see if it can find the method. If the compiler cannot find the method starting at the declared type and going up in the inheritance hierarchy, the statement will not compile.

    Given the objects from the previous problem, which of these method calls would the compiler allow? 12

    1. bee.m1();
    2. bee.m2();
    3. bee.m3();
    4. cee.m1();
    5. cee.m2();
    6. cee.m3();
  5. At runtime, when determining which version of a method will run, the interpreter starts with the actual type of the object and goes up the hierarchy as needed, stopping at the version of the method that is closest to the actual type. This is called dynamic binding, because the decision about which method to call is made as the program runs. Dynamic binding also applies to method calls on the called object that occur within other methods. (For an example, see the last “Dynamic Binding” slide from the unit 5-2 lecture notes.)

    Determine the value returned by each of the method calls from the previous task that actually do compile. 13


  1. The subclass gets its own copy of all fields and methods defined in the superclass. ↩

  2. The Animal class defines the name and numLegs fields as private. Therefore the Cat class must access them using the accessors. ↩

  3. Here’s the constructor:

    public Cat(String name, boolean shortHaired) {
        super(name, 4);
        this.isShortHaired = shortHaired;
    }
    

    ↩

  4. Here’s the toString method:

    public String toString() {
        if (this.isShortHaired) {
            return super.toString() + " (short-haired)";
        } else {
            return super.toString() + " (long-haired)";
        }
    }
    

    ↩

  5. Here’s the new isSleeping method, which overrides the Animal class’ version:

    public boolean isSleeping(int hour, int minute) {
        return true;
    }
    

    ↩

  6. Here’s the new Hamster method:

    public boolean isSleeping(int hour, int minute) {
        return !super.isSleeping(hour, minute);
    }
    

    ↩

  7. Overriding a method refers to a subclass implementing its own version of a method that it inherits from its superclass.
    Overloading a method refers to providing more than one method with the same name, but a different number or type of parameters. ↩

  8. We must make a call to the constructor of the Animal class (which we can access using super), only on the first line of the subclass’ constructor.

    public Hamster(String name) {
        super(name, 4);
        this.eatsInsects = false;
    }
    

    We could also define a Hamster constructor that specifies whether the hamster eats insects:

    public Hamster(String name, boolean eatsInsects) {
        super(name, 4);
        this.eatsInsects = eatsInsects;
    }
    

    Note that we could have added an empty constructor for Animal, which would have made the implicit super() calls in the Hamster constructor work. However, we couldn’t get the hamster’s name or number of legs into the name and numLegs fields using a no-argument constructor, so we’d have to use a mutator after the fact. ↩

  9. Here’s the diagram:

     ↩

  10. B myObj = new A(); works, since A is more specific than B.
    B myObj = new C(); does not work, since C is more general than B.
    C myObj = new A(); works, since A is much more specific than C.
    A myObj = new B(); does not work, since B is more general than A.  ↩

  11. The declared type of bee is the class B, and its actual type is A.
    The declared type of cee is the class C, and its actual type is B. ↩

  12. bee.m1() compiles, since B defines an m1() method.
    bee.m2() compiles, since B defines an m2() method.
    bee.m3() does not compile, since neither B nor any superclasses of B define an m3() method.
    cee.m1() compiles, since C defines an m1() method.
    cee.m2() compiles, since C defines an m2() method.
    cee.m3() does not compile, since neither C nor any superclasses of C define an m3() method. ↩

  13. bee.m1() returns "at". Since the actual type of the object in bee is A, we use the m1() method in the A class.
    bee.m2() returns "at". (m2() is not defined in A, but it is defined in the superclass of A, which is B. Then the m2() in B calls this.m1(), and the dynamic binding process begins, starting from A, which is why "at" is returned by this.m1().
    cee.m1() returns "bat".
    cee.m2() returns "bat". ↩

Last updated on July 14, 2026.