Interfaces em Java
Defina contratos em Java com interfaces — métodos abstratos, métodos padrão e herança múltipla de tipo.
Uma interface é um contrato: um conjunto nomeado de operações que qualquer classe que a implemente se compromete a fornecer. Interfaces não possuem estado de instância, construtores, e (com uma exceção abordada no próximo capítulo) nenhum corpo de método. Elas descrevem o que um tipo pode fazer, deixando cada como para as implementações.
Interfaces são a resposta do Java à herança múltipla. Uma classe estende exatamente uma classe, mas pode implementar qualquer número de interfaces — permitindo que você combine Comparable, AutoCloseable e Iterable no mesmo tipo sem ambiguidade.
Declarando uma interface
Use interface em vez de class:
public interface Shape {
double area(); // implicitly public abstract
double perimeter();
}As declarações de método em uma interface são implicitamente public abstract. Você pode escrever esses modificadores, mas a maioria dos guias de estilo os omite por serem redundantes.
Implementando uma interface
Uma classe declara isso com implements. A classe deve fornecer um corpo para cada método que a interface declara:
public class Circle implements Shape {
private final double r;
public Circle(double r) { this.r = r; }
@Override public double area() { return Math.PI * r * r; }
@Override public double perimeter() { return 2 * Math.PI * r; }
}Se uma classe implementa uma interface mas não fornece todos os métodos, ela deve ser declarada abstract — a mesma regra das classes abstratas.
Você também pode implementar várias interfaces de uma vez:
public class Money implements Comparable<Money>, java.io.Serializable {
private final long cents;
public Money(long cents) { this.cents = cents; }
public int compareTo(Money other) { return Long.compare(this.cents, other.cents); }
}Isso é "herança múltipla de tipo" — Money é ao mesmo tempo um Comparable<Money> e um Serializable. Código que precise de qualquer um deles pode aceitar um Money.
Programando para uma interface
O propósito das interfaces é escrever código contra o contrato, não a implementação:
public double sumAreas(List<Shape> shapes) {
double sum = 0;
for (Shape s : shapes) sum += s.area();
return sum;
}sumAreas não sabe nem se importa com Circle. Adicione Square implements Shape, Triangle implements Shape, e a função funciona em listas deles também — sem alterações.
A biblioteca padrão é construída sobre esse padrão. Você quase sempre declara uma variável de um tipo de interface e instancia uma concreta:
List<String> names = new ArrayList<>(); // List, not ArrayList
Map<String, Integer> counts = new HashMap<>(); // Map, not HashMapSe você mais tarde mudar para LinkedList ou LinkedHashMap, apenas a linha do new precisa ser alterada.
Constantes em interfaces
Todo campo declarado em uma interface é implicitamente public static final — uma constante. Normalmente não se adicionam constantes a interfaces (é considerado má prática — o Constant Interface Antipattern), mas a sintaxe existe:
public interface Color {
String DEFAULT = "black"; // implicitly public static final
}Se você precisar de constantes, prefira um enum ou uma final class simples com campos public static final.
Interfaces podem estender interfaces
Uma interface pode estender outras interfaces — inclusive várias ao mesmo tempo:
public interface Readable { String read(); }
public interface Writable { void write(String s); }
public interface ReadWrite extends Readable, Writable { }Agora qualquer classe que implementa ReadWrite precisa fornecer tanto read() quanto write(). Não há distinção entre class extends / interface implements aqui — interfaces simplesmente estendem interfaces com extends.
Métodos padrão e estáticos (prévia)
Desde o Java 8, interfaces podem ter métodos default (corpos fornecidos pela palavra-chave default) e métodos static. Eles permitem adicionar comportamento a uma interface sem quebrar cada implementador existente. O tratamento completo está no próximo capítulo, métodos padrão:
public interface Shape {
double area();
// Default method — implementors get this for free.
default String describe() {
return getClass().getSimpleName() + " area=" + area();
}
// Static factory on the interface itself.
static Shape unitCircle() { return new Circle(1); }
}Interfaces marcadoras
Uma interface marcadora não declara nenhum método. Ela existe puramente como um rótulo que o código em tempo de execução pode verificar:
public interface Cacheable { } // no methods
public class Snapshot implements Cacheable { ... }E em algum lugar: if (obj instanceof Cacheable) { ... }. O Java moderno prefere anotações para esse tipo de metadado (@Cacheable em vez de implements Cacheable), mas Serializable, Cloneable e RandomAccess são interfaces marcadoras bem conhecidas na biblioteca padrão.
Interfaces funcionais
Uma interface com exatamente um método abstrato é uma interface funcional. Isso importa porque o Java permite que você forneça tal interface com uma expressão lambda ou referência de método em vez de uma classe anônima completa — o lambda é a implementação desse único método.
@FunctionalInterface
public interface Transformer {
String apply(String input); // the single abstract method
}
Transformer upper = s -> s.toUpperCase(); // lambda implements apply
System.out.println(upper.apply("hi")); // prints: HIA anotação opcional @FunctionalInterface é uma proteção em tempo de compilação: o código não compilará se a interface acabar com mais de um método abstrato. (Métodos default e static não contam para o limite.) A biblioteca padrão traz uma caixa de ferramentas completa deles — Runnable, Comparator, Function, Predicate, Supplier — abordados em interfaces funcionais.
Escolhendo entre uma interface e uma classe abstrata
A árvore de decisão:
- As subclasses precisam compartilhar estado ou implementações de métodos compartilhados? Se sim, você provavelmente quer uma classe abstrata — interfaces não podem manter estado de instância.
- Você quer um tipo que várias classes não relacionadas possam satisfazer? Se sim, uma interface — uma classe pode implementar muitas interfaces, mas estender apenas uma classe.
- O contrato vai crescer com o tempo? Interfaces evoluem com mais cuidado — adicionar um método abstrato a uma interface quebra todos os implementadores, a menos que você o torne um método
default. Classes abstratas podem adicionar um método concreto sem quebrar ninguém.
Na maioria dos códigos reais, interfaces vencem para contratos de tipo, e classes abstratas aparecem nos bastidores quando várias implementações compartilham infraestrutura.
Um exemplo completo
O que vem a seguir
As interfaces costumavam ser contratos puros — sem corpos de método algum. Desde o Java 8 isso foi relaxado: interfaces podem fornecer métodos default, métodos static e até métodos auxiliares private. O próximo capítulo é um tour por essas adições. Continue em métodos padrão.