Child classes of a class can redefine the behavior defined in the parent class and add some additional fields for their state. Polymorphism is an object-oriented programming feature that allows an object to have more than one form. As an object oriented programming language, Java fully supports polymorphism. In Java, a reference declared for a parent object may refer to an instance of the parent object or it can refer to an instance of one of its subclasses. This way, the reference may have multiple forms. The parent object may be a concrete class, an abstract class or an interface. A class whose instances can have multiple forms is referred to as a polymorphic class.
Generally speaking, if Class B Is-A Class A, then a reference of type A may point to an instance of Class B as well.
As an example, let’s define two simple classes.
public class A { }
public class B extends A { }
In this case both of the following statements are legal.
A a1 = new A();
A a2 = new B();
The same is true for interfaces and implementing classes. As an example, let’s define a simple interface and a simple class that implements it.
public interface A { }
public class B implements A { }
The following statement is perfectly legal in Java as the parent reference points to an instance of one its children.
A a = new B();
Polymorphism defines its own rules to determine which methods and instance variables of a parent can be accessed using polymorphic ways by its children. Let’s define a Vehicle class and see how polymorphic access works.
public class Vehicle {
private String manufacturer;
private String model;
private int year;
public Vehicle(String manufacturer, String model, int year) {
this.manufacturer = manufacturer;
this.model = model;
this.year = year;
}
public void printVehicleInfo() {
System.out.println(“This vehicle is a “ + year + “ “ + manufacturer + “ “ + model);
}
}
Now let’s define Car and Motorcycle classes that extend Vehicle. Because Car and Motorcycle are subclasses of Vehicle, they have access to public and protected methods of Vehicle class.
public class Car extends Vehicle {
private String fuelType;
public Car(String manufacturer, String model, int year, String type) {
super(manufacturer, model, year);
this.type = type;
}
public void printVehicleInfo() {
super.printVehicleInfo();
System.out.println(“This car’s fuel type is “ + fuelType);
}
}
public class Motorcycle extends Vehicle {
private double weight;
public Motorcycle( String manufacturer, String model, int year, double engineCapacity) {
super(manufacturer, model, year);
this.weight = weight;
}
public void printVehicleInfo() {
super.printVehicleInfo();
System.out.println(“This motorcycle’s engine capacity is “ + engineCapacity + “ cc”);
}
}
Car class defines a new fuelType field and overrides the printVehicleInfo method. Motorcycle class defines a new weight field and also overrides printVehicleInfo method as well. None of the classes inherit manufacturer, year and model fields of Vehicle class as these fields are declared private and can only be accessed using Vehicle’s constructor. Both Car and Motorcycle call Vehicle’s constructor using super in the first line of their constructors and set the fields declared in Vehicle in the constructor method.
Let’s write a new class to see how polymorphic access works in Java.
public class MyClass {
public static void main(String [] args) {
Vehicle vehicle = new Vehicle(“Honda”, “Civic”, 2004);
vehicle.printVehicleInfo();
vehicle = new Car(“Toyota”, “Corolla”, 2011, “gasoline”);
vehicle.printVehicleInfo();
vehicle = new Motorcycle(“Harley-Davidson”, “Street”, 2009, 450);
vehicle.printVehicleInfo();
}
}
In the main method, we first declare a reference variable of type Vehicle and assign it to a Vehicle instance, a 2004 Honda Civic. Following that, the same reference variable is assigned to a Car instance, and then to a Motorcycle instance. The reference variable vehicle first takes the form of a Vehicle object, then a Car object and then a Motorcycle object.
The output from running MyClass is
This vehicle is a 2004 Honda Civic
This vehicle is a 2011 Toyota Corolla.
This car’s fuel type is gasoline.
This vehicle is a 2009 Harley-Davidson Street.
This motorcycle’s engine capacity is 450 cc
From the output we see that, whenever printVehicleInfo method is called through the vehicle instance, the version of the method that belongs to the class that the vehicle instance is an instance of gets invoked. When the vehicle instance takes the form of a Vehicle object, it calls Vehicle’s printVehicleInfo method. When it points to a Car instance, it calls Car’s printVehicleInfo method. When it takes the form of a Motorcycle object, it calls the method version overridden in the Motorcycle class. Java distinguishes between the forms when an overridden method is invoked and calls the proper version of the method depending on which instance the reference variable points to. This concept is called virtual method invocation. In object-oriented programming, a virtual function is a function that can be redefined in child classes. By this definition, every non-final and non-private instance method is a virtual method in Java.
Object Type casting
In Java, a reference variable of a parent class type can be cast to a child class type, allowing the reference variable another way to have more than one form. This is one of the principal uses of polymorphism in Java.
We can change MyClass to demonstrate type casting as follows.
public class MyClass {
public static void main(String [] args) {
Vehicle vehicle = new Car(“Toyota”, “Corolla”, 2011, “gasoline”, 4);
vehicle.printVehicleInfo();
vehicle = (Vehicle) vehicle;
vehicle.printVehicleInfo();
}
}
The output would be
This vehicle is a 2011 Toyota Corolla.
This car’s fuel type is gasoline.
This vehicle is a 2011 Toyota Corolla.
In this version of MyClass, in the main method, we first declare a Vehicle instance but initialize it as a Car object. The first call to printVehicleInfo invokes Car’s printVehicleInfo method. Then the vehicle reference is cast to a Vehicle object. When printVehicleInfo() method is called through the same reference object, this time the version in the Vehicle class gets invoked.
The rule to object type-casting in Java is that, child classes can be type-cast to only parent objects because they have a Is-A relationship to their parents. When a subclass reference is type-cast to a parent class reference, it no longer has access to the additional state and behavior defined in the subclass.
A reference object variable has access to only the methods and fields defined in its class, even if it points to an instance of one of its subclasses. To see this in an example, let’s add a new instance variable and a new method to Car class.
Looking at the main method, the reference variable vehicle is a Vehicle object, even though it points to a Car instance. Therefore, it cannot invoke the printDoorsInfo() method defined in the Car class. The only way to invoke this method is to define a reference variable of type Car. A car can have 2 doors or 4 doors. We can add a new field to keep this information and print this information with a new method.
public class Car extends Vehicle {
private String fuelType;
private int numberOfDoors;
public Car(String manufacturer, String model, int year, String type, int numberOfDoors) {
super(manufacturer, model, year);
this.type = type;
}
public void printVehicleInfo() {
super.printVehicleInfo();
System.out.println(“This car’s fuel type is “ + fuelType);
}
public void printDoorsInfo() {
System.out.println(“This car has “ + numberOfDoors + “ doors”);
}
public static void main(String [] args) {
Car car = new Car(“Ford”, “Escort”, 2014, “gasoline”, 4);
car.printDoorsInfo();
}
}
The car variable defined in the main method is a reference to a Car object. Therefore, the output from this class would be
This car has 4 doors.
In the main method, if, instead, we cast the car object to Vehicle, it no longer points to a Car instance. With this cast operation, the additional fields and methods defined in the child class Car were taken out of the reference object and therefore car variable has no longer access to numberOfDoors field and printDoorsInfo method.
car = (Vehicle) car;
//The car reference variable has access to only Vehicle’s methods and fields now.
car.printVehicleInfo();
//This is no longer allowed because the car reference variable now points to only the fields and methods inherited from Vehicle.
//car.printDoorsInfo()