Java Fundamentals

Constructors and Inheritance

Lecture 6

Object construction, the 'this' keyword, and inheritance mechanisms

Summary of the last lectures

We know how to :
  • Design and create classes
  • Define properties and methods for those classes
  • Control the flow of our program and repeat statements
We saw basic (default) constructors , and how to call it through a java code to create new instances
How to construct complex objects? How to take advantages of the inheritance mechanism? What is the Abstraction concept

Constructors : recall

  • Definition
    A constructor is a dedicated mechanism that is necessary to construct the object. The mechanism of using a constructor to build a new instance is called instantiation
  • Parameters
    The constructor can take parameters, like a method, to initialize the current instance with a particular value

Constructors : schema

package fr.tbr.exercises;

/**
 * Example of a car, constructor with
 * a "color" parameter
 * @author Tom
 */
public class Car {
	private String color;

	public Car(String color) {
		this.color = color;
	}

}
        

Class vs Instance

  • Notice in the previous example the this keyword.
  • this allows to access to the current instance values
  • An instance is a representation with some particular values of the class, Examples:
    • A blue car is a representation of the class Car
    • You are representations of the Human class.
      The class Human, is describing the shape of each Human. You are a specific representation (with your own characteristics) of this class

Class vs Instance (2)

  • The this keyword can be used in any non-static methods
  • Example: interest calculation in the bank account system
    /**
     * return the calculated interest on one year
     * warning, this method updates the balance with the result of that computation
     * @return the interest
     */
    public double calculateInterest(){
    	double	result = this.interestRate * this.balance;
        this.balance += result; //equivalent to this.balance = this.balance + result
        return result;
    }
To precise whether you are using a local variable or a class field, the this keyword is strongly recommended

Taking advantage of inheritance

  • The inheritance mechanism should always represent a IS-A relationship
  • When it applies, this relationship allows to factor a lot of code
  • Example: Inheritance from quadrilateral to rectangle

public class Quadrilateral {

	private double sideA;
	private double sideB;
	private double sideC;
	private double sideD;
    /*...*/
	public double calculatePerimeter(){
		return this.sideA + this.sideB
        + this.sideC + this.sideD;
	}

}
public class Rectangle extends Quadrilateral{
 public Rectangle(double height, double width)
 {
   super (height, height, width, width);
 }

}

Taking advantage of inheritance (2)

  • Notice the super keyword: it allows the access of the inherited Class (here the Quadrilateral Class)
  • As the Rectangle Class inherits from the Quadrilateral class, you don't have to write the calculatePerimeter() method again
Warning: The inheritance mechanism allows natural code factoring, but if the relationship between inheriting objects is not a real IS-A relationship, the code maintenance can be very painful

Exercise

In the geometry example found here, add the Ellipse object and try to generalize the Circle class methods

Inheritance problems

When you are using inheritance you have to face several problems
  • You can't change the characteristics of a super object methods
  • You have to define clearly what methods can be overridden or not
  • You have to foresee that the behaviour of the overridden methods can be unpredictable

Abstract Classes: Introduction

  • Problem: Sometimes you want to define a class that provides common behavior, but it doesn't make sense to instantiate it directly
  • Example: A generic "Shape" class - you never want just a "Shape", you want a Circle, Rectangle, etc.
  • Solution: Use abstract classes
Abstract classes can't be instantiated directly - they serve as templates for subclasses

Abstract Classes: Syntax

public abstract class Shape {
    protected String color;

    public Shape(String color) {
        this.color = color;
    }

    // Abstract method - no implementation
    public abstract double calculateArea();

    // Concrete method - has implementation
    public String getColor() {
        return this.color;
    }
}
  • Use abstract keyword for the class
  • Can contain abstract methods (no implementation)
  • Can contain concrete methods (with implementation)

Abstract Classes: Implementation

public class Circle extends Shape {
    private double radius;

    public Circle(String color, double radius) {
        super(color);  // Call parent constructor
        this.radius = radius;
    }

    @Override
    public double calculateArea() {
        return Math.PI * radius * radius;
    }
}
  • Subclasses must implement all abstract methods
  • Use @Override annotation to indicate you're implementing an abstract method

Interfaces: Introduction

  • Limitation of inheritance: Java doesn't support multiple inheritance
  • Problem: What if a class needs to have multiple "types" of behavior?
  • Solution: Use interfaces
An interface is a contract that specifies what methods a class must implement, without providing the implementation
Example: A Car can be both Drivable and Lockable

