Solved Java exercise on interfaces: contracts, classes implementing several interfaces and default methods, with tested code and a full explanation.
Updated on
Exam Code Java Object-oriented programming Exam level Tested code

A drafting company needs a small model of geometric shapes based on interfaces, not on an abstract base class. You must design two contracts and four implementations.
The interface Shape declares double area() and String name(), and includes a default String describe() method that returns the name, a single space, the text area= and the area with exactly two decimals. For example, for a square of side 2 it would return the name Square, a space and area=4.00. Numbers are formatted with String.format(Locale.ROOT, "%.2f", ...), so the decimal separator is always a dot.
The interface Sides declares int sideCount(). It must be implemented only by the shapes that have sides: the circle does not implement it. The four classes are Circle, Rectangle, Square and Triangle, and their name() methods return exactly those strings. The triangle is a right triangle defined by base and height. The square must be implemented as an independent class, not as a subclass of the rectangle.
Input (STDIN). The first line contains an integer n, the number of shapes (it may be 0). It is followed by n lines, one per shape, with a keyword and its measurements separated by spaces: C r (circle of radius r), R b h (rectangle), S s (square of side s) and T b h (triangle with base b and height h). The measurements are positive real numbers.
Output (STDOUT). For each shape, in input order, one line with the result of describe(); if the shape implements Sides, append on the same line a space, sides= and the number of sides. After the n lines, print one final line with total= followed by the sum of the areas with two decimals, then a space and withSides= followed by the number of shapes that implement Sides. If n is 0, only that final line is printed.
Sample input
4
C 1
R 2 3
S 2
T 3 4Expected output
Circle area=3.14
Rectangle area=6.00 sides=4
Square area=4.00 sides=4
Triangle area=6.00 sides=3
total=19.14 withSides=3- Shape interface and default method describe() correct, with no duplicated code in the classes
- 0.75
- Sides interface and classes implementing one or two interfaces as appropriate (the circle does not implement Sides)
- 0.75
- Input parsing, shape construction and use of instanceof or pattern matching for Sides
- 0.5
- Output in the exact format, rounded to two decimals with Locale.ROOT
- 0.5
Hints
Hint 1 · Where should the code of describe() live?
A default method is written once in the interface and can call the other abstract methods of the same interface (name() and area()). If you find it repeated in all four classes, something is wrong.
Hint 2 · How do you tell a shape with sides from one without?
The circle does not implement Sides. When you walk through the list of Shape, check at run time whether the object is also a Sides using instanceof. Shape does not need to know anything about Sides.
Hint 3 · And the number formatting?
Use String.format(Locale.ROOT, "%.2f", value). Without Locale.ROOT, on a machine configured for a locale with a decimal comma you would get a comma and the output would not match. Accumulate the total in a double and format only when printing.
Solution
Explained solution
The solution separates two independent contracts. Shape describes what every shape can do (compute its area and give its name) and provides, through a default method, a common behaviour built from those two methods. Sides is an optional contract: only polygonal shapes sign it.
The classes Rectangle, Square and Triangle implement both interfaces at once (multiple implementation), while Circle implements only Shape. The main program always works with the type Shape and queries the additional contract with instanceof.
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Scanner;
interface Shape {
double area();
String name();
default String describe() {
return name() + " area=" + String.format(Locale.ROOT, "%.2f", area());
}
}
interface Sides {
int sideCount();
}
class Circle implements Shape {
private final double radius;
Circle(double radius) {
this.radius = radius;
}
public double area() {
return Math.PI * radius * radius;
}
public String name() {
return "Circle";
}
}
class Rectangle implements Shape, Sides {
private final double base;
private final double height;
Rectangle(double base, double height) {
this.base = base;
this.height = height;
}
public double area() {
return base * height;
}
public String name() {
return "Rectangle";
}
public int sideCount() {
return 4;
}
}
class Square implements Shape, Sides {
private final double side;
Square(double side) {
this.side = side;
}
public double area() {
return side * side;
}
public String name() {
return "Square";
}
public int sideCount() {
return 4;
}
}
class Triangle implements Shape, Sides {
private final double base;
private final double height;
Triangle(double base, double height) {
this.base = base;
this.height = height;
}
public double area() {
return base * height / 2;
}
public String name() {
return "Triangle";
}
public int sideCount() {
return 3;
}
}
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
sc.useLocale(Locale.ROOT);
int n = sc.hasNextInt() ? sc.nextInt() : 0;
List<Shape> shapes = new ArrayList<>();
for (int i = 0; i < n; i++) {
String kind = sc.next();
switch (kind) {
case "C" -> shapes.add(new Circle(sc.nextDouble()));
case "R" -> shapes.add(new Rectangle(sc.nextDouble(), sc.nextDouble()));
case "S" -> shapes.add(new Square(sc.nextDouble()));
case "T" -> shapes.add(new Triangle(sc.nextDouble(), sc.nextDouble()));
default -> throw new IllegalArgumentException("Unknown kind: " + kind);
}
}
double total = 0;
int withSides = 0;
for (Shape s : shapes) {
StringBuilder line = new StringBuilder(s.describe());
if (s instanceof Sides sd) {
line.append(" sides=").append(sd.sideCount());
withSides++;
}
System.out.println(line);
total += s.area();
}
System.out.println("total=" + String.format(Locale.ROOT, "%.2f", total) + " withSides=" + withSides);
}
}The interface Shape contains two abstract methods and the default describe() method. Inside describe() the calls to name() and area() are resolved dynamically according to the concrete class of the object. The line format (name, space, area=, two decimals) is therefore written in one single place, and none of the four classes repeats or overrides it.
Sides is a one-method interface. Rectangle, Square and Triangle declare implements Shape, Sides; Circle declares only implements Shape. This models the domain faithfully: having sides is a capability that some shapes have and others do not, which is why sideCount() is not put into Shape returning 0 for the circle. Methods implemented from an interface must be public, which is why that word appears on each of them.
In main we read n and build a List<Shape> with a switch on the keyword. The scanner uses Locale.ROOT so decimals are read with a dot. We always work with the type Shape: this is pure polymorphism, and the rest of the program does not know which concrete class it is holding.
In the output loop, s.describe() gives the base line. Then s instanceof Sides sd checks whether the object also fulfils the second contract and, if so, introduces the variable sd already cast, without an explicit cast. We append sides= with sd.sideCount() and increment withSides. The variable total accumulates the unrounded area and is formatted only when the last line is printed.
With the test cases: the empty list prints only total=0.00 withSides=0; a lone circle prints one line without sides= and withSides=0; the mixed example checks that the three polygonal types report 4, 4 and 3 sides while the circle reports none. The decimals case checks that areas are summed without rounding before formatting, and the repeated-shapes case checks that two triangles are both counted, giving withSides=2.
Test cases
| Case | Input | Expected output | Actual output | Result |
|---|---|---|---|---|
| Mixed example | 4
C 1
R 2 3
S 2
T 3 4 | Circle area=3.14
Rectangle area=6.00 sides=4
Square area=4.00 sides=4
Triangle area=6.00 sides=3
total=19.14 withSides=3 | Circle area=3.14
Rectangle area=6.00 sides=4
Square area=4.00 sides=4
Triangle area=6.00 sides=3
total=19.14 withSides=3 | OK |
| No shapes | 0 | total=0.00 withSides=0 | total=0.00 withSides=0 | OK |
| Only a circle | 1
C 2.5 | Circle area=19.63
total=19.63 withSides=0 | Circle area=19.63
total=19.63 withSides=0 | OK |
| Decimals and accumulation | 3
S 1.5
T 2.5 3
R 0.5 0.5 | Square area=2.25 sides=4
Triangle area=3.75 sides=3
Rectangle area=0.25 sides=4
total=6.25 withSides=3 | Square area=2.25 sides=4
Triangle area=3.75 sides=3
Rectangle area=0.25 sides=4
total=6.25 withSides=3 | OK |
| Repeated shapes | 3
T 10 10
T 1 1
C 0.5 | Triangle area=50.00 sides=3
Triangle area=0.50 sides=3
Circle area=0.79
total=51.29 withSides=2 | Triangle area=50.00 sides=3
Triangle area=0.50 sides=3
Circle area=0.79
total=51.29 withSides=2 | OK |
Actual outputs: code compiled with Java 21.0.12.1 (Temurin) and run in an isolated container on 12 October 2026.
Complexity
Time is O(n): each shape is read once, its object is created in constant time and the list is traversed once to print. Each call to describe(), area() or sideCount() is O(1), and so is the instanceof check.
Memory is O(n) because of the list of shapes kept in memory. If they did not need to be stored, each shape could be processed as it is read and O(1) memory would suffice, but then the point of the exercise, working with a collection of type Shape, would be lost.
Common mistakes
- Putting
sideCount()in theShapeinterface and making the circle return 0: it breaks the contract (a circle does not have 0 sides, it has no sides) and removes the point of using two interfaces. - Forgetting
publicwhen implementing interface methods: all interface methods are public, and reducing visibility in the class is a compile-time error. - Duplicating the body of
describe()in each class instead of taking advantage of thedefaultmethod, or declaring itabstractout of habit from abstract classes. - Formatting with
String.format("%.2f", ...)withoutLocale.ROOT: on a system whose locale uses a decimal comma it prints3,14and the output no longer matches. - Rounding each area before adding it: the total may differ by one hundredth from the correct one. Sum at full precision and format only at the end.
- Making
Squareinherit fromRectanglewhen the statement asks for independent classes, or trying to instantiate an interface withnew Shape().
Variants
Default method conflict between two interfaces
The professor adds a default String label() method to both Shape and Sides, with different bodies. The classes that implement both interfaces stop compiling: they inherit two default implementations with the same signature. The fix is to override label() in the class and, if you want to reuse one of them, call it with the syntax Shape.super.label() or Sides.super.label().
Sorting the shapes by area
The output must be printed from smallest to largest area. One option is to make Shape extend Comparable<Shape> and write a compareTo by area; a cleaner one is an external comparator: shapes.sort(Comparator.comparingDouble(Shape::area)). The list must be sorted before the output loop, and the rest of the program does not change.
On the syllabus
Interfaces as a mechanism for defining contracts appear in the syllabus description of ISIS1209 (Object-Oriented Programming II, Honours) at Universidad de los Andes (Colombia), which mentions studying mechanisms for class reuse, such as inheritance, and for defining contracts, such as interfaces. This exercise is written to practise exactly that distinction: what is inherited and what is promised. It is an original exercise of ours, not one taken from that course or its exams.
Article generated with AI.larebelion



Comentarios
Publicar un comentario