11 Inheritance

AP Computer Science A Prep, 2024 - Rob Franek 2023

11 Inheritance
Part V: Content Review for the AP Computer Science A Exam

Inheritance is the quintessential way—and the only way, for our purposes—to create direct relationships between classes. An inheritance hierarchy is designed in order to quantify this relationship, much like a family tree, and it defines the “parent” class and all of its “child” classes. A carefully designed hierarchy implemented as an inheritance relationship between classes is arguably the most powerful programming technique that you will learn in this course.

HIERARCHIES & DESIGN

The designing of the hierarchy is critical in implementing inheritance. As the programmer, you have limitless ways to design a hierarchy, so the importance of the planning process cannot be overstated.

The first decision for the programmer/designer—yes, there are programmers who focus solely on design—is to define the parent class of the situation. Typically, the parent class (superclass) is the most general form of the objects that will be instantiated in the overall program. Every other class in the program will lie lower in the hierarchy (subclasses of the superclass), but their objects will be more specific versions of the overall parent class. This setup creates an IS-A relationship between the parent and the child classes.

Let’s use potato chips as our example for this discussion. If we are designing a hierarchy of classes to properly represent potato chips, there are a ridiculous number of possibilities—would the parent class be PotatoChip? Potato? Snack? SaltySnack? JunkFood? So we have to make a decision based on the situation that we are given; if the programmer receives no context whatsoever, the design is very difficult and will produce unpredictable—yet all viable—results.

In this case, let’s use Snack as the superclass. A Snack object is very general; there may be many subclasses in our situation. Let’s define two subclasses for Snack, one for our purposes and an extra for practice: PotatoChip and Cookie. Since a potato chip is a snack (as is a cookie), the setup makes intuitive sense. Note how we define the relationship from the bottom (lowest subclasses) of the hierarchy to the top (highest superclass). Let us then define another, lower set of classes that will be subclasses of PotatoChip: BBQChip and OnionChip.

A visual representation of a hierarchy makes the design much easier to understand:

Image

As you can see, there are several IS-A relationships here. A BBQChip IS-A PotatoChip, which IS-A Snack. A BBQChip also IS-A Snack, by inheritance. All of these relationships as designed in the hierarchy should make intuitive sense and/or conform to the given specifications.

On the AP Exam, at least one free-response question and at least several multiple-choice questions typically focus on the design and/or implementation of inheritance relationships. Detailed specifications will be given in order to suppress the variety of exam responses to score.

Let’s discuss the benefits of the setup we have created. When we are ready to implement code, we should decide which variables and methods should be implemented, as well as their locations within the hierarchy. These variables and methods have a HAS-A relationship with the class to which they belong. This task is more formidable than it might seem; very general variables and methods should be closer to the top of the hierarchy, while more specific variables and methods should reside in lower subclasses. For example, virtually every snack food has some kind of flavor. Therefore, code to address the flavor of the snack food should be implemented in Snack, and it will be subsequently inherited by all of the subclasses: Since a Snack object HAS-A flavor, and a PotatoChip object IS-A Snack, a PotatoChip object also HAS-A flavor. What about an OnionChip object? It also HAS-A flavor, as described above.

Now consider code that addresses the crunch of an object in the hierarchy. Without any detail in specification, let’s conclude that it is arguable whether a cookie is crunchy, but all potato chips are crunchy. As a result, code that addresses the crunchy aspect of the program should NOT be implemented in Snack; that way Cookie does not inherit the code. Since all potato chips are crunchy, the appropriate location for the crunchy code would be in PotatoChip, and BBQChip and OnionChip will inherit the aspects and functionalities of that code.

If we wanted to address the spiciness of an object in our hierarchy, we might determine that, out of all of these classes, only the BBQChip has a spice factor worth quantifying. The spiciness code should then appear in BBQChip and will not be inherited, because BBQChip does not have any subclasses.

Classes inherit variables and methods from their superclasses, not the other way around.

It is also worth mentioning here that a class may not have more than one direct superclass. This design is called multiple inheritance and it is NOT allowed in Java.

It is important to note here that instantiating a BBQChip object will automatically instantiate an object each of both PotatoChip and Snack. In order for this to occur correctly, however, Java requires us to address their instantiation explicitly. Therefore, the constructor BBQChip must first make a call to super(), which invokes the constructor of PotatoChip, its superclass. If the super constructor requires (a) parameter(s), then the super() call must include those parameter(s) as well. Java will otherwise invoke super() on its own, often resulting in undesired results and an easily avoided missed point on a free-response question. As a rule, all classes in Java will instantiate the Object class, which is the ultimate generalization—think: if I look around the room right now, what isn’t an object?

Consider the following class:

Image

public class College extends School

{

// private data fields not shown

// the only constructor in this class

public College (String town, double tuition)

{

// code not shown

}

// other methods not shown

}

