Comparable y Cloneable en Java — práctica con un ranking, copias de seguridad y árboles ordenados (y la palabra que desapareció del TreeSet)
Mi primer TreeSet con un Comparator por longitud salió así de bien: le metí siete palabras y me devolvió tres. Revisé el código tres veces buscando el fallo. Revisé la lista de palabras. Hasta sospeché de mi teclado. Java no se había equivocado en nada: simplemente yo le había dicho que dos palabras de la misma longitud eran la misma palabra, y él me había hecho caso.
Esa es la gracia de este tema: Comparable, Comparator y Cloneable son tres herramientas pequeñas que hacen exactamente lo que les pides, aunque no sea lo que querías. Y justo por eso se aprenden practicando: cuando algo sale raro, la causa casi siempre es una definición de «igual» o de «copia» que no era la que tenías en la cabeza.
Si ya has leído la teoría de Comparable y Cloneable, este artículo es para aplicarla: un ranking de alumnos con varios criterios de orden, una comparación entre clone() y el constructor de copia, y un paseo por TreeSet y TreeMap con orden propio. Son programas de dificultad media. Cada uno trae el código comentado, la salida y las dudas que suelen surgir.
Tabla de Contenidos
Antes de empezar
Conviene tener claro:
- Que
compareTodevuelve un número negativo, cero o positivo, y que debe comparar sin restas (Integer.compare,Double.compare) y con desempate cuando dos elementos distintos pueden empatar. - Que
Comparablees el orden natural de la clase yComparatores un criterio externo: puedes tener todos los que quieras y encadenarlos conreversed()ythenComparing(). - Que
clone()necesitaimplements Cloneable, serpublicy empezar porsuper.clone(), y que esa copia es superficial: las listas y los objetos mutables siguen compartidos. - Que
TreeSetyTreeMapno usanequals: si el comparador da0, para ellos son el mismo elemento.
Si alguno te suena dudoso, repasa la teoría antes de seguir.
Los tres programas son independientes. Crea una carpeta para cada uno y compílalos por separado: los programas 1 y 2 tienen cada uno su propia clase Alumno, y no son la misma.
Programa 1: ranking de alumnos
Una clase de seis alumnos con nombre, nota y curso. Queremos verlos en orden alfabético (el orden por defecto de una lista de clase), después en un ranking por nota y, por último, agrupados por curso.
La idea
El orden natural del alumno será el alfabético, porque es lo que esperaría cualquiera de una lista de clase: eso va en compareTo. Todo lo demás (ranking por nota, por curso) son criterios que no son «el» orden del alumno, así que van fuera, en Comparators que se pueden combinar como piezas.
Y además, un método genérico mayor que solo exige que el tipo sea Comparable: la prueba de que Comparable no sirve solo para ordenar, sino para escribir código que vale para cualquier tipo «comparable».
Antes de programar, piensa en qué cambia entre un criterio y otro: solo el campo por el que se compara. Los Comparator.comparing... están hechos justo para eso.
El código
Alumno.java
public class Alumno implements Comparable<Alumno> {
private final String nombre;
private final double nota;
private final int curso;
public Alumno(String nombre, double nota, int curso) {
this.nombre = nombre;
this.nota = nota;
this.curso = curso;
}
public String getNombre() { return nombre; }
public double getNota() { return nota; }
public int getCurso() { return curso; }
// Orden natural: alfabético por nombre
@Override
public int compareTo(Alumno otro) {
return this.nombre.compareTo(otro.nombre);
}
@Override
public String toString() {
return nombre + " (" + nota + ", " + curso + "º)";
}
}
Ranking.java
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class Ranking {
// Funciona con cualquier tipo que sepa compararse a sí mismo
static <T extends Comparable<T>> T mayor(List<T> lista) {
T mejor = lista.get(0);
for (T elemento : lista) {
if (elemento.compareTo(mejor) > 0) {
mejor = elemento;
}
}
return mejor;
}
public static void main(String[] args) {
List<Alumno> clase = new ArrayList<>(List.of(
new Alumno("Marta", 9.1, 2), new Alumno("Luis", 6.5, 1),
new Alumno("Ana", 8.0, 2), new Alumno("Pablo", 8.0, 1),
new Alumno("Eva", 9.1, 1), new Alumno("Sara", 4.8, 2)));
// 1. Orden natural (compareTo): alfabético
Collections.sort(clase);
System.out.println("Alfabético: " + clase);
// 2. Criterio externo: nota descendente y, si empatan, nombre
Comparator<Alumno> porNota = Comparator
.comparingDouble(Alumno::getNota).reversed()
.thenComparing(Alumno::getNombre);
clase.sort(porNota);
System.out.println("\nRanking:");
for (int i = 0; i < clase.size(); i++) {
System.out.println((i + 1) + ". " + clase.get(i));
}
// 3. Top 3 y extremos con el mismo Comparator
System.out.println("\nTop 3: " + clase.subList(0, 3));
System.out.println("Mejor: " + Collections.min(clase, porNota));
System.out.println("Peor: " + Collections.max(clase, porNota));
// 4. Ranking por curso y, dentro de cada curso, por nota
clase.sort(Comparator.comparingInt(Alumno::getCurso).thenComparing(porNota));
System.out.println("\nPor curso: " + clase);
// 5. Un método genérico que solo exige Comparable
System.out.println("\nÚltimo alfabéticamente: " + mayor(clase));
System.out.println("Número mayor: " + mayor(List.of(3, 17, 8)));
System.out.println("Palabra mayor: " + mayor(List.of("pan", "sol", "luna")));
}
}
Salida
Alfabético: [Ana (8.0, 2º), Eva (9.1, 1º), Luis (6.5, 1º), Marta (9.1, 2º), Pablo (8.0, 1º), Sara (4.8, 2º)] Ranking: 1. Eva (9.1, 1º) 2. Marta (9.1, 2º) 3. Ana (8.0, 2º) 4. Pablo (8.0, 1º) 5. Luis (6.5, 1º) 6. Sara (4.8, 2º) Top 3: [Eva (9.1, 1º), Marta (9.1, 2º), Ana (8.0, 2º)] Mejor: Eva (9.1, 1º) Peor: Sara (4.8, 2º) Por curso: [Eva (9.1, 1º), Pablo (8.0, 1º), Luis (6.5, 1º), Marta (9.1, 2º), Ana (8.0, 2º), Sara (4.8, 2º)] Último alfabéticamente: Sara (4.8, 2º) Número mayor: 17 Palabra mayor: sol
Compruébalo a mano:
- Alfabético: Ana, Eva, Luis, Marta, Pablo, Sara. Las notas no han pintado nada:
compareTosolo mira el nombre. - Ranking: primero las notas más altas. Eva y Marta tienen 9,1 y Eva va antes por orden alfabético; igual Ana y Pablo con 8,0. Luis (6,5) y Sara (4,8) cierran.
- Top 3, mejor y peor: el top 3 son los tres primeros del ranking. Como nuestro comparador pone primero al mejor, el mínimo según ese criterio (
Collections.min) es el mejor alumno, Eva; y el máximo, el peor, Sara. - Por curso: primero los del 1.º (Eva, Pablo, Luis, ordenados por nota) y después los del 2.º (Marta, Ana, Sara).
- Último alfabéticamente: Sara. Y el mismo método
mayorda17con enteros ysolcon palabras.
subList(0, 3) es una vista de la lista, no una copia: si cambiases la lista original, la vista se vería afectada. Para una lista independiente, new ArrayList<>(clase.subList(0, 3)).
Preguntas que te puedes hacer
**¿Por qué el ranking es un Comparator y no cambio compareTo?** Porque compareTo es el orden por defecto de la clase y solo puede haber uno. Si lo cambiaras a «por nota», el listado alfabético dejaría de existir sin escribir otro criterio. Con Comparator conviven todos.
**¿Por qué Collections.min(clase, porNota) devuelve al mejor alumno?** Porque min devuelve el elemento que va antes según el comparador, y nuestro ranking pone antes al de mejor nota. «Mínimo» no significa «peor», significa «primero en el orden que le des».
**¿Qué significa <T extends Comparable<T>>?** Que mayor acepta cualquier tipo T que sepa compararse con otros de su misma clase. Por eso la misma función vale para Alumno, Integer y String, sin duplicar código.
¿Cómo se combinan dos comparadores? comparingInt(Alumno::getCurso).thenComparing(porNota) ordena por curso y, dentro de cada curso, usa el comparador porNota que ya teníamos. Los comparadores se reutilizan como piezas de construcción.
Llamar a mayor con una lista vacía. lista.get(0) lanza IndexOutOfBoundsException. Antes de coger el primer elemento, comprueba que la lista no esté vacía.
Pruébalo tú
- Cambia el ranking para que, en caso de empate de nota, gane el alumno de mayor curso.
- Escribe un
Comparatorque ordene por la longitud del nombre y, si empatan, por nota descendente.
Programa 2: clone() frente al constructor de copia
Un alumno con una lista de notas y una dirección. Queremos copiarlo de tres maneras (copia superficial, clone() profundo y constructor de copia) y ver con qué se queda cada una.
La idea
Una copia hecha con super.clone() solo duplica la caja: la lista de notas y la dirección seguirán siendo las mismas. Para que la copia sea independiente, hay que duplicar a mano lo mutable. Lo vamos a hacer de dos formas y a compararlas, y a ver qué pasa cuando hay herencia (un alumno becado que es un Alumno con un campo más).
La dirección también es Cloneable: así Alumno puede pedirle que se clone a sí misma en lugar de conocer sus campos. La profundidad se decide nivel a nivel.
Antes de leer el código, predice qué pasará en cada bloque de la salida. En el primero, ¿cambiará el original al tocar la copia superficial?
El código
Direccion.java
public class Direccion implements Cloneable {
private String ciudad;
public Direccion(String ciudad) { this.ciudad = ciudad; }
public void setCiudad(String ciudad) { this.ciudad = ciudad; }
@Override
public Direccion clone() {
try {
return (Direccion) super.clone(); // solo hay un String (inmutable): basta con esto
} catch (CloneNotSupportedException e) {
throw new AssertionError(e);
}
}
@Override
public String toString() { return ciudad; }
}
Alumno.java
import java.util.ArrayList;
import java.util.List;
public class Alumno implements Cloneable {
private String nombre;
private List<Double> notas = new ArrayList<>();
private Direccion direccion;
public Alumno(String nombre, String ciudad) {
this.nombre = nombre;
this.direccion = new Direccion(ciudad);
}
// Constructor de copia (copia profunda)
public Alumno(Alumno otro) {
this.nombre = otro.nombre;
this.notas = new ArrayList<>(otro.notas);
this.direccion = otro.direccion.clone();
}
public void anotar(double nota) { notas.add(nota); }
public Direccion getDireccion() { return direccion; }
public List<Double> getNotas() { return notas; }
// clone() superficial: solo lo que hace super.clone()
public Alumno cloneSuperficial() {
try {
return (Alumno) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError(e);
}
}
// clone() profundo: super.clone() y después duplicamos lo mutable
@Override
public Alumno clone() {
Alumno copia = cloneSuperficial();
copia.notas = new ArrayList<>(this.notas);
copia.direccion = this.direccion.clone();
return copia;
}
@Override
public String toString() {
return getClass().getSimpleName() + " " + nombre + " " + notas + " en " + direccion;
}
}
AlumnoBecado.java
public class AlumnoBecado extends Alumno {
private double beca;
public AlumnoBecado(String nombre, String ciudad, double beca) {
super(nombre, ciudad);
this.beca = beca;
}
public AlumnoBecado(AlumnoBecado otro) {
super(otro);
this.beca = otro.beca;
}
@Override
public String toString() {
return super.toString() + " con beca de " + beca;
}
}
Copias.java
public class Copias {
public static void main(String[] args) {
Alumno ana = new Alumno("Ana", "Telde");
ana.anotar(8.0);
// 1. Copia superficial: comparte la lista y la dirección
Alumno sup = ana.cloneSuperficial();
sup.anotar(5.0);
sup.getDireccion().setCiudad("Arucas");
System.out.println("Original: " + ana);
System.out.println("Superficial: " + sup);
// 2. Las dos copias profundas
Alumno porClone = ana.clone();
Alumno porConstructor = new Alumno(ana);
porClone.anotar(10.0);
porConstructor.getDireccion().setCiudad("Gáldar");
System.out.println("\nOriginal: " + ana);
System.out.println("Con clone: " + porClone);
System.out.println("Con ctor: " + porConstructor);
// 3. ¿Qué tipo tiene la copia cuando hay herencia?
Alumno becado = new AlumnoBecado("Luis", "Agüimes", 1500);
Alumno copiaClone = becado.clone();
Alumno copiaCtor = new Alumno(becado);
System.out.println("\nclone(): " + copiaClone);
System.out.println("constructor: " + copiaCtor);
}
}
Salida
Original: Alumno Ana [8.0, 5.0] en Arucas Superficial: Alumno Ana [8.0, 5.0] en Arucas Original: Alumno Ana [8.0, 5.0] en Arucas Con clone: Alumno Ana [8.0, 5.0, 10.0] en Arucas Con ctor: Alumno Ana [8.0, 5.0] en Gáldar clone(): AlumnoBecado Luis [] en Agüimes con beca de 1500.0 constructor: Alumno Luis [] en Agüimes
Compruébalo a mano:
- Superficial:
supcomparte la lista de notas y la dirección conana. Al añadir un 5,0 y mudarse a Arucas, el original también los tiene. El daño queda hecho para el resto del programa. - **
clone()profundo:**porClonetiene su propia lista. Le añadimos un 10,0 y el original ([8.0, 5.0]) no se entera. - Constructor de copia:
porConstructortiene su propia dirección. Se muda a Gáldar y el original sigue en Arucas. - Herencia:
becado.clone()devuelve unAlumnoBecadocon su beca;new Alumno(becado)crea unAlumnonormal y la beca desaparece.
clone() | Constructor de copia | |
|---|---|---|
Necesita Cloneable y try/catch | Sí | No |
| Conserva el tipo real (herencia) | Sí (si usas super.clone()) | No: siempre crea la clase del constructor |
| Copia profunda | A mano, tras super.clone() | A mano, campo a campo |
| Claridad al leerlo | Menos | Más |
Campos final | Cuesta (los copia el super.clone() pero no los puedes reasignar) | Fácil |
Preguntas que te puedes hacer
**¿Por qué Direccion.clone() no copia nada a mano?** Porque solo tiene un String, y los String son inmutables: compartirlos no hace daño. Con super.clone() ya es una copia profunda.
**¿Por qué clone() llama a cloneSuperficial() y no directamente a super.clone()?** Podría llamarlo directo. Lo separé para poder demostrar en el mismo programa la copia superficial y la profunda con la misma clase. En código real tendrías solo el clone() profundo.
¿Por qué el constructor de copia pierde la beca? Porque new Alumno(becado) ejecuta el constructor de Alumno, que no sabe nada de becas. El tipo del objeto creado lo decide el new, no el tipo de la variable que le pasas.
¿Cuál uso? No hay un ganador absoluto. Con jerarquías de clases y copias que deben ser del mismo tipo, clone() (o un método copiar() que cada subclase redefine). Con clases simples y ganas de claridad, el constructor de copia.
Copiar una lista con new ArrayList<>(otra) y creer que ya es una copia profunda. La lista es nueva, pero sus elementos siguen siendo los mismos objetos. Con Double (inmutable) no importa; con objetos tuyos mutables, sí.
Pruébalo tú
- Quita
implements CloneabledeAlumnoy mira qué excepción aparece al ejecutarCopias. - Haz que
Direcciontenga además una lista de teléfonos. ¿Qué tienes que cambiar para queAlumno.clone()siga siendo profundo?
Programa 3: TreeSet y TreeMap con orden propio
Siete palabras, un comparador por longitud y mucho cuidado con los empates. Aquí está el lugar del crimen de mi anécdota.
La idea
TreeSet y TreeMap mantienen sus elementos ordenados usando compareTo o un Comparator, y eso lo cambia todo: dejan de usar equals y hashCode, y solo miran el resultado de la comparación. Si el comparador da 0, para ellos es el mismo elemento.
Vamos a probar tres cosas: el orden natural de los String, un comparador que pierde palabras y el mismo comparador arreglado con un desempate. Después navegaremos el conjunto y usaremos un TreeMap para agrupar.
Antes de ejecutar, cuenta cuántas palabras de cada longitud hay. Eso ya te dice cuántas sobrevivirán con el comparador «solo longitud».
El código
Palabras.java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.TreeMap;
import java.util.TreeSet;
public class Palabras {
public static void main(String[] args) {
List<String> palabras = Arrays.asList("casa", "sol", "mesa", "luna", "pan", "cielo", "árbol");
// 1. Orden natural de String: alfabético
TreeSet<String> alfabetico = new TreeSet<>(palabras);
System.out.println("Alfabético: " + alfabetico);
// 2. Comparator solo por longitud: ¡empatar es "ser el mismo"!
TreeSet<String> porLongitud = new TreeSet<>(Comparator.comparingInt(String::length));
porLongitud.addAll(palabras);
System.out.println("Solo longitud: " + porLongitud.size() + " de " + palabras.size() + " " + porLongitud);
// 3. Con desempate no se pierde nada
TreeSet<String> completo = new TreeSet<>(
Comparator.comparingInt(String::length).thenComparing(Comparator.naturalOrder()));
completo.addAll(palabras);
System.out.println("Longitud + alfabético: " + completo.size() + " " + completo);
// 4. Navegar un TreeSet
System.out.println("\nPrimera: " + completo.first() + ", última: " + completo.last());
System.out.println("Anterior a \"mesa\": " + completo.lower("mesa"));
System.out.println("Siguiente a \"mesa\": " + completo.higher("mesa"));
System.out.println("Hasta \"luna\" (inclusive): " + completo.headSet("luna", true));
System.out.println("Desde \"mesa\": " + completo.tailSet("mesa"));
// 5. TreeMap: palabras agrupadas por longitud, siempre ordenadas por clave
TreeMap<Integer, List<String>> porTamano = new TreeMap<>();
for (String p : palabras) {
porTamano.computeIfAbsent(p.length(), k -> new ArrayList<>()).add(p);
}
System.out.println("\nAgrupadas: " + porTamano);
System.out.println("Clave menor y mayor: " + porTamano.firstKey() + " y " + porTamano.lastKey());
System.out.println("Mayor clave <= 4: " + porTamano.floorKey(4));
System.out.println("Menor clave >= 5: " + porTamano.ceilingKey(5));
System.out.println("De mayor a menor: " + porTamano.descendingMap());
// 6. Claves que se comparan sin mirar mayúsculas
TreeMap<String, Integer> votos = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
for (String voto : new String[]{"Sol", "luna", "sol", "SOL", "Luna"}) {
votos.merge(voto, 1, Integer::sum);
}
System.out.println("\nVotos: " + votos);
}
}
Salida
Alfabético: [casa, cielo, luna, mesa, pan, sol, árbol]
Solo longitud: 3 de 7 [sol, casa, cielo]
Longitud + alfabético: 7 [pan, sol, casa, luna, mesa, cielo, árbol]
Primera: pan, última: árbol
Anterior a "mesa": luna
Siguiente a "mesa": cielo
Hasta "luna" (inclusive): [pan, sol, casa, luna]
Desde "mesa": [mesa, cielo, árbol]
Agrupadas: {3=[sol, pan], 4=[casa, mesa, luna], 5=[cielo, árbol]}
Clave menor y mayor: 3 y 5
Mayor clave <= 4: 4
Menor clave >= 5: 5
De mayor a menor: {5=[cielo, árbol], 4=[casa, mesa, luna], 3=[sol, pan]}
Votos: {luna=2, Sol=3}
Compruébalo a mano:
- Alfabético: ojo,
árbolaparece al final. El orden natural deStringcompara códigos de carácter y laátiene un código mayor que laz. No es el orden del diccionario. - Solo longitud: 3 de 7. Hay palabras de 3, 4 y 5 letras: tres longitudes, tres elementos. Se queda la primera palabra que entró de cada longitud (
sol,casa,cielo). - Longitud + alfabético: 7. Con el desempate nadie empata con nadie y no se pierde ninguna.
- Navegar:
lower("mesa")esluna(la anterior) yhigher("mesa")escielo(la siguiente).headSet("luna", true)llega hastalunaincluida;tailSet("mesa")empieza enmesa. - **
TreeMap:** las claves 3, 4 y 5 salen siempre ordenadas.floorKey(4)es 4 yceilingKey(5)es 5. - Votos:
Sol,solySOLson la misma clave; los cinco votos se reparten enSol(3) yluna(2).
lower, higher, floorKey y ceilingKey funcionan aunque la clave que pides no exista: devuelven la más cercana por debajo o por arriba (y null solo si no hay ninguna).
Preguntas que te puedes hacer
**¿Por qué el TreeSet descarta mesa y luna sin avisar?** Porque el comparador dice que compare("mesa", "casa") == 0, y un TreeSet entiende «cero» como «ya lo tengo». No lanza ninguna excepción: añadir devuelve false, y ya está.
¿Por qué con el desempate no se pierde nada? Porque thenComparing(Comparator.naturalOrder()) solo se consulta cuando la longitud empata, y entonces decide el orden alfabético. Dos palabras distintas ya nunca dan 0.
**¿Y la clave que se guarda en el TreeMap con CASE_INSENSITIVE_ORDER? La primera** que entró. Las demás cuentan para esa misma clave, pero no la sustituyen.
**¿Cuándo uso TreeSet en lugar de HashSet? Cuando necesitas los elementos ordenados** o navegar (first, lower, headSet…). Si solo te importa saber si algo está o no, HashSet es más rápido.
Usar un Comparator que no distingue elementos distintos y esperar que el TreeSet guarde los dos. Un TreeSet no mira equals: si compare da 0, el segundo no entra.
Pruébalo tú
- Cambia el comparador de longitud para que ordene de mayor a menor longitud (
.reversed()) y comprueba cuál de las palabras de cuatro letras sobrevive. - Crea un
TreeMap<String, Integer>conString.CASE_INSENSITIVE_ORDERy cuenta cuántas veces aparece cada palabra de un texto, sin distinguir mayúsculas.
Errores típicos en esta práctica
- Restar para comparar en
compareTo: se desborda con valores grandes. UsaInteger.compareoDouble.compare. - **Un criterio de orden que da
0para elementos distintos**: en unTreeSetoTreeMapdesaparecen datos sin avisar. - **Creer que
reversed()invierte toda la cadena: solo invierte lo que hay antes** de él. - **Llamar a
clone()sinimplements Cloneable**:CloneNotSupportedExceptional ejecutar. - Olvidar duplicar lo mutable tras
super.clone(): la copia comparte la lista y cambia el original. - Usar el constructor de copia con herencia y perder los campos de la subclase.
Míralo paso a paso en Python Tutor
Python Tutor ejecuta el código línea a línea y dibuja variables, objetos y referencias. Elige Java. Aquí tienes el desastre del TreeSet reducido a lo mínimo, con el comparador escrito como una clase normal (más fácil de seguir que una lambda):
import java.util.Comparator;
import java.util.TreeSet;
public class Tutor {
static class PorLongitud implements Comparator<String> {
public int compare(String a, String b) {
return Integer.compare(a.length(), b.length());
}
}
public static void main(String[] args) {
TreeSet<String> conjunto = new TreeSet<>(new PorLongitud());
String[] palabras = {"casa", "sol", "mesa", "luna"};
for (String p : palabras) {
boolean nueva = conjunto.add(p);
System.out.println(p + " -> " + nueva + " " + conjunto);
}
}
}
Salida esperada:
casa -> true [casa] sol -> true [sol, casa] mesa -> false [sol, casa] luna -> false [sol, casa]
Qué observar paso a paso:
- En la primera vuelta (
casa),nuevavaletruey el conjunto tiene un único elemento. - En la segunda (
sol), tambiéntrue: la longitud es distinta (3 frente a 4) ysolse coloca antes decasa. - En la tercera (
mesa),nuevapasa afalse. Fíjate en el cambio: el conjunto no crece, porquecompare("mesa", "casa")da0. - La cuarta (
luna) repite lo mismo. Al terminar, de cuatro palabras quedan dos.
Por dentro un TreeSet es un árbol, así que Python Tutor puede dibujarlo con más cajas de las que esperas. Céntrate en la variable nueva y en la salida de cada vuelta, que es lo que cuenta la historia.
Resumen
Comparabledefine el orden natural de la clase;Comparatoraporta tantos criterios externos como quieras.- Los comparadores se encadenan y reutilizan:
comparing,reversed,thenComparing. - Un criterio de orden es una definición de «igual», no solo de «antes y después»: si da
0,TreeSetyTreeMaplo tratan como el mismo elemento. clone()es superficial: trassuper.clone()hay que duplicar a mano listas y objetos mutables.- El constructor de copia es más claro, pero pierde el tipo real de la subclase;
clone()lo conserva. - Los métodos genéricos con
<T extends Comparable<T>>valen para cualquier tipo comparable.
Checklist: ¿lo has practicado?
- [ ] He compilado y ejecutado los tres programas.
- [ ] He comprobado a mano al menos una salida de cada programa.
- [ ] He añadido un criterio nuevo al ranking sin tocar
compareTo. - [ ] He provocado una copia superficial y he visto cómo cambia el original.
- [ ] He perdido una palabra en un
TreeSetpor un empate y la he recuperado con un desempate. - [ ] He mirado el programa de Python Tutor y he visto cómo
adddevuelvefalse.
Y ahora, a por los ejercicios: ejercicios de Comparable y Cloneable, con tres niveles, tres fragmentos para depurar y un chuletario al final. ¡Nos vemos allí!
Siguiente paso: ejerciciosComparable y Cloneable en Java — ejercicios con libros, jugadores y una plantilla clonada (y la resta que puso al más rico el primero)¿Te ha servido este artículo? Así puedes apoyar SergioLearns →

Un comentario