1. Interface Comparable<T>
Você já ordenou uma lista de números ou strings? Claro que sim! Agora imagine que você tem uma lista de seus próprios objetos — por exemplo, estudantes, produtos ou gatinhos. Como o Java vai entender em que ordem ordená-los? É exatamente para isso que serve a interface Comparable<T>.
Essa interface define a “ordem natural” dos objetos — ou seja, a ordem que faz sentido para esse tipo de dado. Por exemplo, para números — crescente; para strings — alfabética; para estudantes — por sobrenome ou idade (você escolhe).
Como o Comparable funciona
A interface é muito simples: ela tem apenas um método:
public interface Comparable<T> {
int compareTo(T o);
}
O método compareTo deve retornar:
- um número negativo, se o objeto atual for “menor” que o outro;
- 0, se forem “iguais”;
- um número positivo, se for “maior”.
Exemplo: ordenando estudantes por idade
Vamos adicionar a classe Student e implementar para ela a interface 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 o exemplo
public String getName() { return name; }
public int getAge() { return age; }
@Override
public int compareTo(Student other) {
// Ordenamos por idade (crescente)
return Integer.compare(this.age, other.age);
}
@Override
public String toString() {
return name + " (" + age + ")";
}
}
Agora podemos ordenar facilmente um array ou uma lista de estudantes:
import java.util.*;
public class Main {
public static void main(String[] args) {
List<Student> students = new ArrayList<>();
students.add(new Student("Vasya", 20));
students.add(new Student("Petya", 18));
students.add(new Student("Masha", 22));
Collections.sort(students); // Funciona graças ao Comparable!
System.out.println("Estudantes ordenados:");
for (Student s : students) {
System.out.println(s);
}
}
}
Resultado:
Petya (18)
Vasya (20)
Masha (22)
Uma nuance importante
Se você implementar Comparable, procure manter o compareTo consistente com o equals. Ou seja, se a.compareTo(b) == 0, então a.equals(b) deve ser true. Caso contrário, a ordenação e as coleções podem se comportar de forma imprevisível — e você ganhará um motivo para reflexões filosóficas sobre o sentido da vida do programador.
2. Interface Serializable
Serialização é a capacidade de um objeto se transformar em uma sequência de bytes (por exemplo, para se salvar em arquivo ou enviar pela rede) e depois ser restaurado. Imagine que você quer salvar o estado do seu jogo ou enviar um objeto para o servidor — sem serialização não dá.
No Java existe a interface marcador Serializable para isso. “Marcador” significa que ela não contém métodos — apenas “marca” a classe como serializável.
import java.io.Serializable;
public class Student implements Serializable {
private String name;
private int age;
// ... restante do código
}
Como serializar um objeto
Para serialização e desserialização usam-se as classes ObjectOutputStream e ObjectInputStream. Exemplo — salvamos um objeto em arquivo e lemos de volta:
import java.io.*;
public class Main {
public static void main(String[] args) throws Exception {
Student s = new Student("Katya", 19);
// Salvamos o objeto em arquivo
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("student.dat"))) {
out.writeObject(s);
}
// Lemos o objeto do arquivo
try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("student.dat"))) {
Student loaded = (Student) in.readObject();
System.out.println("Carregado: " + loaded);
}
}
}
Observação: Todos os campos do objeto (e dos objetos aninhados) também devem ser serializáveis; caso contrário, ocorrerá um erro.
Por que precisamos de uma interface marcador
A interface Serializable não exige a implementação de métodos — ela apenas informa à JVM: “este objeto pode ser serializado”. Se você esquecer de implementá-la, a tentativa de serialização resultará na exceção NotSerializableException.
3. Outras interfaces importantes da biblioteca padrão
Interface Cloneable
Mais uma interface marcador. Sua função é deixar claro para a JVM que o objeto pode ser clonado usando o método Object.clone(). Sem ela, a tentativa de chamar clone() lançará uma exceção.
No entanto, clonagem em Java é um tema traiçoeiro. A clonagem padrão é superficial (shallow copy) e, muitas vezes, é melhor escrever métodos próprios de cópia.
public class Student implements Cloneable {
private String name;
private int age;
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
Interface AutoCloseable
Essa interface contém apenas um método close(). Qualquer classe que a implemente pode ser usada com a construção try-with-resources — fechamento automático de recursos (por exemplo, arquivos, streams):
public class MyResource implements AutoCloseable {
@Override
public void close() {
System.out.println("Recurso fechado!");
}
}
public class Main {
public static void main(String[] args) {
try (MyResource res = new MyResource()) {
System.out.println("Trabalhando com o recurso");
}
// Aqui res.close() será chamado automaticamente
}
}
Interface Iterable<T>
Essa interface permite que seu objeto seja “iterável” em um laço for-each. Ela contém apenas um método iterator(), que retorna um objeto Iterator<T>.
public class MyList implements Iterable<String> {
// ... armazenamento interno
@Override
public java.util.Iterator<String> iterator() {
// Retornamos um iterador para percorrer os elementos
return ...;
}
}
Todas as coleções padrão (ArrayList, HashSet etc.) implementam Iterable, por isso podem ser percorridas com for-each.
Interface Comparator<T>
Essa interface permite comparar objetos por diferentes regras, sem alterar os próprios objetos. Por exemplo, ordenar estudantes pelo nome, e não pela idade.
import java.util.Comparator;
Comparator<Student> byName = new Comparator<Student>() {
@Override
public int compare(Student a, Student b) {
return a.getName().compareTo(b.getName());
}
};
Na Java moderna, isso normalmente é feito com expressões lambda:
Comparator<Student> byName = (a, b) -> a.getName().compareTo(b.getName());
Observer, EventListener
Essas interfaces são usadas para implementar os padrões “observador” e “ouvinte de eventos” — quando um objeto reage a eventos que ocorrem em outro. Por exemplo, em interfaces gráficas (Swing, JavaFX) os handlers de botões implementam a interface ActionListener.
4. Prática: implementando Comparable e serializando um objeto
Exemplo 1. Comparable para sua própria classe
Vamos escrever a classe Book, que pode ser ordenada pelo ano de publicação:
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 Leigos", 2018),
new Book("Guerra e Paz", 1869),
new Book("Harry Potter", 1997)
);
Collections.sort(books);
System.out.println(books);
}
}
Resultado:
[Guerra e Paz (1869), Harry Potter (1997), Java Para Leigos (2018)]
Exemplo 2. Serialização de um objeto
import java.io.*;
public class Book implements Serializable {
private String title;
private int year;
// ... construtor, 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 Leigos", 2018);
// Salvamos o objeto em arquivo
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("book.dat"))) {
out.writeObject(book);
}
// Lemos o objeto do arquivo
try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("book.dat"))) {
Book loaded = (Book) in.readObject();
System.out.println("Carregado: " + loaded);
}
}
}
5. Tabela: principais interfaces da biblioteca padrão
| Interface | Finalidade | Métodos-chave | Exemplo de uso |
|---|---|---|---|
|
Ordem natural dos objetos | |
Ordenação de listas |
|
Comparação personalizada de objetos | |
Ordenação por regras diferentes |
|
Serialização de objetos | — (marcador) | Salvar/carregar objetos |
|
Clonagem de objetos | — (marcador) | Criação de cópias de objetos |
|
Fechamento automático de recursos | |
try-with-resources |
|
Iteração de elementos em coleções | |
loop for-each |
| Observer / EventListener | Reação a eventos | |
Tratamento de eventos em UI, padrões |
6. Erros comuns ao trabalhar com as interfaces padrão
Erro nº 1: A interface não foi implementada, mas o recurso é necessário.
Por exemplo, você esqueceu de implementar Serializable e tenta serializar um objeto — terá NotSerializableException. O mesmo vale para Cloneable e a chamada de clone().
Erro nº 2: Violação do contrato de Comparable e equals.
Se a.compareTo(b) == 0, mas não for verdadeiro que a.equals(b), as coleções podem se comportar de forma estranha. Por exemplo, o TreeSet pode “perder” objetos.
Erro nº 3: Cópia superficial ao clonar.
O método clone() por padrão copia apenas a “camada superior” do objeto. Se você tiver campos que referenciam outros objetos, eles não serão copiados profundamente. Isso pode levar a bugs enigmáticos.
Erro nº 4: Não utilizar try-with-resources.
Se a classe implementa AutoCloseable, mas você não a usa com try-with-resources, corre o risco de esquecer de fechar o recurso — e provocar um vazamento de memória ou o bloqueio de um arquivo.
Erro nº 5: Implementação incorreta de compareTo ou compare.
Se retornar apenas 0 ou 1, em vez de números negativo/zero/positivo, a ordenação funcionará incorretamente.
GO TO FULL VERSION