Semestre 02, 2026
Una interfaz es un tipo que declara un conjunto de métodos sin implementarlos. Funciona como un contrato, y toda clase que la implementa se compromete a definir esos métodos.
public interface IVolador {
void volar();
}
Clases de ramas distintas de la jerarquía pueden implementar la misma interfaz y usarse a través del mismo tipo.
La lección anterior cerró con el límite de la herencia. Gengar es de tipo fantasma y veneno, y Java no permite que una clase extienda dos superclases.
class Gengar extends Pokemon implements IFantasma, IVeneno { }
fantasma + veneno
dragón + volador
public abstract 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 abstract void atacar();
public void mostrarFicha() {
System.out.println(this.nombre + " | Nv. " + this.nivel
+ " | HP " + this.hp);
}
}
Se extenderá la Pokédex con Pokémon de dos tipos. Dragonite es de tipo dragón y volador, y Gyarados es de tipo agua y volador. El juego necesita un movimiento de vuelo que funcione con cualquier Pokémon capaz de volar.
extiende a Pokemon
extiende a PokemonAgua
Dragonite y Gyarados pertenecen a ramas distintas de la jerarquía, pero ambos vuelan. Esa capacidad común se modela con la interfaz IVolador.
public interface IVolador {
void volar();
}
public interface IDragon {
void usarDragoaliento();
}
classDiagram
class Pokemon {
<<abstract>>
#String nombre
#int nivel
#int hp
+atacar() void
+mostrarFicha() void
}
class PokemonElectrico {
+atacar() void
}
class PokemonAgua {
+atacar() void
}
class Gyarados {
+volar() void
}
class Dragonite {
+atacar() void
+usarDragoaliento() void
+volar() void
}
class IVolador {
<<interface>>
+volar() void
}
class IDragon {
<<interface>>
+usarDragoaliento() void
}
Pokemon <|-- PokemonElectrico
Pokemon <|-- PokemonAgua
Pokemon <|-- Dragonite
PokemonAgua <|-- Gyarados
IVolador <|.. Gyarados
IVolador <|.. Dragonite
IDragon <|.. Dragonite
public class Dragonite extends Pokemon
implements IDragon, IVolador {
public Dragonite(String nombre, int nivel, int hp) {
super(nombre, nivel, hp);
}
@Override
public void atacar() {
System.out.println(this.nombre + " usa Hiperrayo.");
}
@Override
public void usarDragoaliento() {
System.out.println(this.nombre + " usa Dragoaliento.");
}
@Override
public void volar() {
System.out.println(this.nombre + " vuela sobre el océano.");
}
}
public class Gyarados extends PokemonAgua implements IVolador {
public Gyarados(String nombre, int nivel, int hp, int presion) {
super(nombre, nivel, hp, presion);
}
@Override
public void volar() {
System.out.println(this.nombre + " se eleva sobre las olas.");
}
}
Gyarados hereda atacar y bucear de PokemonAgua y solo agrega volar. Un Gyarados es un PokemonAgua y además cumple el contrato IVolador.
ArrayList<IVolador> voladores =
new ArrayList<>();
voladores.add(new Dragonite(
"Dragonite", 55, 91));
voladores.add(new Gyarados(
"Gyarados", 40, 95, 120));
for (IVolador v : voladores) {
v.volar();
}
// no compila, Pikachu no vuela
voladores.add(new PokemonElectrico(
"Pikachu", 12, 35, 90));
Dragonite vuela sobre el océano.
Gyarados se eleva sobre las olas.
El recorrido no contiene un solo if. Un Pokémon nuevo que implemente IVolador entra a la lista sin modificarlo.
public static void usarVuelo(IVolador v, String destino) {
v.volar();
System.out.println("Llega a " + destino + ".");
}
usarVuelo(dragonite, "Ciudad Celeste");
usarVuelo(gyarados, "Isla Canela");
usarVuelo(pikachu, "Pueblo Paleta"); // no compila
Un Charizard que implemente IVolador funciona con usarVuelo sin modificar el método.
| Aspecto | Clase abstracta | Interfaz |
|---|---|---|
| Palabra clave | extends | implements |
| Cuántas por clase | una | varias |
| Atributos de instancia | sí | no, solo constantes |
| Constructores | sí | no |
| Métodos con cuerpo | sí | solo default y static |
| Relación que expresa | es-un | puede-hacer |
Pokemon reúne el estado y el comportamiento que comparte toda la jerarquía, como el nombre, el nivel y la ficha.
IVolador declara una capacidad que tienen solo algunos Pokémon, repartidos en ramas distintas de la jerarquía.
Java define interfaces que sus propias clases y métodos utilizan de forma polimórfica. Una clase nueva que las implementa se integra con esas herramientas sin modificarlas.
Se solicita imprimir los Pokémon capturados ordenados de menor a mayor nivel.
Collections.sort(capturados); // no compila
Collections.sort no conoce la clase Pokemon ni sabe qué significa que un Pokémon vaya antes que otro. Solo ordena listas cuyos elementos implementen Comparable.
public interface Comparable<T> {
int compareTo(T otro);
}
public abstract class Pokemon implements Comparable<Pokemon> {
protected String nombre;
protected int nivel;
protected int hp;
// constructor, atacar y mostrarFicha sin cambios
public int getNivel() {
return this.nivel;
}
@Override
public int compareTo(Pokemon otro) {
return Integer.compare(this.nivel, otro.getNivel());
}
}
classDiagram
class Comparable~Pokemon~ {
<<interface>>
+compareTo(Pokemon otro) int
}
class Pokemon {
<<abstract>>
#String nombre
#int nivel
#int hp
+getNivel() int
+compareTo(Pokemon otro) int
}
class PokemonElectrico
class PokemonAgua
class Dragonite
Comparable <|.. Pokemon
Pokemon <|-- PokemonElectrico
Pokemon <|-- PokemonAgua
Pokemon <|-- Dragonite
ArrayList<Pokemon> capturados = new ArrayList<>();
capturados.add(new PokemonElectrico("Jolteon", 30, 65, 400));
capturados.add(new PokemonElectrico("Pikachu", 12, 35, 90));
capturados.add(new Dragonite("Dragonite", 55, 91));
capturados.add(new PokemonAgua("Squirtle", 10, 44, 60));
Collections.sort(capturados);
for (Pokemon p : capturados) {
p.mostrarFicha();
}
Squirtle | Nv. 10 | HP 44
Pikachu | Nv. 12 | HP 35
Jolteon | Nv. 30 | HP 65
Dragonite | Nv. 55 | HP 91
Collections.sort se escribió años antes que la clase Pokemon. Invoca compareTo a través de una referencia Comparable, y se ejecuta la versión que Pokemon definió y que todas sus subclases heredaron.
public String getNombre() {
return this.nombre;
}
@Override
public int compareTo(Pokemon otro) {
int porNivel = Integer.compare(this.nivel,
otro.getNivel());
if (porNivel != 0) {
return porNivel;
}
return this.nombre.compareTo(otro.getNombre());
}
El nombre se consulta solo cuando el nivel empata, y reutiliza el compareTo de String.
@Override
public int compareTo(Pokemon otro) {
return this.nivel - otro.getNivel();
}
Con valores cercanos a los límites de int la resta se desborda, cambia de signo y el orden queda invertido.
Un peso de 6.0 kg contra uno de 6.4 kg da -0.4, y la conversión a int lo trunca a cero. Los dos Pokémon se consideran iguales.
Un equipo Pokémon admite como máximo seis integrantes. Se requiere una clase Equipo que los almacene en un arreglo privado de tamaño fijo, y el código cliente necesita recorrer el equipo para que cada integrante ataque.
// dentro de Equipo
public Pokemon[] getMiembros() {
return this.miembros;
}
// en el código cliente, con dos integrantes
Pokemon[] miembros = equipo.getMiembros();
for (int i = 0; i < miembros.length; i++) {
miembros[i].atacar(); // falla en i = 2
}
miembros[0] = null; // altera el equipo
| Estructura | Recorrido sin iterador |
|---|---|
| Arreglo | índice hasta la cantidad real |
| ArrayList | get(i) hasta size() |
| HashSet | no tiene índices |
| Equipo | depende de su implementación interna |
Sin un mecanismo común, el código que recorre una colección queda atado a la forma en que esa colección guarda sus datos.
Un iterador es un objeto que recorre una colección y entrega sus elementos uno a la vez, sin revelar cómo están almacenados.
El for mejorado usa ese protocolo por dentro, y por eso acepta cualquier objeto que implemente Iterable.
for (Pokemon p : equipo) { // no compila
p.atacar();
}
Equipo todavía no implementa Iterable. El for mejorado solo acepta arreglos y objetos que implementen esa interfaz.
public interface Iterable<T> {
Iterator<T> iterator();
}
public interface Iterator<T> {
boolean hasNext();
T next();
}
import java.util.Iterator;
public class Equipo implements Iterable<Pokemon> {
private Pokemon[] miembros;
private int cantidad;
public Equipo() {
this.miembros = new Pokemon[6];
this.cantidad = 0;
}
public void agregar(Pokemon p) {
this.miembros[this.cantidad] = p;
this.cantidad++;
}
@Override
public Iterator<Pokemon> iterator() {
return new IteradorEquipo(this.miembros, this.cantidad);
}
}
public class IteradorEquipo implements Iterator<Pokemon> {
private Pokemon[] miembros;
private int cantidad;
private int actual;
public IteradorEquipo(Pokemon[] miembros, int cantidad) {
this.miembros = miembros;
this.cantidad = cantidad;
this.actual = 0;
}
@Override
public boolean hasNext() {
return this.actual < this.cantidad;
}
@Override
public Pokemon next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
Pokemon p = this.miembros[this.actual];
this.actual++;
return p;
}
}
classDiagram
direction LR
class Iterable~Pokemon~ {
<<interface>>
+iterator() Iterator
}
class Iterator~Pokemon~ {
<<interface>>
+hasNext() boolean
+next() Pokemon
}
class Equipo {
-Pokemon[] miembros
-int cantidad
+agregar(Pokemon p) void
+iterator() Iterator
}
class IteradorEquipo {
-Pokemon[] miembros
-int cantidad
-int actual
+hasNext() boolean
+next() Pokemon
}
Iterable <|.. Equipo
Iterator <|.. IteradorEquipo
Equipo ..> IteradorEquipo : crea
Equipo equipo = new Equipo();
equipo.agregar(new PokemonElectrico("Pikachu", 12, 35, 90));
equipo.agregar(new Dragonite("Dragonite", 55, 91));
for (Pokemon p : equipo) {
p.atacar();
}
El compilador traduce el for mejorado a llamadas sobre las interfaces Iterable e Iterator.
Iterator<Pokemon> it = equipo.iterator();
while (it.hasNext()) {
Pokemon p = it.next();
p.atacar();
}
Pikachu lanza 90 V.
Dragonite usa Hiperrayo.
El for mejorado depende solo de Iterable e Iterator. Funciona igual con ArrayList, HashSet o Equipo, porque las tres implementan la misma interfaz.
public static double nivelPromedio(Iterable<Pokemon> grupo) {
int suma = 0;
int n = 0;
for (Pokemon p : grupo) {
suma += p.getNivel();
n++;
}
return (double) suma / n;
}
nivelPromedio(equipo); // un Equipo
nivelPromedio(capturados); // un ArrayList<Pokemon>
Se solicita liberar a los Pokémon capturados con nivel menor que 15. La lista contiene a Pikachu, Jolteon, Squirtle y Dragonite, en ese orden.
for (Pokemon p : capturados) {
if (p.getNivel() < 15) {
capturados.remove(p);
}
}
El for mejorado usa un iterador por debajo, y la lista se modificó sin pasar por él. El iterador detecta el cambio en la siguiente llamada a next.
Iterator<Pokemon> it = capturados.iterator();
while (it.hasNext()) {
Pokemon p = it.next();
if (p.getNivel() < 15) {
it.remove();
}
}
Elimina el último elemento entregado por next y mantiene al iterador sincronizado con la lista. En la lista quedan Jolteon y Dragonite.