In object-oriented programming, inheritance hierarchies allow classes to share common behaviors and attributes through a parent-child relationship, where a superclass passes down functionality to subclasses. Creating references using inheritance hierarchies is a powerful concept that enables polymorphism, allowing superclass references to point to subclass objects. This promotes code reusability, modularity, and flexibility. By understanding how to create and manipulate references in such hierarchies, students can effectively design systems that are extensible and maintainable, a key skill for success in AP Computer Science A.
Learning Objectives
In learning about “Creating References Using Inheritance Hierarchies” for AP Computer Science A, you should focus on understanding how superclass references can point to subclass objects, enabling polymorphism. Mastering method overriding, safely casting objects between superclass and subclass types, and applying the is-a relationship are key concepts. You should also learn to identify the advantages of using inheritance for code reusability and organization, as well as how polymorphism allows dynamic method binding to achieve flexible and maintainable code structures.
Creating References Using Inheritance Hierarchies for AP Computer Science A
Inheritance hierarchies are a powerful feature in object-oriented programming that allow classes to inherit properties and methods from other classes, promoting code reusability and organization. Understanding how to create references using inheritance is crucial for both conceptual clarity and practical applications.
Understanding the Hierarchy
In an inheritance hierarchy, the superclass (or parent class) is the more general class that defines common attributes and behaviors. The subclass (or child class) inherits from the superclass and can add more specific behaviors or override inherited ones. Inheritance is a key concept in Object-Oriented Programming (OOP), allowing classes to inherit properties and behaviors (attributes and methods) from a parent or superclass. An inheritance hierarchy is essentially the structure formed when multiple classes derive from a common superclass, forming a tree-like structure.
Superclass (Parent Class): The class that passes down attributes and methods.
Subclass (Child Class): The class that inherits from the superclass.
For example:
class Animal {
public void makeSound() {
System.out.println("Some sound");
}
}
class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("Bark");
}
}
Here, Animal is the superclass, and Dog is the subclass.
Creating References
Superclass reference to subclass object: You can create a reference of the superclass type and assign it to a subclass object. This is legal because a subclass object is-a superclass object.
Animal myDog = new Dog(); // Superclass reference to a subclass object
myDog.makeSound(); // Output: Bark (due to method overriding)
This concept allows you to treat subclass objects as instances of their superclass, which is useful for polymorphism.
Polymorphism :
Polymorphism allows objects of different classes related by inheritance to be treated as objects of the superclass. This way, a single reference type can point to objects of various subclasses.
For example:
Animal myAnimal = new Dog();
Animal anotherAnimal = new Cat();
In both cases, the references are of type Animal, but the actual object is either Dog or Cat. The overridden makeSound() method in the respective subclasses will be called, demonstrating polymorphism.
Casting
In AP Computer Science A, casting is essential for converting between data types, especially in object-oriented programming. It involves upcasting (from subclass to superclass) and downcasting (from superclass to subclass). Downcasting requires caution and should be checked using the instanceof operator to avoid ClassCastException. Understanding casting ensures effective use of inheritance and polymorphism, as it allows managing different types within an inheritance hierarchy while maintaining object behavior.
Animal myAnimal = new Dog();
if (myAnimal instanceof Dog) {
Dog myDog = (Dog) myAnimal; // Downcasting to Dog
myDog.bark(); // Now you can call Dog-specific methods
}
This ensures you are safely accessing subclass methods without risking errors.
Advantages of Inheritance Hierarchies
- Code Reusability: Superclass methods can be reused by subclasses, reducing code duplication.
- Modularity: Hierarchies allow better organization of code, where shared behavior is placed in a common superclass, and specific behavior in subclasses.
- Extensibility: New subclasses can be added without altering existing code, enabling easy expansion.
- Maintainability: Inheritance hierarchies help in maintaining and updating code efficiently. Changes made to the superclass automatically propagate to all subclasses, ensuring consistency and reducing the need for redundant updates across classes.
- Abstraction: By defining common behavior and attributes in the superclass, you create an abstract representation of shared characteristics, which simplifies the understanding and usage of code. Subclasses can focus on more specific functionality, hiding complexity.
Examples
Example 1: Animal Hierarchy
class Animal {
void sound() {
System.out.println("Animal makes a sound");
}
}
class Dog extends Animal {
@Override
void sound() {
System.out.println("Dog barks");
}
}
class Cat extends Animal {
@Override
void sound() {
System.out.println("Cat meows");
}
}
public class Main {
public static void main(String[] args) {
Animal myAnimal = new Dog(); // Reference of Animal, instance of Dog
myAnimal.sound(); // Output: Dog barks
myAnimal = new Cat(); // Reference of Animal, instance of Cat
myAnimal.sound(); // Output: Cat meows
}
}
Example 2: Vehicle Hierarchy
class Vehicle {
void start() {
System.out.println("Vehicle is starting");
}
}
class Car extends Vehicle {
@Override
void start() {
System.out.println("Car is starting");
}
}
class Bike extends Vehicle {
@Override
void start() {
System.out.println("Bike is starting");
}
}
public class Main {
public static void main(String[] args) {
Vehicle myVehicle = new Car(); // Reference of Vehicle, instance of Car
myVehicle.start(); // Output: Car is starting
myVehicle = new Bike(); // Reference of Vehicle, instance of Bike
myVehicle.start(); // Output: Bike is starting
}
}
Example 3: Shape Hierarchy
abstract class Shape {
abstract void draw();
}
class Circle extends Shape {
@Override
void draw() {
System.out.println("Drawing Circle");
}
}
class Square extends Shape {
@Override
void draw() {
System.out.println("Drawing Square");
}
}
public class Main {
public static void main(String[] args) {
Shape myShape = new Circle(); // Reference of Shape, instance of Circle
myShape.draw(); // Output: Drawing Circle
myShape = new Square(); // Reference of Shape, instance of Square
myShape.draw(); // Output: Drawing Square
}
}
Example 4: Employee Hierarchy
class Employee {
void work() {
System.out.println("Employee is working");
}
}
class Manager extends Employee {
@Override
void work() {
System.out.println("Manager is managing");
}
}
class Developer extends Employee {
@Override
void work() {
System.out.println("Developer is coding");
}
}
public class Main {
public static void main(String[] args) {
Employee myEmployee = new Manager(); // Reference of Employee, instance of Manager
myEmployee.work(); // Output: Manager is managing
myEmployee = new Developer(); // Reference of Employee, instance of Developer
myEmployee.work(); // Output: Developer is coding
}
}
Example 5: Appliance Hierarchy
class Appliance {
void turnOn() {
System.out.println("Appliance is turned on");
}
}
class WashingMachine extends Appliance {
@Override
void turnOn() {
System.out.println("Washing Machine is turned on");
}
}
class Refrigerator extends Appliance {
@Override
void turnOn() {
System.out.println("Refrigerator is turned on");
}
}
public class Main {
public static void main(String[] args) {
Appliance myAppliance = new WashingMachine(); // Reference of Appliance, instance of WashingMachine
myAppliance.turnOn(); // Output: Washing Machine is turned on
myAppliance = new Refrigerator(); // Reference of Appliance, instance of Refrigerator
myAppliance.turnOn(); // Output: Refrigerator is turned on
}
}
Multiple Choice Questions
Question 1
Given the following code:
class Animal {
void makeSound() {
System.out.println("Animal sound");
}
}
class Dog extends Animal {
void makeSound() {
System.out.println("Bark");
}
}
public class Main {
public static void main(String[] args) {
Animal myAnimal = new Dog();
myAnimal.makeSound();
}
}
What will be the output of the above program?
A. Compile-time error
B. Animal sound
C. Bark
D. Runtime error
Answer: C. Bark
Explanation: Even though myAnimal is a reference of type Animal, it is pointing to a Dog object. Java uses dynamic method dispatch, where the method to be executed is determined at runtime based on the actual object type, not the reference type. Since myAnimal is referring to a Dog object, the Dog class’s overridden makeSound method will be executed, resulting in the output “Bark.”
Question 2
Which of the following statements is true about referencing in inheritance hierarchies?
A. A reference variable of a superclass can refer to an object of its subclass.
B. A reference variable of a subclass can refer to an object of its superclass.
C. A reference variable can only call methods of the class type it is declared as.
D. Method overriding is not allowed in inheritance hierarchies.
Answer: A. A reference variable of a superclass can refer to an object of its subclass.
Explanation: In Java, a reference variable of a superclass type can point to objects of its subclass. This is a fundamental principle of inheritance, allowing polymorphism. For example, an Animal reference can refer to a Dog object. The correct method (overridden or inherited) will be invoked based on the actual type of the object during runtime (dynamic dispatch). Option B is incorrect because subclass references cannot point to superclass objects without casting. Option C is incorrect because overridden methods in the subclass can be called based on the actual object. Option D is incorrect because method overriding is a key concept in inheritance.
Question 3
Consider the following code:
class Vehicle {
void start() {
System.out.println("Vehicle starts");
}
}
class Car extends Vehicle {
void start() {
System.out.println("Car starts");
}
}
public class Main {
public static void main(String[] args) {
Vehicle myVehicle = new Car();
Car myCar = (Car) myVehicle;
myCar.start();
}
}
What is the output of the code?
A. Vehicle starts
B. Car starts
C. Compile-time error
D. Runtime error
Answer: B. Car starts
Explanation: In this example, myVehicle is a reference of type Vehicle, but it points to a Car object. The reference is then cast back to Car, which allows it to call the start method from the Car class. As a result, the method in the Car class is invoked, producing the output “Car starts.” Option A is incorrect because method overriding ensures that the subclass’s method is executed. There is no compile-time or runtime error, so options C and D are incorrect.