Interfaces em Java
No Java, a herança de classes é limitada: uma classe só pode herdar diretamente de uma única superclasse. Em linguagens como C++, existe herança múltipla de classes, mas no Java isso não é permitido.
Para oferecer mais flexibilidade, o Java conta com interfaces, que definem contratos de métodos (e constantes) que uma ou mais classes podem implementar. Uma classe pode implementar vários interfaces, independentemente de já herdar de outra classe.
O que é um interface
Um interface é um tipo especial que contém:
- Constantes (implicitamente
public static final
) - Métodos abstratos (implicitamente
public abstract
) - Métodos com implementação (default methods)
- Métodos estáticos
- Métodos privados
Por padrão, todo método de um interface é público. Um interface não pode ter atributos de instância e não pode ser instanciado diretamente.
Criando um interface
Para definir um interface, usa-se a palavra-chave interface
:
interface Printable {
void print();
}
Aqui, print()
não tem implementação.
Qualquer classe que implemente Printable
será obrigada a fornecer uma implementação concreta para print()
.
Implementando um interface
Uma classe implementa um interface usando implements
:
public class Program {
public static void main(String[] args) {
Book b1 = new Book("Java. Complete Reference", "H. Schildt");
b1.print();
}
}
interface Printable {
void print();
}
class Book implements Printable {
String name;
String author;
Book(String name, String author) {
this.name = name;
this.author = author;
}
public void print() {
System.out.printf("%s (%s)%n", name, author);
}
}
⚠️ Importante: Se a classe não implementar todos os métodos do interface, ela deve ser declarada como
abstract
.
Usando variáveis de interface
Não é possível criar instâncias diretas de um interface:
Printable p = new Printable(); // Erro
Mas uma variável do tipo do interface pode armazenar objetos de qualquer classe que o implemente:
Printable printable = new Book("Java. Complete Reference", "H. Schildt");
printable.print(); // Java. Complete Reference (H. Schildt)
printable = new Journal("Foreign Policy");
printable.print(); // Foreign Policy
Isso permite trabalhar de forma polimórfica: o código usa o interface, mas a implementação concreta pode variar.
Casting com interfaces
Assim como com classes, é possível fazer casting entre variáveis de interface e classes concretas:
Printable p = new Journal("Foreign Affairs");
p.print();
// Para acessar métodos específicos de Journal
String name = ((Journal) p).getName();
System.out.println(name);
Se o método não está no interface, é preciso converter para a classe correspondente.
Métodos default
Até o Java 7, todos os métodos de interface precisavam ser implementados pelas classes.
A partir do Java 8, é possível criar métodos com implementação padrão usando default
:
interface Printable {
default void print() {
System.out.println("Undefined printable");
}
}
A classe que implementar esse interface pode sobrescrever o método ou herdar a implementação padrão.
Métodos estáticos em interfaces
Um interface pode ter métodos estáticos, chamados pelo próprio nome do interface:
interface Printable {
static void read() {
System.out.println("Read printable");
}
}
Printable.read();
Métodos privados em interfaces
Métodos privados em interfaces servem para reutilizar código dentro do próprio interface. Eles não podem ser chamados de fora.
interface Calculatable {
default int sum(int a, int b) {
return sumAll(a, b);
}
default int sum(int a, int b, int c) {
return sumAll(a, b, c);
}
private int sumAll(int... values) {
int result = 0;
for (int n : values) result += n;
return result;
}
}
Constantes em interfaces
As constantes declaradas em interfaces são sempre public static final
:
interface Stateable {
int OPEN = 1;
int CLOSED = 0;
void printState(int state);
}
class WaterPipe implements Stateable {
public void printState(int n) {
if (n == OPEN) System.out.println("Water is opened");
else if (n == CLOSED) System.out.println("Water is closed");
else System.out.println("State is invalid");
}
}
Implementando múltiplos interfaces
Uma classe pode implementar mais de um interface, separados por vírgula:
class Book implements Printable, Searchable {
// implementação
}
Herança entre interfaces
Interfaces também podem estender outros interfaces:
interface BookPrintable extends Printable {
void paint();
}
A classe que implementar BookPrintable
precisará implementar também os métodos de Printable
.
Interfaces aninhadas
Interfaces podem ser declaradas dentro de classes ou outros interfaces:
class Printer {
interface Printable {
void print();
}
}
class Journal implements Printer.Printable {
String name;
Journal(String name) {
this.name = name;
}
public void print() {
System.out.println(name);
}
}
Interfaces como parâmetros e retorno
Assim como classes, interfaces podem ser usadas como tipo de parâmetro ou de retorno de métodos:
static void read(Printable p) {
p.print();
}
static Printable createPrintable(String name, boolean option) {
if (option) {
return new Book(name, "Undefined");
} else {
return new Journal(name);
}
}