1.In order to write LawAcademy, a subclass of College, which of the following constructors is valid?

I.   public LawAcademy (String twn, double tuit)

{

super.College(twn, tuit);

}

II. public LawAcademy (String twn, double tuit)

{

super(twn, tuit);

}

III. public LawAcademy (String twn, double tuit, String st)

{

super(st, tuit);

}

(A) II only

(B) I and II only

(C) I and III only

(D) II and III only

(E) I, II, and III

Here’s How to Crack It:

The final answer is (D) but let’s start with eliminations. This one has an item that’s easy to eliminate—I—and one you may be tempted to eliminate—III. Super.College is not a valid reference, since the super-dot operator is different from the super() method. Segment II is a typical way to handle the situation and so it is fine. Segment III brings in the extra parameter, which ends up being used as the first parameter (the String one) in the super call, rendering twn unused, but that doesn’t matter. As long as the super constructor is called correctly, we are good. So (D) is the correct answer.

Image

Let’s tackle another.

Image

2.Consider a class Recliner that extends a Chair class. Assuming both classes each have a no-parameter constructor, which of the following statements is NOT valid?

(A) Object a = new Recliner();

(B) Recliner b = new Chair();

(C) Chair c = new Chair();

(D) Chair d = new Recliner();

(E) All of the above choices are valid.

Here’s How to Crack It

This question enforces the idea of creating references using hierarchies. According to the question, a Recliner is a Chair, not the other way around. Likewise, any Java object is an Object. It is valid to declare a variable’s type to be more generic than its referenced object, but not the other way around. Choice (B) is the only choice that breaks this rule: declaring a variable requires a Chair to be a Recliner, not the other way around (the reverse of what the question dictates). The correct answer is (B).

Image

Image

3.Consider the following two classes:

public class A

{

public int method1(int x)

{

return 2;

}

}

public class B extends A

{/* code not shown */}

Which of the following could be the signature of a method in class B that correctly overloads method1 in class A?

(A) public int method1(String x)

(B) public int method1(int y)

(C) private int method1(int x)

(D) public int method2(String x)

(E) public int method2(int y)

Here’s How to Crack It

Overloading a method means creating a new method with the same name, visibility, and return type, regardless of whether the method lies in the parent class or the child class. The parameter must be different, however, or the new method overrides the original method. Choice (B) will override method1, (C), (D), and (E) will create all new methods that are independent of method1, and (C) does not have the same visibility as method1. The answer is (A).

Image

Now, suppose there is functionality in a superclass that is inherited, but should be changed based on the level of inheritance. For example, all snacks can be eaten, so the programmer appropriately implements an eat() method in Snack. As a result, all subclasses will inherit this method and its functionality. But what if, say, an OnionChip must be eaten differently than the other snacks? Perhaps after a few chips, “eaters” have to wipe their hands before continuing to eat. The desired eat() method—possessing the identical name, parameters, and return type—would be implemented in OnionChip, and all objects of that class (and only that class, in this example) would use this new eat() method. The superclass’s eat() method has been overridden by this new version. The workaround for this situation would be to use the super keyword: super.eat().

Though no longer part of the official Course Description, it’s pretty cool to know that another level of overriding involves method abstraction. Suppose that the programmer wants to force all subclasses to have the eat() method, but decides that an eat() method’s code in Snack is inappropriate; for example, all Snack objects can be eaten but the WAY they are eaten depends so much on the type of snack that superclass code for the method seems inappropriate. (You would not eat a chip the same way you would eat a cookie, would you? Go with it.) The eat() method can be declared abstract; in this design, the Snack class has now mandated every subclass to either (1) override the abstract method with code or (2) declare the method abstract once again, forcing its subclasses to override the abstract method.

Image

4.Consider the following two classes:

public class Parent

{

public void writeMe(String s)

{

System.out.println(“Object”);

}

}

public class Child extends Parent

{

public void writeMe(String s)

{

System.out.println(“Object”);

}

}

Which of the following best describes the writeMe method of the Child class?

(A) An inherited method

(B) An overridden method

(C) An overloaded method

(D) An interface method

(E) An abstract method

Image

Here’s How to Crack It

Since the writeMe method in Child has the same name, return type, and parameter types, it is overriding writeMe in Parent. The answer is (B).

POLYMORPHISM

Polymorphism is a technique that, in a way, breaks all of the rules we think would happen in inheritance—and yet, it conforms to them at the same time.

Using our Snack example from above, including the overridden method in OnionChip, suppose several objects from various levels in the hierarchy reside in an untyped ArrayList. The programmer would like to, using a simple loop, simulate the user “eating” the chips in the list, regardless of their type. The loop will iterate through the list and automatically invoke the appropriate eat() method, including the overridden method for OnionChip objects, as desired. This is an example of polymorphism.

