CodeGym /Cursos /JAVA 25 SELF /Interfaces de la biblioteca estándar: Comparable, Seriali...

Interfaces de la biblioteca estándar: Comparable, Serializable y otros

JAVA 25 SELF
Nivel 21 , Lección 4
Disponible

1. Interfaz Comparable<T>

¿Alguna vez has ordenado una lista de números o cadenas? ¡Claro que sí! Ahora imagina que tienes una lista de tus propios objetos — por ejemplo, una lista de estudiantes, productos o gatitos. ¿Cómo sabrá Java en qué orden ordenarlos? Justo para eso existe la interfaz Comparable<T>.

Esta interfaz define el «orden natural» de los objetos — es decir, el orden que tiene sentido para ese tipo de datos. Por ejemplo, para números — ascendente; para cadenas — alfabético; para estudiantes — por apellido o por edad (tú eliges).

Cómo está hecho Comparable

La interfaz es muy sencilla: solo tiene un método:

public interface Comparable<T> {
    int compareTo(T o);
}

El método compareTo debe devolver:

  • un número negativo si el objeto actual es «menor» que el otro;
  • 0 si «igual»;
  • un número positivo si «mayor».

Ejemplo: ordenamos estudiantes por edad

Añadamos la clase Student e implementemos para ella la interfaz Comparable<Student>:

public class Student implements Comparable<Student> {
    private String name;
    private int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // Getters para el ejemplo
    public String getName() { return name; }
    public int getAge() { return age; }

    @Override
    public int compareTo(Student other) {
        // Ordenamos por edad (ascendente)
        return Integer.compare(this.age, other.age);
    }

    @Override
    public String toString() {
        return name + " (" + age + ")";
    }
}

Ahora podemos ordenar fácilmente un array o una lista de estudiantes:

import java.util.*;

public class Main {
    public static void main(String[] args) {
        List<Student> students = new ArrayList<>();
        students.add(new Student("John", 20));
        students.add(new Student("Alice", 18));
        students.add(new Student("Charlie", 22));

        Collections.sort(students); // ¡Funciona gracias a Comparable!

        System.out.println("Estudiantes ordenados:");
        for (Student s : students) {
            System.out.println(s);
        }
    }
}

Resultado:

Alice (18)
John (20)
Charlie (22)

Un matiz importante

Si implementas Comparable, intenta que compareTo sea coherente con equals. Es decir, si a.compareTo(b) == 0, entonces a.equals(b) debería ser true. De lo contrario, la ordenación y las colecciones pueden comportarse de forma impredecible, y tendrás motivos para filosofar sobre el sentido de la vida del programador.

2. Interfaz Serializable

La serialización es la capacidad de un objeto para convertirse en una secuencia de bytes (por ejemplo, para guardarse en un archivo o enviarse por la red) y luego restaurarse de nuevo. Imagina que quieres guardar el estado de tu juego o enviar un objeto al servidor — sin serialización, imposible.

En Java existe para ello una interfaz marcadora Serializable. Marcadora significa que no contiene métodos y simplemente «marca» la clase como serializable.

import java.io.Serializable;

public class Student implements Serializable {
    private String name;
    private int age;

    // ... resto del código
}

Cómo serializar un objeto

Para serializar y deserializar se usan las clases ObjectOutputStream y ObjectInputStream. Ejemplo — guardamos un objeto en un archivo y lo leemos de vuelta:

import java.io.*;

public class Main {
    public static void main(String[] args) throws Exception {
        Student s = new Student("Diana", 19);

        // Guardamos el objeto en un archivo
        try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("student.dat"))) {
            out.writeObject(s);
        }

        // Leemos el objeto desde el archivo
        try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("student.dat"))) {
            Student loaded = (Student) in.readObject();
            System.out.println("Cargado: " + loaded);
        }
    }
}

Nota: Todos los campos del objeto (y de los objetos anidados) también deben ser serializables; de lo contrario habrá un error.

Para qué sirve la interfaz marcadora

La interfaz Serializable no exige implementar métodos — simplemente informa a la JVM: «este objeto se puede serializar». Si olvidas implementarla, el intento de serialización lanzará la excepción NotSerializableException.

3. Otras interfaces importantes de la biblioteca estándar

Interfaz Cloneable

Otra interfaz marcadora. Su cometido es hacer saber a la JVM que el objeto se puede clonar mediante el método Object.clone(). Sin ella, el intento de llamar a clone() lanzará una excepción.

Sin embargo, el clonado en Java tiene trampa. El clonado por defecto es superficial (shallow copy), y a menudo en su lugar es mejor escribir métodos de copia propios.

public class Student implements Cloneable {
    private String name;
    private int age;

    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

Interfaz AutoCloseable

Esta interfaz contiene un único método close(). Cualquier clase que la implemente puede usarse en la construcción try-with-resources — cierre automático de recursos (por ejemplo, archivos, flujos):

public class MyResource implements AutoCloseable {
    @Override
    public void close() {
        System.out.println("¡Recurso cerrado!");
    }
}

public class Main {
    public static void main(String[] args) {
        try (MyResource res = new MyResource()) {
            System.out.println("Trabajando con el recurso");
        }
        // Aquí se invocará res.close() automáticamente
    }
}

Interfaz Iterable<T>

Esta interfaz permite que tu objeto sea «recorrible» en un bucle for-each. Contiene un único método iterator(), que devuelve un objeto Iterator<T>.

public class MyList implements Iterable<String> {
    // ... almacenamiento interno

