Polymorphism is a fundamental concept in object-oriented programming that allows objects of different subclasses to be treated as instances of a common superclass. In Java, associating subclass objects with superclass references enables flexible and dynamic behavior, where overridden methods in subclasses are called based on the actual object type during runtime. This concept provides the ability to create more generic, reusable, and maintainable code by allowing a superclass reference to point to various subclass objects, making polymorphism a key technique for efficient program design in AP Computer Science A.
Learning Objectives
In studying “Associating Subclass Objects With Superclasses to Create Polymorphism” for AP Computer Science A, focus on understanding how subclass objects can be referenced using superclass types to achieve polymorphism. You should learn how method overriding allows different behaviors for the same method name, enabling runtime decision-making. Grasp how dynamic method dispatch works and how interfaces contribute to polymorphism. Lastly, you should be able to write flexible and maintainable code by applying these concepts in object-oriented design and understand the benefits of reusability and extensibility.
Associating Subclass Objects With Superclasses to Create Polymorphism
Polymorphism is one of the fundamental principles of object-oriented programming (OOP), and it allows objects of different classes to be treated as objects of a common superclass. In Java, polymorphism is achieved when a superclass reference variable can refer to an object of any subclass. This capability promotes flexibility and reusability in code.
Key Concepts of Polymorphism
Superclass and Subclass Relationship
A superclass defines general properties and behaviors (attributes and methods), while a subclass specializes or extends the superclass by adding or modifying these behaviors. For example, if Animal is the superclass, subclasses like Dog and Cat inherit from Animal and may override or define additional behaviors.
1. Inheritance in Superclass-Subclass Relationship : Inheritance is a fundamental concept in object-oriented programming (OOP) where a subclass derives or inherits attributes and methods from a superclass. This allows the subclass to reuse and extend the functionalities of the superclass without needing to rewrite existing code.
- Superclass (also called the parent class or base class) defines the common characteristics and behaviors shared by all its subclasses.
- Subclass (also called the child class or derived class) inherits all the attributes and methods of the superclass. However, the subclass can add its own methods and attributes or override existing ones from the superclass to provide more specialized behavior.
2. “is-a” Relationship : The relationship between a superclass and its subclass is often described as an “is-a” relationship. This means that the subclass is a specialized version of the superclass.
- A Dog is a specialized Animal.
- A Cat is also a specialized Animal.
3. Inheritance of Fields and Methods : Subclasses inherit both fields (attributes) and methods (behaviors) from the superclass:
- Fields: The subclass inherits the instance variables from the superclass, which can be accessed directly or through getter/setter methods (if they are public or protected).
- Methods: Subclasses inherit the behaviors (methods) of the superclass, but they can override these methods to implement specialized behavior.
Polymorphic Behavior
A superclass reference variable can hold an object of any subclass. This allows methods to be invoked on objects without needing to know their specific subclass type at compile time. This is how polymorphism enables flexibility, as the method invoked depends on the actual object type (in this case, Dog), rather than the reference type (Animal). This allows for dynamic method dispatch, where the method that gets executed is determined at runtime based on the object’s class.
Example:
class Animal {
public void makeSound() {
System.out.println("Animal makes a sound");
}
}
class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("Dog barks");
}
}
class Main {
public static void main(String[] args) {
Animal myAnimal = new Dog(); // Animal reference holding a Dog object
myAnimal.makeSound(); // Calls Dog's makeSound() method
}
}
Method Overriding
Subclasses can override methods of the superclass. This is essential for polymorphism because when a superclass reference points to a subclass object, the overridden method in the subclass is called. Java uses dynamic (runtime) method dispatch to determine the method to invoke based on the actual object’s type, not the reference variable type.
- Annotation @Override: When overriding a method from a superclass, it is good practice to use the @Override annotation. This helps the compiler check if you are actually overriding a method and not mistakenly creating a new method due to signature differences.
- Same Method Signature: The overridden method in the subclass must have the same method signature as the method in the superclass, meaning it must have the same name, return type, and parameter list. If the method signature is different, it becomes method overloading, not overriding.
- Access Modifiers in Overriding: The access modifier of the overriding method cannot be more restrictive than the method in the superclass. For example, if the superclass method is public, the overriding method must also be public or can remain public. You cannot make it protected or private.
Upcasting and Downcasting
Upcasting: Assigning a subclass object to a superclass reference. This is done implicitly because every subclass is a type of its superclass.
- Definition: Upcasting refers to assigning a subclass object to a superclass reference. Since every subclass is a type of its superclass, upcasting is implicit and does not require an explicit cast.
- Purpose: Upcasting is often used when you want to handle multiple types of objects in a generic way. For example, you can group different subclasses under a single superclass type and manage them uniformly. However, upcasting can limit access to only the methods and properties defined in the superclass unless overridden methods in the subclass are called at runtime (polymorphism).
- Key Point: Since upcasting is implicit, it doesn’t require any special syntax. You can upcast any subclass object to a superclass reference automatically.
Animal animal = new Dog(); // Implicit upcasting
Downcasting: Casting a superclass reference back to a subclass type. This is explicit and requires a cast to avoid runtime exceptions.
- Definition: Downcasting is the process of casting a superclass reference back to a subclass type. Since downcasting can potentially lead to runtime errors (if the object being cast is not actually an instance of the subclass), an explicit cast is required.
- Purpose: Downcasting is useful when you need to access subclass-specific methods or properties that are not available in the superclass. It allows you to treat an object as an instance of its subclass again after it has been upcast.
- Key Point: Downcasting must be done explicitly using a cast operator ((Subclass)), and you should always ensure that the object being downcasted is an instance of the target subclass to avoid runtime exceptions (ClassCastException).
Dog dog = (Dog) animal; // Explicit downcasting
Polymorphism in Arrays and Collections
olymorphism is a powerful feature in Java that extends beyond individual objects to arrays and collections. It allows you to store objects of different subclasses in a single array or collection, as long as they share a common superclass or implement the same interface. This approach promotes code flexibility, reusability, and scalability, making it easier to manage groups of related objects.
Polymorphism in Arrays:
In Java, arrays are typically used to hold objects of a single type, but through polymorphism, you can create an array of a superclass type and store objects of its subclasses. This allows for efficient grouping of different objects under one structure, while leveraging common functionality across them.
Polymorphism in Collections (e.g., ArrayList):
Java’s Collection framework (e.g., ArrayList, LinkedList) also supports polymorphism, allowing you to store objects of different types that share a common superclass or implement a common interface. Using polymorphism with collections can make your code more flexible, especially when dealing with large or dynamically sized datasets.
Animal[] animals = { new Dog(), new Cat(), new Bird() };
for (Animal a : animals) {
a.makeSound(); // Calls makeSound of each subclass dynamically
}
Examples
Example 1: Shape and its Subclasses:
Consider a superclass Shape with subclasses like Circle, Rectangle, and Triangle. All these shapes share common properties like area calculation. With polymorphism, you can create an array of Shape objects that hold instances of Circle, Rectangle, or Triangle. The appropriate area() method is called at runtime depending on the actual object type.
Shape s = new Circle();
s.area(); // Calls the Circle's version of the area method
Example 2: Animal and its Subclasses
A classic example is an Animal superclass with subclasses like Dog, Cat, and Bird. These subclasses may override a makeSound() method. When an Animal reference points to any of these subclass objects, the correct version of makeSound() is executed at runtime, based on the subclass.
Animal a = new Dog();
a.makeSound(); // Executes Dog's version of makeSound
Example 3: Employee and its Subclasses
In a payroll system, Employee is the superclass with subclasses such as SalariedEmployee, HourlyEmployee, and CommissionEmployee. The calculateSalary() method is overridden in each subclass to account for the specific salary structure. A reference of type Employee can hold any of these subclass objects and invoke the correct calculateSalary() method.
Employee e = new SalariedEmployee();
e.calculateSalary(); // Calls the SalariedEmployee's method
Example 4: Vehicle and its Subclasses
In a transportation system, Vehicle can be a superclass with subclasses like Car, Truck, and Motorcycle. Each subclass might have a drive() method that is customized for the specific vehicle type. Using polymorphism, a reference of type Vehicle can point to any subclass, and the appropriate drive() method is called based on the actual type.
Vehicle v = new Truck();
v.drive(); // Calls the Truck's version of drive
Example 5: Payment and its Subclasses
A payment processing system can have a Payment superclass with subclasses such as CreditCardPayment, PaypalPayment, and BankTransferPayment. Each subclass would implement a processPayment() method differently. Using polymorphism, you can write code to process any type of payment without needing to know the specific payment type in advance.
Payment p = new PaypalPayment();
p.processPayment(); // Executes PaypalPayment's processPayment method
Multiple Choice Questions
Question 1
Which of the following statements about polymorphism is TRUE?
A) A subclass reference can hold an object of its superclass.
B) A superclass reference can refer to an object of any subclass.
C) Polymorphism allows methods to be overloaded, not overridden.
D) The method to execute is determined at compile time in polymorphism.
Answer: B) A superclass reference can refer to an object of any subclass.
Explanation:
- B is correct because the essence of polymorphism is that a superclass reference can hold a reference to any subclass object. This allows a method to be called on the reference without knowing which subclass object it is referring to at runtime.
- A is incorrect because a subclass reference cannot hold an object of its superclass. This would violate the “is-a” relationship required in polymorphism.
- C is incorrect because polymorphism relates to method overriding, where a subclass provides a specific implementation of a method defined in its superclass. Overloading refers to defining multiple methods with the same name but different signatures, which is unrelated to polymorphism.
- D is incorrect because in polymorphism, the actual method to execute is determined at runtime, not compile time. This dynamic method dispatch ensures the subclass method is invoked.
Question 2
Which of the following is an example of upcasting in Java?
A) Dog dog = new Animal();
B) Animal animal = (Animal) new Dog();
C) Dog dog = (Dog) animal;
D) Dog dog = new Dog();
Answer: B) Animal animal = (Animal) new Dog();
Explanation:
- B is correct because upcasting occurs when a subclass object (Dog) is assigned to a superclass reference (Animal). In this case, the Dog object is upcast to an Animal type. Upcasting is implicit and doesn’t require explicit casting in Java, but the code still represents the concept.
- A is incorrect because you cannot assign a superclass object (Animal) to a subclass reference (Dog). This would require downcasting and may result in a ClassCastException.
- C is incorrect because it represents downcasting, not upcasting. Downcasting occurs when you cast a superclass reference back to a subclass reference.
- D is incorrect because this is not an example of casting at all. It simply assigns a new Dog object to a Dog reference.
Question 3
What is the result of the following code?
class Animal {
public void sound() {
System.out.println("Animal sound");
}
}
class Dog extends Animal {
@Override
public void sound() {
System.out.println("Dog barking");
}
}
public class Main {
public static void main(String[] args) {
Animal myAnimal = new Dog();
myAnimal.sound();
}
}
A) Compilation error
B) Prints “Animal sound”
C) Prints “Dog barking”
D) Throws an exception at runtime
Answer: C) Prints “Dog barking”
Explanation:
- C is correct because polymorphism ensures that when the sound() method is called on a superclass reference (Animal myAnimal), the overridden method in the subclass (Dog) is executed. Even though the reference is of type Animal, the actual object is of type Dog, and therefore the Dog class’s sound() method is invoked.
- A is incorrect because the code compiles successfully.
- B is incorrect because it would print “Animal sound” only if the method wasn’t overridden in the subclass.
- D is incorrect because there is no runtime exception in this case. The code runs correctly, demonstrating polymorphism.