The word polymorphism, which means “many forms,” can also apply to programs in a more profound manner. This process, directly or indirectly, involves virtually every technique we have learned in this book.

Suppose an interface called Eatable is implemented by all of the classes in the Snack hierarchy. Every class has either overridden the abstract methods from the interface, as normally required, or passed the abstraction to a subclass.

Have you ever seen those snack bags that have multiple forms of snacks (for example, potato chips AND pretzels AND nacho chips…) in them? This example is similar; if you instantiated the “bag” as either a typed ArrayList or an array, you could fill the structure with instances of all of these classes by declaring the type as Eatable. Once the eat() method is invoked on all of the components of the list structure using a loop, each object will automatically invoke its corresponding eat() method! Pretty awesome.

Use the information below to answer Questions 5 and 6.

Consider the following declaration for a class that will be used to represent a rectangle:

public class Rectangle

{

private double width;

private double height;

public Rectangle()

{

width = 0;

height = 0;

}

public Rectangle(double w, double h)

{

width = w;

height = h;

}

// postcondition: returns the height

public double getHeight()

{

return height;

}

// postcondition: returns the width

public double getWidth()

{

return width;

}

}

The following incomplete class declaration is intended to extend the above class so the rectangles can be filled with a color when displayed:

public class FilledRectangle extends Rectangle

{

private String color;

// constructors go here

public String getColor()

{

return color;

}

}

Consider the following proposed constructors for this class:

I. public FilledRectangle()

{

color = “red”;

}

II. public FilledRectangle(double w, double h, String c)

{

super (w, h);

color = c;

}

III. public FilledRectangle(double w, double h, String c)

{

width = w;

height = h;

color = c;

}

5.Which of these constructors would be legal for the FilledRectangle class?

(A) I only

(B) II only

(C) III only

(D) I and II

(E) I and III

Here’s How to Crack It

This is an interesting one. II follows all of the rules nicely, invoking the super constructor and initializing the data field in its class, so II is good; eliminate (A), (C), and (E). Note that we do not have to check III now. For I, remember that the superclass’s default constructor (the one with no parameters) will be invoked automatically if it is not called. Therefore, I is fine, so eliminate (B). Only one answer choice remains, so there is no need to continue. However, to see why III does not work, note that the variables width and height are private variables in Rectangle and, therefore, not inherited by FilledRectangle. Therefore, this will not compile. The answer is (D).

Image

Image

6.Based on the class declarations for Rectangle and FilledRectangle given above, which of the following code segments would be legal in a client class? Assume that the constructor that takes no arguments has been implemented for FilledRectangle.

I. FilledRectangle r1 = new Rectangle();

double height = r1.getHeight();

II. Rectangle r2 = new FilledRectangle();

double height = r2.getHeight();

III. Rectangle r3 = new FilledRectangle();

r3.getColor();

Which of the code segments above are legal?

(A) None

(B) II only

(C) III only

(D) I and II

(E) II and III

Here’s How to Crack It

Since II appears in the most answer choices, let’s check that option first. A FilledRectangle may be declared as a Rectangle because it is a subclass of Rectangle, and a FilledRectangle inherits getHeight from Rectangle as well, so II is legal: eliminate (A) and (C). A Rectangle cannot be declared as a FilledRectangle for the same reason, so I is illegal and (D) can be eliminated. As for III, a Rectangle object can invoke methods only from Rectangle (regardless of r3’s identity as a FilledRectangle), so the second line is illegal. The answer is (B).

Image

Image

7.Consider the following class:

public class Cat

{

private String name;

private int age;

public Cat(String name, int age)

{

this.name = name;

this.age = age;

}

public String toString()

{

return (name + “: ” + age);

}

}

Suppose another class were to include the following code segment:

Cat c = new Cat(“Billi”, 5);

System.out.println(c);

Which of the following will be the output of the code segment?

(A) c

(B) 5

(C) Billi

(D) Billi: 5

(E) There would be no output.

Here’s How to Crack It

The first line of the code segment creates a Cat object with name initialized as Billi and age initialized as 5. The second line prints the Cat object c. When an object is used as the parameter to a print, the toString method of the Object class is called. In this case, however, that method is overridden by the toString method of the Cat class. This method returns the name of the Cat, concatenated with “: ”, concatenated with the age of the Cat. Therefore, it prints Billi: 5, which is (D).

Image

KEY TERMS

inheritance

inheritance hierarchy

superclass

subclasses

multiple inheritance

overridden

super keyword

method abstraction

polymorphism

CHAPTER 11 REVIEW DRILL

Answers to the review questions can be found in Chapter 13.

1.Consider the following two classes:

public class Parent

{

private String str;

public void write()

{

System.out.println(str);

}

public void write(String greeting)

{

System.out.println(greeting + str);

}

}

