Skip to content

TD3 — Inheritance, Interfaces & Polymorphism

← Part 1 Overview

S7 Inf A3 — Java for Graphical and Mobile Programming Stéphane Derrode — Centrale Lyon
Part 1 — Java Fundamentals
Duration: 2h · Continue in your Maven project (td1 or create td3)


Objectives

  • Build a class hierarchy using extends and abstract
  • Implement multiple interfaces on a single class
  • Exploit polymorphism to write generic algorithms
  • Use Comparable<T> to sort objects
  • Practice with static fields and utility classes

Part 1 — Shape Hierarchy (50 min)

1.1 Abstract base class Shape

Create src/main/java/com/s7infa3/shapes/Shape.java.

package com.s7infa3.shapes;

public abstract class Shape implements Comparable<Shape> {
    protected String color;

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

    public String getColor() { return color; }

    // Abstract — every concrete shape must implement these
    public abstract double area();
    public abstract double perimeter();

    // Concrete — available to all subclasses
    public String describe() {
        return String.format("%s %s: area=%.2f, perimeter=%.2f",
            color, getClass().getSimpleName(), area(), perimeter());
    }

    // Natural ordering by area (ascending)
    @Override
    public int compareTo(Shape other) {
        return Double.compare(this.area(), other.area());
    }

    @Override
    public String toString() {
        return describe();
    }
}

1.2 Interface Drawable

Create src/main/java/com/s7infa3/shapes/Drawable.java.

package com.s7infa3.shapes;

public interface Drawable {
    void draw();                        // print a text representation
    default String getDisplayInfo() {   // default method
        return "Drawable shape";
    }
}

1.3 Concrete classes

Create the following three classes in the same package. Each must: - Extend Shape - Implement Drawable - Provide a constructor, area(), perimeter(), and draw()

Circle

Member Formula / value
field double radius
area() π × r²
perimeter() 2 × π × r
draw() prints ○ Circle(r=3.00, color=red)

Rectangle

Member Formula / value
fields double width, double height
area() width × height
perimeter() 2 × (width + height)
draw() prints ▭ Rectangle(4.00×2.00, color=blue)

Triangle

Member Formula / value
fields double a, double b, double c (three sides)
area() Heron's formula: √(s(s-a)(s-b)(s-c)) where s = (a+b+c)/2
perimeter() a + b + c
draw() prints △ Triangle(3.00, 4.00, 5.00, color=green)
constructor throw IllegalArgumentException if the three sides do not form a valid triangle (triangle inequality: each side must be < sum of the other two)

1.4 Test in main()

import com.s7infa3.shapes.*;
import java.util.*;

List<Shape> shapes = new ArrayList<>();
shapes.add(new Circle("red", 3.0));
shapes.add(new Rectangle("blue", 4.0, 2.0));
shapes.add(new Triangle("green", 3.0, 4.0, 5.0));
shapes.add(new Circle("yellow", 1.5));

// Polymorphism: describe() calls the right area/perimeter
for (Shape s : shapes) {
    System.out.println(s.describe());
}

// Sort by area (uses compareTo)
Collections.sort(shapes);
System.out.println("\n--- Sorted by area (ascending) ---");
for (Shape s : shapes) {
    System.out.printf("  %.2f  %s%n", s.area(), s.getClass().getSimpleName());
}

// Draw all Drawable shapes
System.out.println("\n--- Drawing ---");
for (Shape s : shapes) {
    if (s instanceof Drawable d) {   // pattern matching (Java 16+)
        d.draw();
    }
}

// Sort descending by perimeter using lambda
shapes.sort((a, b) -> Double.compare(b.perimeter(), a.perimeter()));
System.out.println("\n--- Sorted by perimeter (descending) ---");
shapes.forEach(s -> System.out.printf("  %.2f  %s%n",
    s.perimeter(), s.getClass().getSimpleName()));

Expected output (approximate):

red Circle: area=28.27, perimeter=18.85
blue Rectangle: area=8.00, perimeter=12.00
green Triangle: area=6.00, perimeter=12.00
yellow Circle: area=7.07, perimeter=9.42

