Polymorphism is a core concept in object-oriented programming (OOP) that allows objects to be treated as instances of their parent class rather than their actual class. This enables a single interface to represent different underlying forms (data types). In Groovy, polymorphism is achieved through method overriding and interface implementation.
Key Concepts
- Method Overriding: Subclasses provide a specific implementation of a method that is already defined in its superclass.
- Interface Implementation: Different classes can implement the same interface, allowing them to be used interchangeably.
Method Overriding
Method overriding allows a subclass to provide a specific implementation of a method that is already defined in its superclass. This is useful when the subclass needs to modify or extend the behavior of the superclass method.
Example
class Animal {
void makeSound() {
println "Some generic animal sound"
}
}
class Dog extends Animal {
@Override
void makeSound() {
println "Bark"
}
}
class Cat extends Animal {
@Override
void makeSound() {
println "Meow"
}
}
Animal myDog = new Dog()
Animal myCat = new Cat()
myDog.makeSound() // Output: Bark
myCat.makeSound() // Output: MeowExplanation
- Animal Class: The superclass with a method
makeSound(). - Dog and Cat Classes: Subclasses that override the
makeSound()method to provide specific implementations. - Polymorphic Behavior: The
myDogandmyCatvariables are of typeAnimal, but they call the overridden methods inDogandCatclasses, respectively.
Interface Implementation
Interfaces define methods that must be implemented by any class that implements the interface. This allows different classes to be used interchangeably if they implement the same interface.
Example
interface Shape {
double area()
}
class Circle implements Shape {
double radius
Circle(double radius) {
this.radius = radius
}
@Override
double area() {
return Math.PI * radius * radius
}
}
class Rectangle implements Shape {
double width
double height
Rectangle(double width, double height) {
this.width = width
this.height = height
}
@Override
double area() {
return width * height
}
}
Shape myCircle = new Circle(5)
Shape myRectangle = new Rectangle(4, 6)
println "Circle area: ${myCircle.area()}" // Output: Circle area: 78.53981633974483
println "Rectangle area: ${myRectangle.area()}" // Output: Rectangle area: 24.0Explanation
- Shape Interface: Defines a method
area(). - Circle and Rectangle Classes: Implement the
Shapeinterface and provide specific implementations of thearea()method. - Polymorphic Behavior: The
myCircleandmyRectanglevariables are of typeShape, but they call the implementedarea()methods inCircleandRectangleclasses, respectively.
Practical Exercise
Exercise
Create a superclass Vehicle with a method move(). Then, create two subclasses Car and Bicycle that override the move() method. Instantiate objects of Car and Bicycle and call the move() method on each.
Solution
class Vehicle {
void move() {
println "The vehicle is moving"
}
}
class Car extends Vehicle {
@Override
void move() {
println "The car is driving"
}
}
class Bicycle extends Vehicle {
@Override
void move() {
println "The bicycle is pedaling"
}
}
Vehicle myCar = new Car()
Vehicle myBicycle = new Bicycle()
myCar.move() // Output: The car is driving
myBicycle.move() // Output: The bicycle is pedalingExplanation
- Vehicle Class: The superclass with a method
move(). - Car and Bicycle Classes: Subclasses that override the
move()method to provide specific implementations. - Polymorphic Behavior: The
myCarandmyBicyclevariables are of typeVehicle, but they call the overridden methods inCarandBicycleclasses, respectively.
Common Mistakes and Tips
- Forgetting to Use
@OverrideAnnotation: Always use the@Overrideannotation when overriding methods to avoid mistakes and improve code readability. - Incorrect Method Signatures: Ensure that the method signature in the subclass matches the method signature in the superclass or interface.
- Not Leveraging Polymorphism: Use polymorphism to write more flexible and reusable code. For example, you can create a list of
Vehicleobjects that can contain bothCarandBicycleobjects.
Conclusion
Polymorphism is a powerful feature in Groovy that allows for flexible and reusable code. By understanding and utilizing method overriding and interface implementation, you can create more dynamic and maintainable applications. In the next section, we will explore encapsulation, another fundamental concept in object-oriented programming.
