Before upcasting became a common practice, developers faced several challenges:
Let's explore these concepts using a shape hierarchy:
// Base class
class Shape {
private String color;
public Shape(String color) {
this.color = color;
}
public double getArea() {
return 0.0; // Default implementation
}
public String getColor() {
return color;
}
public void display() {
System.out.println("This is a shape with color: " + color);
}
}
// Circle class
class Circle extends Shape {
private double radius;
public Circle(String color, double radius) {
super(color);
this.radius = radius;
}
@Override
public double getArea() {
return Math.PI * radius * radius;
}
public double getRadius() {
return radius;
}
@Override
public void display() {
System.out.println("This is a circle with radius: " + radius);
}
}
// Rectangle class
class Rectangle extends Shape {
private double width;
private double height;
public Rectangle(String color, double width, double height) {
super(color);
this.width = width;
this.height = height;
}
@Override
public double getArea() {
return width * height;
}
public double getWidth() {
return width;
}
public double getHeight() {
return height;
}
@Override
public void display() {
System.out.println("This is a rectangle with width: " + width + " and height: " + height);
}
}
// Square class - a special case of rectangle
class Square extends Rectangle {
public Square(String color, double side) {
super(color, side, side);
}
public double getSide() {
return getWidth(); // Width and height are the same
}
@Override
public void display() {
System.out.println("This is a square with side: " + getSide());
}
}
Upcasting occurs when a derived class reference is converted to a base class reference. It happens implicitly and is always safe.
// Create objects of each type
Circle circle = new Circle("Red", 5.0);
Rectangle rectangle = new Rectangle("Blue", 4.0, 6.0);
Square square = new Square("Green", 4.0);
// Upcasting examples
Shape shapeFromCircle = circle; // Circle → Shape
Shape shapeFromRectangle = rectangle; // Rectangle → Shape
Shape shapeFromSquare = square; // Square → Shape (two-level upcast)
Rectangle rectangleFromSquare = square; // Square → Rectangle (intermediate upcast)
Polymorphic Collections: Store different but related objects together
Shape[] shapes = new Shape[3];
shapes[0] = new Circle("Red", 5.0); // Upcasting
shapes[1] = new Rectangle("Blue", 4, 6); // Upcasting
shapes[2] = new Square("Green", 4.0); // Upcasting
Polymorphic Method Calls: Overridden methods from derived classes are called
// All call their respective overridden methods
shapeFromCircle.display(); // Calls Circle's display()
shapeFromRectangle.display(); // Calls Rectangle's display()
shapeFromSquare.display(); // Calls Square's display()
Generic Methods: Write code that works with any shape
public void printArea(Shape shape) {
System.out.println("Area: " + shape.getArea());
}