--- Sorted by area (ascending) ---
  6.00  Triangle
  7.07  Circle
  8.00  Rectangle
  28.27  Circle

--- Drawing ---
○ Circle(r=3.00, color=red)
▭ Rectangle(4.00×2.00, color=blue)
△ Triangle(3.00, 4.00, 5.00, color=green)
○ Circle(r=1.50, color=yellow)

1.5 Test the triangle guard

try {
    new Triangle("red", 1.0, 2.0, 10.0); // invalid: 1+2 < 10
} catch (IllegalArgumentException e) {
    System.out.println("Caught: " + e.getMessage()); // expected
}

Part 2 — Static Utility Class ShapeStats (20 min)

Create src/main/java/com/s7infa3/shapes/ShapeStats.java with only static methods — no instance fields, no constructor (or a private no-arg constructor to prevent instantiation).

Method Description
totalArea(List<Shape> shapes) Returns the sum of all areas
totalPerimeter(List<Shape> shapes) Returns the sum of all perimeters
largest(List<Shape> shapes) Returns the shape with the largest area
smallest(List<Shape> shapes) Returns the shape with the smallest area
countByType(List<Shape> shapes, Class<?> type) Returns the number of shapes of a given type

Test:

System.out.printf("Total area: %.2f%n", ShapeStats.totalArea(shapes));
System.out.println("Largest:  " + ShapeStats.largest(shapes).describe());
System.out.println("Smallest: " + ShapeStats.smallest(shapes).describe());
System.out.println("Circles: " + ShapeStats.countByType(shapes, Circle.class));


Part 3 — Zoo Polymorphism (20 min)

3.1 Class hierarchy

Create in package com.s7infa3.zoo:

Animal (abstract)
├── field: String name
├── abstract: String speak()
├── concrete: String describe() → "Rex (Dog) says: Woof!"
├── Dog    extends Animal → speak() = name + " says: Woof!"
├── Cat    extends Animal → speak() = name + " says: Meow!"
└── Bird   extends Animal → speak() = name + " says: Tweet!"

Also create a Trainable interface:

public interface Trainable {
    boolean learn(String command);  // returns true if the animal can learn it
    List<String> getKnownCommands();
}

Make Dog implement Trainable: - Stores learned commands in an ArrayList<String> - learn(command) adds the command and returns true - A Cat is NOT Trainable

3.2 Test polymorphism

List<Animal> animals = new ArrayList<>();
animals.add(new Dog("Rex"));
animals.add(new Cat("Luna"));
animals.add(new Dog("Max"));
animals.add(new Bird("Tweety"));

// Polymorphic loop
for (Animal a : animals) {
    System.out.println(a.describe());
}

// Train only Trainable animals
for (Animal a : animals) {
    if (a instanceof Trainable t) {
        t.learn("sit");
        t.learn("fetch");
        System.out.println(a.getName() + " knows: " + t.getKnownCommands());
    }
}

Part 4 — Exploration (remaining time)

4.1 Add a Square class that extends Rectangle — the constructor takes only one side length. Override toString() to print "Square(side=3.00)". Does it need to override area() and perimeter()?

4.2 Add a static int instanceCount field to Shape that counts the total number of shapes ever created. Increment it in the Shape constructor. Add Shape.getInstanceCount().

4.3 Make Triangle implement Comparable<Triangle> independently of Shape, sorting by perimeter instead of area. What problem do you encounter? How would you solve it?


Deliverable

No formal submission. Verify all main() outputs match the expected values before the end of the session.


Common Errors

Error Cause Fix
Class is not abstract and does not override abstract method Forgot to implement area() or perimeter() Add the missing @Override methods
cannot find symbol on getClass().getSimpleName() Called on wrong object Check you call it on this inside the class
instanceof pattern not recognized Java < 16 Use instanceof Drawable && ((Drawable)s).draw()
NaN for triangle area Negative value under sqrt (invalid triangle not guarded) Add the triangle inequality check
Collections.sort() fails Shape doesn't implement Comparable<Shape> Check implements Comparable<Shape> on the abstract class