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?
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 |