Semestre 02, 2026
La herencia trae un modificador nuevo: protected, a medio camino entre private y public.
private: solo dentro de la misma clase.protected: la clase y sus subclases.public: cualquier clase del programa.
public class Persona {
private String dpi; // solo dentro de Persona
protected String nombre; // Persona y sus subclases
public int edad; // cualquier clase
public void saludar() {
System.out.println("Hola, soy " + nombre);
}
}
Lo que la subclase necesite tocar directamente va protected; lo demás se queda private.
Una subclase hereda atributos y métodos de una superclase con extends.
public class Persona {
protected String nombre;
public void saludar() {
System.out.println("Hola, soy " + nombre);
}
}
public class Estudiante extends Persona {
protected String carnet;
}
Toda clase hereda de Object. Ejemplo: Object → Persona → Estudiante.
toString(): representación en textoequals(): comparación de igualdadgetClass(): tipo en ejecuciónUna subclase redefine un método heredado con la misma firma. Se resuelve en tiempo de ejecución.
class Estudiante extends Persona {
@Override
public void saludar() {
System.out.println("Hola, soy estudiante");
}
}
Refiere a la superclase: invoca su constructor o sus métodos sobrescritos. Empezamos por la superclase.
public class Persona {
protected String nombre;
public Persona(String nombre) {
this.nombre = nombre;
}
public void saludar() {
System.out.println("Hola, soy " + nombre);
}
}
public class Estudiante extends Persona {
protected String carnet;
public Estudiante(String nombre, String carnet) {
super(nombre);
this.carnet = carnet;
}
@Override
public void saludar() {
super.saludar();
System.out.println("Estudio con el carnet " + carnet);
}
}
Va siempre en la primera línea del constructor. Persona se encarga del nombre y Estudiante solo agrega el carnet.
public class Estudiante extends Persona {
protected String carnet;
public Estudiante(String nombre, String carnet) {
super(nombre);
this.carnet = carnet;
}
@Override
public void saludar() {
super.saludar();
System.out.println("Estudio con el carnet " + carnet);
}
}
Ejecuta el saludo de Persona y después agrega lo suyo. Sin esta línea, el saludo de la superclase se perdería.
public class Main {
public static void main(String[] args) {
Estudiante e = new Estudiante("Ana", "24001");
e.saludar();
}
}
Hola, soy Ana
Estudio con el carnet 24001
Primero corre la línea de Persona y después la de Estudiante, porque super.saludar() va antes del println.
Una referencia de la superclase puede apuntar a un objeto de cualquier subclase.
Persona p1 = new Estudiante();
Persona p2 = new Profesor();
p1.saludar(); // Hola, soy estudiante
p2.saludar(); // Buenos días, soy profesor
Persona p = new Estudiante(); // upcasting implícito
Estudiante e = (Estudiante) p; // downcasting explícito
ClassCastException.
ArrayList<Persona> personas = new ArrayList<>();
personas.add(new Estudiante());
personas.add(new Profesor());
for (Persona p : personas) {
p.saludar(); // cada objeto ejecuta su versión
}
No se instancian; pueden tener métodos abstractos que las subclases deben implementar (un contrato).
abstract class Animal {
public abstract void hacerSonido();
}
class Perro extends Animal {
@Override
public void hacerSonido() {
System.out.println("Guau");
}
}
final class: no puede extenderse.final en método: no puede sobrescribirse.Vamos a construir un equipo Pokémon aplicando todo lo anterior, paso a paso.
Cada Pokémon como clase independiente repite los mismos datos una y otra vez.
public class Pikachu {
String nombre;
int nivel;
int hp;
public void mostrarFicha() { /* ... */ }
}
public class Squirtle {
String nombre; // otra vez
int nivel; // otra vez
int hp; // otra vez
public void mostrarFicha() { /* ... */ }
}
public class Pokemon {
protected String nombre;
protected int nivel;
protected int hp;
public Pokemon(String nombre, int nivel, int hp) {
this.nombre = nombre;
this.nivel = nivel;
this.hp = hp;
}
public void atacar() {
System.out.println(nombre + " usa Placaje.");
}
public void mostrarFicha() {
System.out.println(nombre + " | Nv. " + nivel + " | HP " + hp);
}
}
public class Pokemon {
protected String nombre;
protected int nivel;
protected int hp;
public Pokemon(String nombre, int nivel, int hp) {
this.nombre = nombre;
this.nivel = nivel;
this.hp = hp;
}
public void atacar() {
System.out.println(nombre + " usa Placaje.");
}
public void mostrarFicha() {
System.out.println(nombre + " | Nv. " + nivel + " | HP " + hp);
}
}
Lo que todo Pokémon tiene sin importar su tipo. Se declaran protected para que las subclases puedan usarlos directamente, sin abrirlos al resto del programa.
public class Pokemon {
protected String nombre;
protected int nivel;
protected int hp;
public Pokemon(String nombre, int nivel, int hp) {
this.nombre = nombre;
this.nivel = nivel;
this.hp = hp;
}
public void atacar() {
System.out.println(nombre + " usa Placaje.");
}
public void mostrarFicha() {
System.out.println(nombre + " | Nv. " + nivel + " | HP " + hp);
}
}
El constructor de la superclase inicializa los datos comunes. Las subclases lo van a reutilizar en vez de volver a escribirlo.
public class Pokemon {
protected String nombre;
protected int nivel;
protected int hp;
public Pokemon(String nombre, int nivel, int hp) {
this.nombre = nombre;
this.nivel = nivel;
this.hp = hp;
}
public void atacar() {
System.out.println(nombre + " usa Placaje.");
}
public void mostrarFicha() {
System.out.println(nombre + " | Nv. " + nivel + " | HP " + hp);
}
}
Un ataque genérico que sirve para cualquier Pokémon. Cada tipo lo va a sobrescribir con su propio movimiento.
public class Pokemon {
protected String nombre;
protected int nivel;
protected int hp;
public Pokemon(String nombre, int nivel, int hp) {
this.nombre = nombre;
this.nivel = nivel;
this.hp = hp;
}
public void atacar() {
System.out.println(nombre + " usa Placaje.");
}
public void mostrarFicha() {
System.out.println(nombre + " | Nv. " + nivel + " | HP " + hp);
}
}
Este método se hereda tal cual y ninguna subclase necesita reescribirlo. Eso es reutilización de código.
public class PokemonElectrico extends Pokemon {
private int voltaje;
public PokemonElectrico(String nombre, int nivel,
int hp, int voltaje) {
super(nombre, nivel, hp);
this.voltaje = voltaje;
}
@Override
public void atacar() {
System.out.println(nombre + " lanza " + voltaje + " V.");
}
}
public class PokemonElectrico extends Pokemon {
private int voltaje;
public PokemonElectrico(String nombre, int nivel,
int hp, int voltaje) {
super(nombre, nivel, hp);
this.voltaje = voltaje;
}
@Override
public void atacar() {
System.out.println(nombre + " lanza " + voltaje + " V.");
}
}
Hereda nombre, nivel y hp sin escribirlos otra vez, y agrega lo suyo. El voltaje solo tiene sentido para un Pokémon eléctrico, así que vive aquí.
public class PokemonElectrico extends Pokemon {
private int voltaje;
public PokemonElectrico(String nombre, int nivel,
int hp, int voltaje) {
super(nombre, nivel, hp);
this.voltaje = voltaje;
}
@Override
public void atacar() {
System.out.println(nombre + " lanza " + voltaje + " V.");
}
}
La primera línea del constructor delega en el constructor de Pokemon, que llena los tres atributos comunes. Después la subclase solo se encarga del voltaje.
public class PokemonElectrico extends Pokemon {
private int voltaje;
public PokemonElectrico(String nombre, int nivel,
int hp, int voltaje) {
super(nombre, nivel, hp);
this.voltaje = voltaje;
}
@Override
public void atacar() {
System.out.println(nombre + " lanza " + voltaje + " V.");
}
}
Misma firma que en la superclase, comportamiento distinto. Pikachu ya no usa Placaje, lanza su descarga eléctrica.
public class PokemonAgua extends Pokemon {
private int presion;
public PokemonAgua(String nombre, int nivel, int hp, int presion) {
super(nombre, nivel, hp);
this.presion = presion;
}
@Override
public void atacar() {
System.out.println(nombre + " ataca a " + presion + " psi.");
}
public void bucear() {
System.out.println(nombre + " se sumerge y esquiva el ataque.");
}
}
public class PokemonAgua extends Pokemon {
private int presion;
public PokemonAgua(String nombre, int nivel, int hp, int presion) {
super(nombre, nivel, hp);
this.presion = presion;
}
@Override
public void atacar() {
System.out.println(nombre + " ataca a " + presion + " psi.");
}
public void bucear() {
System.out.println(nombre + " se sumerge y esquiva el ataque.");
}
}
La misma estructura que el Pokémon eléctrico, con otro ataque. La superclase no cambió ni una línea para que esto funcione.
public class PokemonAgua extends Pokemon {
private int presion;
public PokemonAgua(String nombre, int nivel, int hp, int presion) {
super(nombre, nivel, hp);
this.presion = presion;
}
@Override
public void atacar() {
System.out.println(nombre + " ataca a " + presion + " psi.");
}
public void bucear() {
System.out.println(nombre + " se sumerge y esquiva el ataque.");
}
}
Un método que solo existe en esta subclase. Una variable declarada como Pokemon no lo puede invocar, aunque el objeto sea un PokemonAgua.
Un solo tipo general arriba, especializaciones abajo. Agregar un tipo nuevo no toca nada de lo anterior.
ArrayList<Pokemon> equipo = new ArrayList<>();
equipo.add(new PokemonElectrico("Pikachu", 12, 35, 90));
equipo.add(new PokemonAgua("Squirtle", 10, 44, 60));
equipo.add(new PokemonElectrico("Jolteon", 30, 65, 400));
for (Pokemon p : equipo) {
p.mostrarFicha();
p.atacar();
}
ArrayList<Pokemon> equipo = new ArrayList<>();
equipo.add(new PokemonElectrico("Pikachu", 12, 35, 90));
equipo.add(new PokemonAgua("Squirtle", 10, 44, 60));
equipo.add(new PokemonElectrico("Jolteon", 30, 65, 400));
for (Pokemon p : equipo) {
p.mostrarFicha();
p.atacar();
}
La lista es de Pokemon, pero guarda objetos de cualquier subclase. Eso es upcasting, y ocurre solo.
ArrayList<Pokemon> equipo = new ArrayList<>();
equipo.add(new PokemonElectrico("Pikachu", 12, 35, 90));
equipo.add(new PokemonAgua("Squirtle", 10, 44, 60));
equipo.add(new PokemonElectrico("Jolteon", 30, 65, 400));
for (Pokemon p : equipo) {
p.mostrarFicha();
p.atacar();
}
Una sola llamada, tres comportamientos distintos. Java decide en tiempo de ejecución cuál versión de atacar corresponde a cada objeto.
Pikachu | Nv. 12 | HP 35
Pikachu lanza 90 V.
Squirtle | Nv. 10 | HP 44
Squirtle ataca a 60 psi.
Jolteon | Nv. 30 | HP 65
Jolteon lanza 400 V.
mostrarFicha se heredó sin cambios y atacar cambió en cada subclase.
Un Pokémon genérico no existe en el juego. Nadie debería poder crear uno.
public abstract class Pokemon {
protected String nombre;
protected int nivel;
protected int hp;
public Pokemon(String nombre, int nivel, int hp) { /* ... */ }
public abstract void atacar(); // cada tipo define el suyo
public void mostrarFicha() { /* se hereda tal cual */ }
}
new Pokemon(...) deja de compilar.Gengar es de fantasma y veneno. Dragonite es de dragón y volador. ¿De qué clase heredan?
fantasma + veneno
dragón + volador
// no compila: Java no permite heredar de dos clases
class Gengar extends PokemonFantasma, PokemonVeneno { }
Copiar el método del veneno en cada clase que lo necesite, y volvemos a la duplicación del inicio.
Separar el tipo (herencia) de la habilidad (contrato), y la habilidad se declara en una interfaz.
Cada tipo pasa a ser un contrato independiente, no una clase padre.
interface IFantasma {
void atravesarMuros();
}
interface IVeneno {
void envenenar();
}
class Gengar extends Pokemon implements IFantasma, IVeneno {
@Override
public void atravesarMuros() {
System.out.println(nombre + " atraviesa el muro.");
}
@Override
public void envenenar() {
System.out.println(nombre + " envenena al rival.");
}
}
Gengar hereda de Pokemon, que es lo que todo Pokémon comparte.
Los tipos se suman con implements, y se pueden combinar sin límite.
Dragonite resuelve igual con IDragon e IVolador sobre la misma clase Pokemon.
extends reutiliza los atributos y métodos de la superclase.super(...) llama al constructor de la superclase.@Override especializa un comportamiento heredado.