    @Override
    public java.util.Iterator<String> iterator() {
        // Devolvemos un iterador para recorrer los elementos
        return ...;
    }
}

Todas las colecciones estándar (ArrayList, HashSet, etc.) implementan Iterable, por eso se pueden recorrer con for-each.

Interfaz Comparator<T>

Esta interfaz permite comparar objetos con reglas distintas, sin cambiar los propios objetos. Por ejemplo, ordenar estudiantes por nombre en lugar de por edad.

import java.util.Comparator;

Comparator<Student> byName = new Comparator<Student>() {
    @Override
    public int compare(Student a, Student b) {
        return a.getName().compareTo(b.getName());
    }
};

En Java moderno esto suele hacerse con expresiones lambda:

Comparator<Student> byName = (a, b) -> a.getName().compareTo(b.getName());

Observer, EventListener

Estas interfaces se usan para implementar los patrones «observador» y «escuchador de eventos» — cuando un objeto reacciona a eventos que ocurren en otro. Por ejemplo, en interfaces gráficas (Swing, JavaFX) los manejadores de botones implementan la interfaz ActionListener.

4. Práctica: implementamos Comparable y serializamos un objeto

Ejemplo 1. Comparable para tu propia clase

Escribamos una clase Book que se pueda ordenar por año de edición:

public class Book implements Comparable<Book> {
    private String title;
    private int year;

    public Book(String title, int year) {
        this.title = title;
        this.year = year;
    }

    @Override
    public int compareTo(Book other) {
        return Integer.compare(this.year, other.year);
    }

    @Override
    public String toString() {
        return title + " (" + year + ")";
    }
}
import java.util.*;

public class Main {
    public static void main(String[] args) {
        List<Book> books = Arrays.asList(
            new Book("Java para Dummies", 2018),
            new Book("Guerra y paz", 1869),
            new Book("Harry Potter", 1997)
        );
        Collections.sort(books);
        System.out.println(books);
    }
}

Resultado:

[Guerra y paz (1869), Harry Potter (1997), Java para Dummies (2018)]

Ejemplo 2. Serialización de un objeto

import java.io.*;

public class Book implements Serializable {
    private String title;
    private int year;
    // ... constructor, getters, toString

    public Book(String title, int year) {
        this.title = title;
        this.year = year;
    }

    @Override
    public String toString() {
        return title + " (" + year + ")";
    }
}

public class Main {
    public static void main(String[] args) throws Exception {
        Book book = new Book("Java para Dummies", 2018);

        // Guardamos el objeto en un archivo
        try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("book.dat"))) {
            out.writeObject(book);
        }

        // Leemos el objeto desde el archivo
        try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("book.dat"))) {
            Book loaded = (Book) in.readObject();
            System.out.println("Cargado: " + loaded);
        }
    }
}

5. Tabla: interfaces principales de la biblioteca estándar

Interfaz Finalidad Métodos clave Ejemplo de uso
Comparable<T>
Orden natural de los objetos
int compareTo(T o)
Ordenación de listas
Comparator<T>
Comparación personalizada de objetos
int compare(T a, T b)
Ordenación con distintas reglas
Serializable
Serialización de objetos — (marcador) Guardado/carga de objetos
Cloneable
Clonado de objetos — (marcador) Creación de copias de objetos
AutoCloseable
Cierre automático de recursos
void close()
try-with-resources
Iterable<T>
Recorrido de elementos en colecciones
Iterator<T> iterator()
Bucle for-each
Observer / EventListener Reacción a eventos
update(), actionPerformed
Gestión de eventos en la UI, patrones

6. Errores típicos al trabajar con las interfaces estándar

Error n.º 1: No se ha implementado la interfaz, pero se necesita la funcionalidad.
Por ejemplo, olvidaste implementar Serializable y tratas de serializar un objeto — obtendrás NotSerializableException. De forma análoga con Cloneable y la llamada a clone().

Error n.º 2: Incumplir el contrato de Comparable y equals.
Si a.compareTo(b) == 0 pero no se cumple a.equals(b), las colecciones pueden comportarse de forma extraña. Por ejemplo, TreeSet puede «perder» objetos.

Error n.º 3: Copia superficial al clonar.
El método clone() por defecto solo copia la «capa superior» del objeto. Si tienes campos que son referencias a otros objetos, no se copian en profundidad. Esto puede llevar a bugs misteriosos.

Error n.º 4: No usar try-with-resources.
Si una clase implementa AutoCloseable pero no la usas con try-with-resources, corres el riesgo de olvidar cerrar el recurso — y provocar una fuga de memoria o el bloqueo de un archivo.

Error n.º 5: Implementación incorrecta de compareTo o compare.
Si devuelves solo 0 o 1, en lugar de un número negativo/cero/positivo, la ordenación funcionará de forma incorrecta.

1
Cuestionario/control
Interfaces avanzados, nivel 21, lección 4
No disponible
Interfaces avanzados
Interfaces avanzados e interfaces funcionales
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION