TD3 — Inheritance, Interfaces & Polymorphism
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
extendsandabstract - Implement multiple interfaces on a single class
- Exploit polymorphism to write generic algorithms
- Use
Comparable<T>to sort objects - Practice with
staticfields 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?
4.4 (Modern Java — optional) Rewrite ShapeStats with the Streams API instead of explicit loops — the Java counterpart of Python comprehensions / sum(...):
import java.util.Comparator;
public static double totalArea(List<Shape> shapes) {
return shapes.stream().mapToDouble(Shape::area).sum();
}
public static Shape largest(List<Shape> shapes) {
return shapes.stream()
.max(Comparator.comparingDouble(Shape::area))
.orElse(null);
}
public static int countByType(List<Shape> shapes, Class<?> type) {
return (int) shapes.stream().filter(type::isInstance).count();
}
Same results as your loop version, less code. See the Python → Java reference.
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 |