image.png


Understanding Upcasting, Downcasting, and Slicing in Object-Oriented Programming

Problems Before Upcasting

Before upcasting became a common practice, developers faced several challenges:

  1. Code Duplication: Similar functionality had to be reimplemented across related classes
  2. Type-Specific Collections: Couldn't store different but related objects in the same collection
  3. Limited Polymorphism: Couldn't easily write code that worked with multiple related types
  4. Complex Method Designs: Methods needed separate versions for each related type

Shape Hierarchy Example

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

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)

Benefits of Upcasting:

  1. 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
    
    
  2. 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()
    
    
  3. Generic Methods: Write code that works with any shape

    public void printArea(Shape shape) {
        System.out.println("Area: " + shape.getArea());
    }
    
    

Slicing