Actualizado el
Código de Examen Java Programación orientada a objetos Nivel examen Código probado
Ejercicio de examen resuelto en Java: jerarquía de empleados con clase abstracta, constructores encadenados con super y this, y sueldos con super.sueldo().

Una consultora quiere calcular la nómina mensual de su plantilla. Debes modelar el personal con una jerarquía de clases en Java y obtener el sueldo de cada persona mediante herencia, de modo que cada clase añada solo lo que la distingue de su superclase.
La raíz es la clase abstracta Empleado, con dos atributos privados y finales, dni (String) y base (int), y con un único constructor protected Empleado(String dni, int base). Declara el método abstracto String tipo() y el método int sueldo(), que devuelve la base. Su toString() devuelve el DNI, el tipo y el sueldo separados por un espacio.
Las subclases son las siguientes. Programador tiene base 1800 y cobra 25 euros por hora extra; ofrece el constructor público Programador(String dni, int horasExtra) y el constructor protected Programador(String dni, int base, int horasExtra). ProgramadorSenior extiende Programador, tiene base 2400, conserva el pago por horas extra y suma 150 euros por guardia; su constructor es ProgramadorSenior(String dni, int horasExtra, int guardias).
Comercial tiene base 1500 y suma el 8 % de sus ventas, truncado a euros enteros (división entera); su constructor es Comercial(String dni, int ventas). Gerente tiene base fija 3500 y constructor Gerente(String dni). Los valores de tipo() son, respectivamente, Programador, Senior, Comercial y Gerente.
Requisitos obligatorios: todo constructor de subclase debe invocar a un constructor de su superclase con super(...) (o delegar en otro de la misma clase con this(...)); ninguna subclase puede acceder directamente a los atributos de Empleado; y cada sueldo() redefinido debe apoyarse en super.sueldo() en lugar de repetir la fórmula de la superclase.
Entrada (por la entrada estándar): un entero n (0 ≤ n ≤ 1000) y, a continuación, n líneas. Cada línea empieza por una letra de tipo y un DNI sin espacios: P dni horasExtra, S dni horasExtra guardias, C dni ventas o G dni. Todos los números son enteros entre 0 y 1000000 y la entrada es siempre válida.
Salida: guarda todos los empleados en una List<Empleado> en el orden de entrada y, después, recórrela imprimiendo una línea por empleado con el formato de toString(). Termina con una línea TOTAL t, donde t es la suma de todos los sueldos. Si la plantilla está vacía, solo se imprime TOTAL 0.
Entrada de ejemplo
5
P 11111111A 4
S 22222222B 2 3
C 33333333C 1234
G 44444444D
C 55555555E 12500Salida esperada
11111111A Programador 1900
22222222B Senior 2900
33333333C Comercial 1598
44444444D Gerente 3500
55555555E Comercial 2500
TOTAL 12398- Clase abstracta Empleado con atributos privados y finales, constructor protected y método abstracto tipo()
- 0,75
- Constructores encadenados correctamente: super(...) como primera sentencia, this(...) en Programador y paso de la base desde ProgramadorSenior
- 1,0
- Redefinición de sueldo() reutilizando super.sueldo(), sin duplicar fórmulas, y recorrido polimórfico de la plantilla
- 0,5
- Lectura de la entrada y salida con el formato exacto, incluida la plantilla vacía
- 0,25
Pistas
Pista 1 · ¿Dónde vive cada dato?
El DNI y la base son comunes a todos, así que pertenecen a Empleado. Las horas extra solo tienen sentido en Programador (y, por herencia, en ProgramadorSenior), las guardias solo en el senior y las ventas solo en Comercial. Si un atributo aparece repetido en dos clases hermanas, probablemente esté en el sitio equivocado.
Pista 2 · ¿Cómo le llega al senior una base distinta?
ProgramadorSenior no puede asignar base porque es privada en Empleado. Tiene que pasarla hacia arriba: necesita un constructor de Programador que reciba la base como parámetro. Para no duplicar código, el constructor público de Programador puede delegar en ese otro con this(...), pasando 1800.
Pista 3 · ¿Cómo se suma sin repetir fórmulas?
Piensa en el sueldo como capas: Empleado aporta la base, Programador añade las horas extra y ProgramadorSenior añade las guardias. Cada capa llama a super.sueldo() y suma su parte. Así el senior hereda el cálculo de horas extra sin escribirlo de nuevo.
Solución
Solución explicada
La clave del ejercicio es repartir bien las responsabilidades: cada clase guarda solo sus propios datos, recibe por su constructor lo que necesita la superclase y lo entrega con super(...). Las cinco clases se escriben como clases anidadas estáticas de Main para que todo quepa en un único fichero; en un proyecto real irían en ficheros separados dentro de un paquete, pero el diseño es idéntico.
El programa lee todos los tokens de la entrada, construye cada empleado con un switch sobre la letra de tipo, los guarda en la lista y la recorre después tratando todos los objetos como Empleado. Si quieres repasar a fondo el despacho dinámico que hace funcionar ese recorrido, tienes un ejercicio complementario en JAVA: Ejercicio sobre polimorfismo.
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class Main {
static abstract class Empleado {
private final String dni;
private final int base;
protected Empleado(String dni, int base) {
this.dni = dni;
this.base = base;
}
public String getDni() {
return dni;
}
public int getBase() {
return base;
}
public abstract String tipo();
public int sueldo() {
return base;
}
@Override
public String toString() {
return dni + " " + tipo() + " " + sueldo();
}
}
static class Programador extends Empleado {
private static final int BASE_PROGRAMADOR = 1800;
private static final int PRECIO_HORA = 25;
private final int horasExtra;
public Programador(String dni, int horasExtra) {
this(dni, BASE_PROGRAMADOR, horasExtra);
}
protected Programador(String dni, int base, int horasExtra) {
super(dni, base);
this.horasExtra = horasExtra;
}
@Override
public String tipo() {
return "Programador";
}
@Override
public int sueldo() {
return super.sueldo() + PRECIO_HORA * horasExtra;
}
}
static class ProgramadorSenior extends Programador {
private static final int BASE_SENIOR = 2400;
private static final int PLUS_GUARDIA = 150;
private final int guardias;
public ProgramadorSenior(String dni, int horasExtra, int guardias) {
super(dni, BASE_SENIOR, horasExtra);
this.guardias = guardias;
}
@Override
public String tipo() {
return "Senior";
}
@Override
public int sueldo() {
return super.sueldo() + PLUS_GUARDIA * guardias;
}
}
static class Comercial extends Empleado {
private static final int BASE_COMERCIAL = 1500;
private static final int PORCENTAJE = 8;
private final int ventas;
public Comercial(String dni, int ventas) {
super(dni, BASE_COMERCIAL);
this.ventas = ventas;
}
@Override
public String tipo() {
return "Comercial";
}
@Override
public int sueldo() {
return super.sueldo() + ventas * PORCENTAJE / 100;
}
}
static class Gerente extends Empleado {
private static final int BASE_GERENTE = 3500;
public Gerente(String dni) {
super(dni, BASE_GERENTE);
}
@Override
public String tipo() {
return "Gerente";
}
}
public static void main(String[] args) throws IOException {
String entrada = new String(System.in.readAllBytes()).trim();
String[] tk = entrada.isEmpty() ? new String[0] : entrada.split("\\s+");
int p = 0;
int n = tk.length == 0 ? 0 : Integer.parseInt(tk[p++]);
List<Empleado> plantilla = new ArrayList<>();
for (int i = 0; i < n; i++) {
String tipo = tk[p++];
String dni = tk[p++];
Empleado nuevo = switch (tipo) {
case "P" -> new Programador(dni, Integer.parseInt(tk[p++]));
case "S" -> {
int horas = Integer.parseInt(tk[p++]);
int guardias = Integer.parseInt(tk[p++]);
yield new ProgramadorSenior(dni, horas, guardias);
}
case "C" -> new Comercial(dni, Integer.parseInt(tk[p++]));
case "G" -> new Gerente(dni);
default -> throw new IllegalArgumentException("Tipo desconocido: " + tipo);
};
plantilla.add(nuevo);
}
StringBuilder salida = new StringBuilder();
long total = 0;
for (Empleado e : plantilla) {
salida.append(e).append('\n');
total += e.sueldo();
}
salida.append("TOTAL ").append(total).append('\n');
System.out.print(salida);
}
}Empleado es abstracta porque no existe un empleado genérico: todo objeto real es de alguna subclase. Sus atributos son private final, así que nadie fuera de la clase puede modificarlos y solo se asignan una vez, en el constructor. Ese constructor es protected: no tiene sentido llamarlo desde fuera de la jerarquía, pero las subclases lo necesitan obligatoriamente, porque es el único y Java no generará uno sin parámetros.
En Programador hay dos constructores. El público fija la base llamando a this(dni, BASE_PROGRAMADOR, horasExtra); el protegido recibe la base desde fuera, la entrega con super(dni, base) y después asigna horasExtra. Observa el orden: la llamada a super o a this tiene que ser la primera sentencia del constructor, y por eso un mismo constructor nunca puede contener ambas.
ProgramadorSenior usa ese constructor protegido con super(dni, BASE_SENIOR, horasExtra). Cuando se crea un senior, la cadena se ejecuta de arriba abajo: primero se inicializan dni y base en Empleado, luego horasExtra en Programador y por último guardias. Sin el constructor de tres parámetros, el senior no tendría forma de cambiar la base, porque es privada.
Los métodos sueldo() forman capas. En el caso de ejemplo, el senior 22222222B con 2 horas y 3 guardias obtiene 2400 de Empleado, más 50 de las horas extra en Programador, más 450 de las guardias: 2900. El cálculo de horas extra se escribe una sola vez y el senior lo reutiliza mediante super.sueldo(), que invoca la versión de su superclase directa.
Comercial calcula ventas * PORCENTAJE / 100 en aritmética entera, multiplicando antes de dividir. Con 1234 de ventas da 98 (no 98,72) y el sueldo es 1598; con 12500 da 1000 y el sueldo es 2500. El caso de prueba de límites incluye 99 de ventas, que aporta 7, y el máximo de 1000000, que aporta 80000. Como la multiplicación llega como mucho a 8000000, cabe holgadamente en un int.
Gerente no redefine sueldo(): hereda el de Empleado y devuelve la base. En main, la lista es de tipo List<Empleado> y el bucle final no pregunta nunca de qué tipo es cada objeto. salida.append(e) llama a toString(), que a su vez llama a tipo() y sueldo(), y la máquina virtual elige en tiempo de ejecución la versión de la clase real. El total se acumula en un long por prudencia.
La lectura trata el caso límite de la plantilla vacía: con 0 el bucle no se ejecuta y solo se imprime TOTAL 0, como comprueba el segundo test. El tercero, con un único senior sin horas ni guardias, verifica que el sueldo se queda exactamente en la base de 2400 y que, por tanto, la base del senior no se ha mezclado con la de 1800 de Programador.
Casos de prueba
| Caso | Entrada | Salida esperada | Salida real | Resultado |
|---|---|---|---|---|
| Ejemplo del enunciado | 5
P 11111111A 4
S 22222222B 2 3
C 33333333C 1234
G 44444444D
C 55555555E 12500 | 11111111A Programador 1900
22222222B Senior 2900
33333333C Comercial 1598
44444444D Gerente 3500
55555555E Comercial 2500
TOTAL 12398 | 11111111A Programador 1900
22222222B Senior 2900
33333333C Comercial 1598
44444444D Gerente 3500
55555555E Comercial 2500
TOTAL 12398 | OK |
| Plantilla vacía | 0 | TOTAL 0 | TOTAL 0 | OK |
| Un solo senior sin extras | 1
S 99999999Z 0 0 | 99999999Z Senior 2400
TOTAL 2400 | 99999999Z Senior 2400
TOTAL 2400 | OK |
| Truncado y valores límite | 3
C 10000000A 99
C 10000000B 1000000
P 10000000C 0 | 10000000A Comercial 1507
10000000B Comercial 81500
10000000C Programador 1800
TOTAL 84807 | 10000000A Comercial 1507
10000000B Comercial 81500
10000000C Programador 1800
TOTAL 84807 | OK |
| Senior frente a programador | 2
S 1A 10 4
P 2B 10 | 1A Senior 3250
2B Programador 2050
TOTAL 5300 | 1A Senior 3250
2B Programador 2050
TOTAL 5300 | OK |
Salidas reales: código compilado con Java 21.0.12.1 (Temurin) y ejecutado en un contenedor aislado el 8 de octubre de 2026.
Complejidad
Tiempo: Θ(n + L), donde n es el número de empleados y L la longitud de la entrada. Leer y trocear la entrada es lineal en L; crear cada objeto cuesta tiempo constante, y en el recorrido final cada llamada a sueldo() recorre como mucho tres niveles de la jerarquía, que es una constante fija que no depende de n.
Memoria: Θ(n + L). La lista guarda n objetos de tamaño constante, el array de tokens ocupa memoria proporcional a la entrada y el StringBuilder acumula n líneas de longitud acotada antes de imprimirlas de una vez. Es el coste que impone el enunciado, que pide guardar primero toda la plantilla y recorrerla después; con n ≤ 1000 es despreciable.
Errores típicos
- Escribir un constructor de subclase sin llamar a
super(...)cuandoEmpleadono tiene constructor sin parámetros: Java inserta unsuper()implícito y el código no compila. - Poner
this.horasExtra = horasExtra;antes desuper(dni, base), o intentar usarthis(...)ysuper(...)en el mismo constructor: ambas llamadas deben ser la primera sentencia. - Declarar
basecomoprotectedo hacerla no final para queProgramadorSeniorla sobrescriba después de construirse, en lugar de pasarla por la cadena de constructores. - Repetir en
ProgramadorSenior.sueldo()la fórmula completa de las horas extra en vez de llamar asuper.sueldo(); si cambia el precio de la hora, habrá que tocar dos clases. - Calcular la comisión como
ventas / 100 * 8: al dividir primero se pierde precisión y con 1234 de ventas sale 96 en lugar de 98. - Recorrer la lista con cadenas de
instanceofpara decidir qué imprimir, lo que anula la ventaja de tenertipo()ysueldo()redefinidos en cada clase.
Variantes
Añadir un becario que hereda de Programador
Se pide una clase Becario con base 900 que cobra las horas extra a la mitad de precio. La forma limpia es añadir a Programador un constructor protegido que reciba también el precio por hora y guardarlo en un atributo final; el constructor público y el del senior delegan pasando 25, y el becario llama a super(dni, 900, horasExtra, 12). Becario solo redefine tipo(), porque la fórmula de sueldo() no cambia.
Listar la plantilla ordenada por sueldo
Si se pide imprimir de mayor a menor sueldo y, a igual sueldo, por DNI, basta con ordenar la lista antes del bucle: plantilla.sort(Comparator.comparingInt(Empleado::sueldo).reversed().thenComparing(Empleado::getDni)). El comparador trabaja con el tipo Empleado y funciona para todas las subclases gracias al mismo despacho dinámico que usa toString(). El coste pasa a Θ(n log n).
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



Comentarios
Publicar un comentario