Ejercicio resuelto de interfaces en Java: contratos, clases que implementan varias interfaces y métodos default, con código probado y explicado.
Actualizado el
Código de Examen Java Programación orientada a objetos Nivel examen Código probado

Una empresa de planos necesita un pequeño modelo de figuras geométricas basado en interfaces, no en una clase base abstracta. Debes diseñar dos contratos y cuatro implementaciones.
La interfaz Figura declara double area() y String nombre(), e incluye un método default String describe() que devuelve el nombre, un único espacio, el texto area= y el área con exactamente dos decimales. Por ejemplo, para un cuadrado de lado 2 devolvería el nombre Cuadrado, un espacio y area=4.00. Los números se formatean con String.format(Locale.ROOT, "%.2f", ...), de modo que el separador decimal siempre es el punto.
La interfaz Lados declara int numLados(). Debe ser implementada únicamente por las figuras que tienen lados: el círculo no la implementa. Las cuatro clases son Circulo, Rectangulo, Cuadrado y Triangulo, y sus métodos nombre() devuelven exactamente esas cadenas, escritas así, sin tildes. El triángulo es rectángulo y se define por base y altura. El cuadrado debe implementarse como clase independiente, no como subclase del rectángulo.
Entrada (STDIN). La primera línea contiene un entero n, el número de figuras (puede ser 0). Siguen n líneas, una por figura, con una palabra clave y sus medidas separadas por espacios: C r (círculo de radio r), R b h (rectángulo), Q l (cuadrado de lado l) y T b h (triángulo de base b y altura h). Las medidas son números reales positivos.
Salida (STDOUT). Por cada figura, en el orden de entrada, una línea con el resultado de describe(); si la figura implementa Lados, se añade en la misma línea un espacio, lados= y el número de lados. Tras las n líneas se imprime una última línea con total= seguido de la suma de las áreas con dos decimales, y a continuación conLados= con la cantidad de figuras que implementan Lados, separados por un espacio. Si n es 0, solo se imprime esa última línea.
Entrada de ejemplo
4
C 1
R 2 3
Q 2
T 3 4Salida esperada
Circulo area=3.14
Rectangulo area=6.00 lados=4
Cuadrado area=4.00 lados=4
Triangulo area=6.00 lados=3
total=19.14 conLados=3- Interfaz Figura y método default describe() correctos, sin duplicar código en las clases
- 0,75
- Interfaz Lados y clases que implementan una o dos interfaces según corresponda (el círculo no implementa Lados)
- 0,75
- Lectura de la entrada, construcción de las figuras y uso de instanceof o pattern matching para Lados
- 0,5
- Salida con el formato exacto y redondeo a dos decimales con Locale.ROOT
- 0,5
Pistas
Pista 1 · ¿Dónde debe vivir el código de describe()?
Un método default se escribe una sola vez en la interfaz y puede llamar a los otros métodos abstractos de la misma interfaz (nombre() y area()). Si lo ves repetido en las cuatro clases, algo va mal.
Pista 2 · ¿Cómo se distingue una figura con lados de otra sin?
El círculo no implementa Lados. Cuando recorras la lista de Figura, comprueba en tiempo de ejecución si el objeto es también una Lados con instanceof. No hace falta que Figura sepa nada de Lados.
Pista 3 · ¿Y el formato de los números?
Usa String.format(Locale.ROOT, "%.2f", valor). Sin Locale.ROOT, en un ordenador configurado en español obtendrías una coma decimal y la salida no coincidiría. Acumula el total en un double y formatea solo al imprimir.
Solución
Solución explicada
La solución separa dos contratos independientes. Figura describe lo que toda figura sabe hacer (calcular su área y dar su nombre) y aporta, mediante un método default, un comportamiento común construido a partir de esos dos métodos. Lados es un contrato opcional: solo lo firman las figuras poligonales.
Las clases Rectangulo, Cuadrado y Triangulo implementan ambas interfaces a la vez (implementación múltiple), mientras que Circulo implementa solo Figura. El programa principal trabaja siempre con el tipo Figura y consulta el contrato adicional con instanceof.
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Scanner;
interface Figura {
double area();
String nombre();
default String describe() {
return nombre() + " area=" + String.format(Locale.ROOT, "%.2f", area());
}
}
interface Lados {
int numLados();
}
class Circulo implements Figura {
private final double radio;
Circulo(double radio) {
this.radio = radio;
}
public double area() {
return Math.PI * radio * radio;
}
public String nombre() {
return "Circulo";
}
}
class Rectangulo implements Figura, Lados {
private final double base;
private final double altura;
Rectangulo(double base, double altura) {
this.base = base;
this.altura = altura;
}
public double area() {
return base * altura;
}
public String nombre() {
return "Rectangulo";
}
public int numLados() {
return 4;
}
}
class Cuadrado implements Figura, Lados {
private final double lado;
Cuadrado(double lado) {
this.lado = lado;
}
public double area() {
return lado * lado;
}
public String nombre() {
return "Cuadrado";
}
public int numLados() {
return 4;
}
}
class Triangulo implements Figura, Lados {
private final double base;
private final double altura;
Triangulo(double base, double altura) {
this.base = base;
this.altura = altura;
}
public double area() {
return base * altura / 2;
}
public String nombre() {
return "Triangulo";
}
public int numLados() {
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<Figura> figuras = new ArrayList<>();
for (int i = 0; i < n; i++) {
String tipo = sc.next();
switch (tipo) {
case "C" -> figuras.add(new Circulo(sc.nextDouble()));
case "R" -> figuras.add(new Rectangulo(sc.nextDouble(), sc.nextDouble()));
case "Q" -> figuras.add(new Cuadrado(sc.nextDouble()));
case "T" -> figuras.add(new Triangulo(sc.nextDouble(), sc.nextDouble()));
default -> throw new IllegalArgumentException("Tipo desconocido: " + tipo);
}
}
double total = 0;
int conLados = 0;
for (Figura f : figuras) {
StringBuilder linea = new StringBuilder(f.describe());
if (f instanceof Lados l) {
linea.append(" lados=").append(l.numLados());
conLados++;
}
System.out.println(linea);
total += f.area();
}
System.out.println("total=" + String.format(Locale.ROOT, "%.2f", total) + " conLados=" + conLados);
}
}La interfaz Figura contiene dos métodos abstractos y el método default describe(). Dentro de describe() se llama a nombre() y area(), que se resuelven dinámicamente según la clase concreta del objeto. Así el formato de la línea (nombre, espacio, area=, dos decimales) está escrito en un único sitio, y ninguna de las cuatro clases lo repite ni lo sobrescribe.
Lados es una interfaz de un solo método. Rectangulo, Cuadrado y Triangulo declaran implements Figura, Lados; Circulo declara solo implements Figura. Esto modela fielmente el dominio: tener lados es una capacidad que unas figuras tienen y otras no, y por eso no se mete numLados() en Figura devolviendo 0 para el círculo. Los métodos implementados de una interfaz deben ser public, razón por la que aparece esa palabra en cada uno.
En main se lee n y se construye una List<Figura> con un switch sobre la palabra clave. El escáner usa Locale.ROOT para leer los decimales con punto. Se trabaja siempre con el tipo Figura: es polimorfismo puro, y el resto del programa no sabe qué clase concreta tiene delante.
En el bucle de salida, f.describe() da la línea base. Después, f instanceof Lados l comprueba si el objeto también cumple el segundo contrato y, en caso afirmativo, introduce la variable l ya convertida, sin cast explícito. Se añade lados= con l.numLados() y se incrementa conLados. La variable total acumula el área sin redondear, y solo se formatea al imprimir la última línea.
Con los casos de prueba: la lista vacía imprime únicamente total=0.00 conLados=0; un círculo solo imprime una línea sin lados= y conLados=0; el caso mixto comprueba que los tres tipos poligonales informan 4, 4 y 3 lados y que el círculo no los informa. El caso de acumulación de decimales verifica que se suma sin redondear antes de formatear.
Casos de prueba
| Caso | Entrada | Salida esperada | Salida real | Resultado |
|---|---|---|---|---|
| Ejemplo mixto | 4
C 1
R 2 3
Q 2
T 3 4 | Circulo area=3.14
Rectangulo area=6.00 lados=4
Cuadrado area=4.00 lados=4
Triangulo area=6.00 lados=3
total=19.14 conLados=3 | Circulo area=3.14
Rectangulo area=6.00 lados=4
Cuadrado area=4.00 lados=4
Triangulo area=6.00 lados=3
total=19.14 conLados=3 | OK |
| Sin figuras | 0 | total=0.00 conLados=0 | total=0.00 conLados=0 | OK |
| Solo un círculo | 1
C 2.5 | Circulo area=19.63
total=19.63 conLados=0 | Circulo area=19.63
total=19.63 conLados=0 | OK |
| Decimales y acumulación | 3
Q 1.5
T 2.5 3
R 0.5 0.5 | Cuadrado area=2.25 lados=4
Triangulo area=3.75 lados=3
Rectangulo area=0.25 lados=4
total=6.25 conLados=3 | Cuadrado area=2.25 lados=4
Triangulo area=3.75 lados=3
Rectangulo area=0.25 lados=4
total=6.25 conLados=3 | OK |
| Figuras repetidas | 3
T 10 10
T 1 1
C 0.5 | Triangulo area=50.00 lados=3
Triangulo area=0.50 lados=3
Circulo area=0.79
total=51.29 conLados=2 | Triangulo area=50.00 lados=3
Triangulo area=0.50 lados=3
Circulo area=0.79
total=51.29 conLados=2 | OK |
Salidas reales: código compilado con Java 21.0.12.1 (Temurin) y ejecutado en un contenedor aislado el 12 de octubre de 2026.
Complejidad
El tiempo es O(n): se lee cada figura una vez, se crea el objeto en tiempo constante y se recorre la lista una vez para imprimir. Cada llamada a describe(), area() o numLados() es O(1), y la comprobación instanceof también.
La memoria es O(n) por la lista de figuras que se mantiene en memoria. Si no hiciera falta conservarlas, podría procesarse cada figura al leerla y bastaría memoria O(1), pero entonces se perdería el ejercicio de trabajar con una colección de tipo Figura.
Errores típicos
- Poner
numLados()en la interfazFiguray hacer que el círculo devuelva 0: rompe el contrato (un círculo no tiene 0 lados, no tiene lados) y elimina el sentido de usar dos interfaces. - Olvidar
publical implementar métodos de una interfaz: todos los métodos de interfaz son públicos, y reducir la visibilidad en la clase es un error de compilación. - Duplicar el cuerpo de
describe()en cada clase en lugar de aprovechar el métododefault, o declararlo comoabstractpor costumbre con las clases abstractas. - Formatear con
String.format("%.2f", ...)sinLocale.ROOT: en un sistema en español se imprime3,14y la salida deja de coincidir con la esperada. - Redondear cada área antes de sumarla: el total puede diferir en una centésima del correcto. Se suma con precisión completa y se formatea solo al final.
- Hacer que
Cuadradoherede deRectangulocuando el enunciado pide clases independientes, o intentar crear una instancia de una interfaz connew Figura().
Variantes
Conflicto de métodos default entre dos interfaces
El profesor añade un método default String etiqueta() tanto en Figura como en Lados, con cuerpos distintos. Las clases que implementan ambas interfaces dejan de compilar: hereda dos implementaciones por defecto con la misma firma. La solución es sobrescribir etiqueta() en la clase y, si se quiere reutilizar una de ellas, invocarla con la sintaxis Figura.super.etiqueta() o Lados.super.etiqueta().
Ordenar las figuras por área con Comparable
Se pide que la salida se imprima de menor a mayor área. Una opción es hacer que Figura extienda Comparable<Figura> y dar un compareTo por área; otra, más limpia, es usar un comparador externo: figuras.sort(Comparator.comparingDouble(Figura::area)). Hay que ordenar la lista antes del bucle de salida, y el resto del programa no cambia.
En los temarios
El uso de interfaces como mecanismo para definir contratos figura en el temario del curso ISIS1209 (Aprendizaje de la programación orientada a objetos II, Honores) de la Universidad de los Andes (Colombia), cuya descripción menciona el estudio de mecanismos de reutilización de clases, como la herencia, y de definición de contratos, como las interfaces. Este ejercicio trabaja justamente esa distinción: qué se hereda y qué se promete.
Historial de cambios
- Reescrito con la plantilla de Código de Examen: enunciado de examen, pistas, solución explicada, casos de prueba y código compilado y ejecutado; versión en inglés enlazada.
- Publicación original.
Artículo generado mediante AI.larebelion · Revisado por Kernel
