Polymorphism is one of the core concepts in object-oriented programming (OOP) that allows objects to be treated as instances of their parent class. It enables a single interface to represent different underlying forms (data types). In Java, polymorphism is primarily achieved through method overriding and method overloading. This tutorial will focus on method overriding and how it leads to polymorphic behavior.
Inheritance is the foundation of polymorphism in Java. When a subclass overrides a method from its superclass, it can provide a specific implementation that differs from the superclass's method. This allows objects of different subclasses to be treated as objects of their common superclass, leading to more flexible and reusable code.
Polymorphism is crucial because it promotes code reusability and flexibility. It enables developers to write more generic and maintainable code by allowing methods to operate on objects of different classes through a single interface.
Method overriding occurs when a subclass provides a specific implementation for a method that is already defined in its superclass. The method signature (name, return type, and parameters) must be exactly the same in both the superclass and the subclass.
1class Animal {2void makeSound() {3System.out.println("Some generic animal sound");4};5}67class Dog extends Animal {8@Override9void makeSound() {10System.out.println("Bark");11}12}1314public class Main {15public static void main(String[] args) {16Animal myAnimal = new Animal();17Animal myDog = new Dog();1819myAnimal.makeSound(); // Output: Some generic animal sound20myDog.makeSound(); // Output: Bark21}22}
Some generic animal sound Bark
In this example, the Dog class overrides the makeSound method of the Animal class. When we call makeSound on an object of type Dog, it executes the overridden method in the Dog class.
Polymorphism allows us to use a superclass reference to refer to objects of its subclasses. This is particularly useful when dealing with collections of objects.
1class Animal {2void makeSound() {3System.out.println("Some generic animal sound");4}5}67class Dog extends Animal {8@Override9void makeSound() {10System.out.println("Bark");11}12}1314class Cat extends Animal {15@Override16void makeSound() {17System.out.println("Meow");18}19}2021public class Main {22public static void main(String[] args) {23Animal[] animals = {new Animal(), new Dog(), new Cat()};2425for (Animal animal : animals) {26animal.makeSound();27}28}29}
Some generic animal sound Bark Meow
In this example, we have an array of Animal objects that includes instances of Animal, Dog, and Cat. When we iterate over the array and call makeSound on each object, Java determines at runtime which method to execute based on the actual object type.
Let's create a practical example where we use polymorphism to manage different types of shapes.
1abstract class Shape {2abstract double area();3abstract double perimeter();45void displayInfo() {6System.out.println("Area: " + area());7System.out.println("Perimeter: " + perimeter());8}9}1011class Circle extends Shape {12private double radius;1314public Circle(double radius) {15this.radius = radius;16}1718@Override19double area() {20return Math.PI * radius * radius;21}2223@Override24double perimeter() {25return 2 * Math.PI * radius;26}27}2829class Rectangle extends Shape {30private double width;31private double height;3233public Rectangle(double width, double height) {34this.width = width;35this.height = height;36}3738@Override39double area() {40return width * height;41}4243@Override44double perimeter() {45return 2 * (width + height);46}47}4849public class Main {50public static void main(String[] args) {51Shape[] shapes = {new Circle(5), new Rectangle(4, 6)};5253for (Shape shape : shapes) {54shape.displayInfo();55System.out.println();56}57}58}
Area: 78.53981633974483 Perimeter: 31.41592653589793 Area: 24.0 Perimeter: 20.0
In this example, we have an abstract class Shape with two subclasses Circle and Rectangle. Each subclass provides its own implementation of the area and perimeter methods. The Main class demonstrates how polymorphism allows us to treat different shapes as Shape objects and call their methods.
In the next topic, we will explore Java Inner Classes, which allow you to define a class within another class. This can be useful for creating more organized and modular code. Stay tuned!