7.7. Abstract Classes¶
We will motivate this section using a thought exercise:
Thought Exercise
Given the UML diagram for the animal hierarchy used in the previous section:
Consider the following block of code:
String name = "Freddie";
Animal myAnimal = new Animal(name, ______, ______); // blanks 1 and 2
myAnimal.makeSound(); // print the sound the animal makes
What would you expect to be printed when we call
makeSound
on anAnimal
object?What do you think belongs in blanks 1 and 2 corresponding to the
genus
andspecies
of theAnimal
object? Can we specify this information?
Thought Exercise Discussion (open after considering the points above)
The method makeSound
would either:
Have a separate if-statement for each allowed genus/species (potentially dozens or hundreds!) to make a specific sound, or
It would have to print a generic animal sound. Something like “the animal makes a noise” instead of “Bark!” (for a dog).
Scenario #1 is not ideal because every time we add a class to
the hierarchy, we would have to go in and modify the Animal
class’s makeSound
method. When we add a new class, that
should not force us to modify existing classes.
Scenario #2 is not ideal because we don’t get sounds that correspond to the type of animal we have created.
What about the genus and species? Well, its hard to specify these
inputs if we are creating a generic Animal
object because
there are no correct values. If you wanted to put a specific
genus and species, wouldn’t it make more sense to create a more
appropriate type of object (a Cat
, Dog
, Elephant
, etc.)?
Given the confusing nature of what it really means to create
an Animal
object, we would expect that users would always
prefer to create objects of specific animal types. Even though
the Animal
class shouldn’t be instantiated, we still need
to use it to gain the benefits of inheritance.
In Java, Abstract Classes are an elegant solution to this problem. Abstract Classes cannot be instantiated. However, they can still contain instance variables and methods that can be passed down to child classes. They can also include abstract methods (just like interfaces can)!
By declaring the Animal
class abstract
, we are telling
the users of our hierarchy that they should not instantiate it
(it doesn’t really make sense to do so) and that they should
instantiate one of the child classes instead.
Take a moment to consider the updated UML Class Diagram below:
Note
By making the Animal
class abstract, the users of our hierarchy
cannot instantiate the Animal
class. However, we still get the
benefits of inheritance as any instance variables/methods created
in Animal.java
are automatically copied to the child classes
and do not have to be repeated in each!
Test Yourself
Assume that in Animal.java
, the implementation of makeSound
looks
like this:
public abstract void makeSound();
Also, assume the following overrides in the child classes:
@Override
public void makeSound() {
System.out.println(this.getName() + " growls and then barks!");
} // makeSound
@Override
public void makeSound() {
System.out.println(this.getName() + " purrs and then meows!");
} // makeSound
What would be output from the following code block?
Note the two different data types of dog
and dog2
.
Animal dog = new Dog("Juno", "Jack Russell Terrier");
System.out.println("Dog:\n");
System.out.println(dog.getGenus());
System.out.println(dog.getSpecies());
System.out.println(dog.getName());
System.out.println(dog.makeSound());
Dog dog2 = new Dog("Vince", "Mix");
System.out.println("Dog 2:\n");
System.out.println(dog2.makeSound());
Test Yourself Solution (Open after attempting the question above)
Dog:
Canis
Familiaris
Juno
Juno growls and then barks
Dog 2:
Vince growls and then barks