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: Meow
Explanation
- 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
myDog
andmyCat
variables are of typeAnimal
, but they call the overridden methods inDog
andCat
classes, 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.0
Explanation
- Shape Interface: Defines a method
area()
. - Circle and Rectangle Classes: Implement the
Shape
interface and provide specific implementations of thearea()
method. - Polymorphic Behavior: The
myCircle
andmyRectangle
variables are of typeShape
, but they call the implementedarea()
methods inCircle
andRectangle
classes, 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 pedaling
Explanation
- 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
myCar
andmyBicycle
variables are of typeVehicle
, but they call the overridden methods inCar
andBicycle
classes, respectively.
Common Mistakes and Tips
- Forgetting to Use
@Override
Annotation: Always use the@Override
annotation 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
Vehicle
objects that can contain bothCar
andBicycle
objects.
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.