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:
Survey each class and take note of the class relationships.
What does it mean to say that a subclass inherits the fields and methods of its superclass? 1
The Cat class cannot directly access the fields it inherits from
Animal. Why not? 2
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
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
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
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
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.
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
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"; } }
Draw an inheritance hierarchy for these classes. Include the names of the methods that each class defines. 9
Which of these assignments would be allowed, taking into account what polymorphism allows? 10
B myObj = new A();B myObj = new C();C myObj = new A();A myObj = new B();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
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
bee.m1();bee.m2();bee.m3();cee.m1();cee.m2();cee.m3();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
The subclass gets its own copy of all fields and methods defined in the superclass. ↩
The Animal class defines the name and numLegs fields as private.
Therefore the Cat class must access them using the accessors. ↩
Here’s the constructor:
public Cat(String name, boolean shortHaired) { super(name, 4); this.isShortHaired = shortHaired; }
Here’s the toString method:
public String toString() { if (this.isShortHaired) { return super.toString() + " (short-haired)"; } else { return super.toString() + " (long-haired)"; } }
Here’s the new isSleeping method, which overrides the Animal class’
version:
public boolean isSleeping(int hour, int minute) { return true; }
Here’s the new Hamster method:
public boolean isSleeping(int hour, int minute) { return !super.isSleeping(hour, minute); }
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. ↩
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. ↩
Here’s the diagram:
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. ↩
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. ↩
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. ↩
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.