Interfaces: Syntax

public interface Drawable {
    // All methods in interfaces are implicitly public and abstract
    void draw();
    double getArea();

    // Since Java 8: default methods with implementation
    default void display() {
        System.out.println("Drawing shape with area: " + getArea());
    }
}
  • Use interface keyword instead of class
  • Methods are implicitly public abstract
  • Can have default methods (since Java 8) with implementation
  • Can have static methods

Interfaces: Implementation

public class Rectangle extends Shape implements Drawable, Comparable<Rectangle> {
    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 calculateArea() {
        return width * height;
    }

    @Override
    public void draw() {
        System.out.println("Drawing a " + color + " rectangle");
    }

    @Override
    public double getArea() {
        return calculateArea();
    }

    @Override
    public int compareTo(Rectangle other) {
        return Double.compare(this.calculateArea(), other.calculateArea());
    }
}

Abstract Classes vs Interfaces

Feature Abstract Class Interface
Inheritance Can extend only one Can implement multiple
Fields Can have any fields Only public static final
Methods Abstract + concrete Abstract + default + static
Constructor Yes No
Use case IS-A relationship CAN-DO behavior

When to Use Which?

Use Abstract Class When:

  • You have closely related classes
  • You want to share code among several related classes
  • You need non-public fields or methods
  • Classes have a true IS-A relationship
Example: Shape → Circle, Rectangle

Use Interface When:

  • Unrelated classes implement your interface
  • You want to specify behavior without implementation
  • You want multiple inheritance of type
  • Classes CAN-DO something
Example: Comparable, Drawable, Serializable

Polymorphism with Interfaces

public class ShapeDemo {
    public static void main(String[] args) {
        // Polymorphism: treat all shapes uniformly
        List<Shape> shapes = new ArrayList<>();
        shapes.add(new Circle("Red", 5.0));
        shapes.add(new Rectangle("Blue", 4.0, 6.0));
        shapes.add(new Triangle("Green", 3.0, 4.0));

        // Calculate total area using polymorphism
        double totalArea = 0;
        for (Shape shape : shapes) {
            totalArea += shape.calculateArea();  // Calls correct method
            System.out.println(shape.getClass().getSimpleName() +
                             " area: " + shape.calculateArea());
        }

        System.out.println("Total area: " + totalArea);
    }
}
Polymorphism allows you to treat different types uniformly through their common interface or superclass

Real-World Example: Geometry System

// Abstract base class
public abstract class Shape {
    protected String color;

    public Shape(String color) {
        this.color = color;
    }

    public abstract double calculateArea();
    public abstract double calculatePerimeter();
}

// Interface for drawable objects
public interface Drawable {
    void draw();
    default String getDescription() {
        return "A drawable shape";
    }
}

// Concrete implementation
public class Circle extends Shape implements Drawable, Comparable<Circle> {
    private double radius;

    public Circle(String color, double radius) {
        super(color);
        this.radius = radius;
    }

    @Override
    public double calculateArea() {
        return Math.PI * radius * radius;
    }

    @Override
    public double calculatePerimeter() {
        return 2 * Math.PI * radius;
    }

    @Override
    public void draw() {
        System.out.println("Drawing a " + color + " circle with radius " + radius);
    }

    @Override
    public int compareTo(Circle other) {
        return Double.compare(this.radius, other.radius);
    }
}

Exercise: Geometry System

Create a complete geometry system with the following:

  1. Create an abstract Shape class with:
    • A color field
    • Abstract methods: calculateArea() and calculatePerimeter()
    • A concrete method: getColor()
  2. Create a Drawable interface with a draw() method
  3. Implement these concrete classes:
    • Circle (radius)
    • Rectangle (width, height)
    • Triangle (base, height)
  4. Create a ShapeManager class that:
    • Stores a list of shapes
    • Calculates the total area of all shapes
    • Finds the shape with the largest area
    • Draws all shapes
Bonus: Make your shapes implement Comparable to sort them by area

Key Takeaways

  • Abstract Classes:
    • Cannot be instantiated
    • Can have both abstract and concrete methods
    • Used for IS-A relationships with shared implementation
  • Interfaces:
    • Define contracts for behavior
    • Support multiple inheritance of type
    • Used for CAN-DO behaviors
  • Polymorphism:
    • Treat different types uniformly through common interfaces/superclasses
    • Enables flexible and maintainable code

Slide Overview