public class Child extends Parent

{

public void write(String g)

{

super.writeMe(g);

}

}

The following statements have been written in a client program and meant to be executed in succession. However, one of them cause a compiler error. Identify the line of code that causes the compiler error.

Parent p1 = new Parent(); // statement A

Child c1 = new Child(); // statement B

p1.write(); ;  // statement C

c1.write();  // statement D

c1.write(5);  // statement E

(A) statement A

(B) statement B

(C) statement C

(D) statement D

(E) statement E

2.How many classes can a given class extend?

(A) None

(B) 1

(C) 2

(D) 3

(E) As many as it needs to

3.Consider the following classes.

public class Employee

{

private String name;

private String department;

Employee ()

{

name = “”;

department = “”;

}

Employee (String iName)

{

name = iName;

department = “”;

}

Employee (String iName, String iDepartment)

{

name = iName;

department = iDepartment;

}

public String toString()

{

return name + “ ” + department;

}

}

public class Manager extends Employee

{

private double salary;

Manager()

{

salary = 0;

}

Manager(double iSalary)

{

salary = iSalary;

}

Manager(String iName, String iDepartment, double iSalary)

{

super(iName, iDepartment);

salary = iSalary;

}

public void setSalary(double iSalary)

{

salary = iSalary;

}

public String toString()

{

String str = super.toString();

return str += “ ” + salary;

}

}

Which of the following code combinations will NOT cause a compilation error?

(A) Employee emp1 = new Employee();

(B) Employee emp2 = new Employee(“Jones”, “Sales”);

(C) Employee emp3 = new Manager(“Jones”, “Sales”, 50000.0);

(D) Employee emp4 = new Manager(50000.0);

(E) Manager emp5 = new Employee(“Jones”, “Sales”);

For questions 4—5, consider the following classes:

public class Bread

{

private String name;

Bread()

{

name = “bread”;

System.out.println(“Freshly baked bread smells good”);

}

public String toString()

{

return name;

}

}

public class Pastry extends Bread

{

Pastry(){

System.out.println(“Baking pastry is an art”);

}

}

public class Croissant extends Pastry

{

Croissant()

{

System.out.println(“Croissants taste buttery”);

}

}

4.If a client program executes the following statement:

Croissant c = new Croissant();

Which of the following represents the resulting output?

(A) Freshly baked bread smells good

(B) Croissants taste buttery

(C) Croissants taste buttery

Freshly baked bread smells good

(D) Freshly baked bread smells good

Baking pastry is an art Croissants taste buttery

(E) Croissants taste buttery

Baking pastry is an art

Freshly baked bread smells good

5.If a client program executes the following statements:

Croissant c = new Croissant();

System.out.println(c);

Which of the following is true about the last line of code?

(A) The following will printed:

bread

(B) The following will printed:

croissant

(C) An error will occur because name can be referenced only in Bread class.

(D) An error will occur because there is no toString() method in the Croissant class.

(E) None of the above are true.

6.An apartment rental company has asked you to write a program to store information about the apartments that it has available for rent. For each apartment, the company wants to keep track of the following information: number of rooms, whether or not the apartment has a dishwasher, and whether or not pets are allowed. Which of the following is the best design?

(A) Use four unrelated classes: Apartment, Rooms, Dishwasher, and Pets.

(B) Use one class, Apartment, which has three subclasses: Room, Dishwasher, and Pet.

(C) Use one class, Apartment, which has three data fields: int rooms, boolean hasDishwasher, boolean allowsPets.

(D) Use three classes—Pets, Rooms, and Dishwasher—each with a subclass Apartment.

(E) Use four classes: Apartment, Pets, Dishwasher, and Rooms. The class Apartment contains instances of the other classes as attributes.

7.Consider the following class declarations:

public class Vehicle

{

private int maxPassengers;

public Vehicle()

{

maxPassengers = 1;

}

public Vehicle(int x)

{

maxPassengers = x;

}

public int maxPassengers()

{

return maxPassengers;

}

}

public class Motorcycle extends Vehicle

{

public Motorcycle()

{

super(2);

}

}

Which of the following code segments will NOT cause a compilation error?

(A) Motorcycle m1 = new Motorcycle(3);

(B) Vehicle v1 = new Motorcycle(4);

(C) Motorcycle m2 = new Vehicle();

(D) Vehicle v2 = new Motorcycle();

(E) Vehicle v3 = new Vehicle();

int max = v3.maxPassengers;

Summary

o A subclass inherits from a superclass. A superclass is the most general form of an object. Subclasses are more specific types of the superclass.

o A superclass can have more than one subclass, but a subclass can have only one superclass.

o Subclasses inherit methods and variables from superclasses but can override these methods and variables.

o The reserved word super can be used to call an overridden method or variable.

o An object of a subclass is also an object of its superclass.