Você está na página 1de 7

Interfaces em Java – uma introdução

Miguel Jonathan – revisão em 6/5/2010

Conceito de interfaces em Java


Uma interface em Java é uma espécie de classe, com as seguintes propriedades:
a) Não pode ser instanciável (não podemos criar objetos com new);
b) Só pode possuir assinaturas de métodos de instância, públicos e abstratos (sem corpo).
Não pode possuir métodos concretos (com corpo), nem métodos estáticos.
Os prefixos abstract e public podem ser usados, mas são em geral omitidos;
c) Não pode conter variáveis de instância ou de classe (static);
d) Pode conter declarações de constantes (com prefixo final e inicializadas para um valor) –
nesse caso essa variável funcionará como uma constante de classe.
O prefixo static possa ser usado, mas é em geral omitido;
e) Pode ser criada como subinterface de outra interface já existente, usando extends, como
as classes.

Para criar interfaces usamos uma sintaxe parecida com a das classes, substituindo a palavra class
por interface, por exemplo:

public interface InterfaceExemplo{


public final String PALAVRA = "UFRJ";
public void metodo1(int x);
public String metodo2 (String s);
}

public interface InterfaceExemplo2 extends InterfaceExemplo {


public void metodo3();
}

Nos exemplos acima, são criadas duas interfaces.


A segunda é uma subinterface da primeira, ou uma extensão dela.
A interface InterfaceExemplo2 contém todos os três métodos, e mais a constante PALAVRA.
Ou seja, ela "herda" as definições da sua superinterface Exemplo (bem como de todas as
superinterfaces acima na hierarquia que existirem).

Ao contrário das classes, não existe uma interface "raiz" de todas as interfaces, como ocorre com a
classe Object.
A interface InterfaceExemplo, vista acima, não herda de nenhuma outra.

Note que a mesma convenção para nome de classe se aplica aos nomes das interfaces: iniciar com
letra maiúscula, seguida de letras minúsculas.
A constante PALAVRA do exemplo acima segue a convenção de nome de constantes em Java, que
é ter todas as letras maiúsculas.

O arquivo fonte de uma interface, da mesma forma que no caso de classes, também tem o nome
da interface com a terminação .java.
E o compilador gera da mesma forma um arquivo .class de mesmo nome.

Usando interfaces
Uma Interface funciona de forma análoga a uma classe abstrata.
Ou seja, ela não pode ser instanciada, mas pode ser como que "herdada" por uma classe.
A forma sintática para uma classe herdar de uma interface utiliza a palavra chave implements,
como no exemplo fictício abaixo:

public class ClasseTeste implements InterfaceExemplo {

public void metodo1(int x) {


System.out.println(x);
}

public String metodo2(String s) {


return s + " da " + PALAVRA;
}

// outros métodos e atributos desta classe


}

O sentido de uma classe "herdar" de uma interface é similar ao de herdar métodos abstratos de
uma superclasse abstrata.
A classe fica obrigada a implementar concretamente todos os métodos declarados na interface, e
nas suas super-interfaces, ou a classe não compilará.
Note que a classe pode usar diretamente a constante definida na interface.

Classes podem implementar mais de uma interface


Uma diferença essencial entre classes e interfaces é que uma classe pode implementar diversas
interfaces, embora possa ser subclasse de apenas uma superclasse.

Nesse caso, após a palavra chave implements, escrevemos a lista das interfaces que a classe
implementa, separadas por vírgulas.

Por exemplo, sejam as interfaces InterfaceExemplo, InterfaceExemplo2 e InterfaceY, e uma


classe ClasseExemplo2 que implementa as duas últimas interfaces:

public interface InterfaceExemplo{


public final String PALAVRA= "UFRJ";
public void metodo1(int x);
public String metodo2 (String s);
}
=============================================================
public interface InterfaceExemplo2 extends InterfaceExemplo {
public void metodo3();
}
============================================================
public interface InterfaceY {
public int f1();
public int f2();
}
============================================================
public class ClasseExemplo2 implements InterfaceExemplo2, InterfaceY {
public void metodo1(int x) {
// texto do método
}

public String metodo2(String s) {


// texto do método
}

public void metodo3() {


// texto do método
}

public int f1() {


// texto do método
}

public int f2() {


// texto do método
}

// outros métodos, atributos, etc. desta classe


}

Note que a classe fica obrigada a implementar concretamente todos os métodos declarados em
todas as interfaces que implementa (isso incluindo todos os métodos declarados em todas as
superinterfaces de todas elas).

Interfaces podem ser tipos de referências


Da mesma forma que as classes, uma referência pode ser declarada como tendo o tipo de uma
interface. Essa propriedade permite, inclusive, um dos mais importantes usos de interfaces, como
veremos mais adiante.

Por exemplo, supondo as interfaces dos exemplos acima, poderíamos declarar, em qualquer
método, variáveis como:
InterfaceExemplo ex;
InterfaceY y1;

Uma variável do tipo de uma interface pode referenciar qualquer objeto de qualquer classe que
implemente essa interface, ou qualquer subinterface dela.
Mas há uma restrição importante: a partir de uma referência de um tipo, só é possível acessar os
métodos definidos no nível desse tipo ou acima.

Acompanhe o teste abaixo. Algumas linhas contêm erros comentados:

public class TesteRef{


public static void main(String[] args){
InterfaceExemplo ex1;
InterfaceY y1, y2;
ex1 = new ClasseExemplo2(); // OK, ClasseExemplo2 implementa sub-interface InterfaceExemplo
y1 = new ClasseExemplo2(); // OK, ClasseExemplo2 implementa InterfaceY diretamente
// y1 = ex1; // Erro: y1 e ex1 têm direitos de acesso diferentes ao mesmo objeto.
y2 = (Y) ex1; // OK, com o casting, o compilador aceita a atribuição
ex1.metodo1(100); // OK, void metodo1(int) foi definido na interface InterfaceExemplo
// ex1.exemplo3()); // ERRO: método void exemplo3() não foi definido para o tipo InterfaceExemplo

// System.out.println(ex1.f1()); // ERRO: metodo f1() não é definido para o tipo InterfaceExemplo

System.out.println(y1.f1()); // OK, método int f1() foi definido para o tipo InterfaceY
}
}

Interfaces parametrizadas da API do Java SE


No pacote java.util encontramos duas interfaces fundamentais para se entender e utilizar as
classes que lidam com coleções de objetos em Java.
Trata-se das interfaces java.lang.Comparable<T> e java.lang.Comparator<T>.
Na interface Comparable<T> está definido um único método com a assinatura:
public int compareTo (T o);

Note o parâmetro genérico T.


Se uma classe implementa a interface Comparable<T>, então deve implementar esse método
com obrigatoriamente um parâmetro também do do tipo T.
Em geral, essa classe será a classe T, e o método permite comparar duas instâncias dessa classe.
Esse método deve ter a seguinte funcionalidade, supondo a operação:
t1.compareTo(t2), onde t1 e t2 são do tipo T:

a) se o objeto t1 deve ser considerado menor (deve ficar antes) que t2, o método deve retornar
um inteiro negativo;
b) se o objeto t1 deve ser considerado maior (deve ficar depois) que t2, o método deve retornar
um inteiro positivo;
c) se o objeto t1 deve ser considerado igual a (deve ficar na mesma posição que) t2, o método
deve retornar zero.
O exemplo abaixo ajuda a compreender o uso dessa interface para ordenar um vetor de objetos
de uma classe qualquer.
Seja uma classe Data, para representar datas do calendário, com campos dia, mes e ano.
Se queremos que duas datas sejam "comparáveis", no sentido acima, a classe deve implementar a
interface Comparable<Data>, e implementatar um método public int compareTo (Data
d){....} que tenha a funcionalidade convencionada nesta interface.

Esse método pode ser escrito da forma abaixo:


public int compareTo(Data d) {
if (ano < d.ano) return -1;
if (ano > d.ano)return 1;
if (mes < d.mes) return -1;
if (mes > d.mes) return 1;
return dia -d.dia;
}

O projeto Comparaveis.zip mostra essa classe e um exemplo de utilização para ordenar um vetor
de datas.

A coleção java.util.TreeSet<E>, e o uso das interfaces Comparable e Comparator:


Um TreeSet é uma classe que implementa a interface java.util.Set. Ela pertence ao grupo de
classes conhecido como Java Collections Framework.
Uma instância de TreeSet<E> pode armazenar referências do tipo E, ou de qualquer subtipo de E.
Um TreeSet, como um HashSet, não pode ter referências duplicadas. Mas nesse caso, o
significado de "referências duplicadas" é bem mais sutil.
No caso do HashSet, se x1 e x2 são duas referências contidas no conjunto, então x1.equals(x2)
e x2.equals(x1) devem retornar o valor false.
Os TreeSets, por sua vez, têm a propriedade de manter seus elementos em uma ordem dada por
um critério de ordenação definido no momento da sua construção.
Esse critério depende da forma do construtor utilizado:
a) Se o construtor for apenas new TreeSet<E>(), sem parâmetro, então o critério de
ordenação será o definido pelo método:
int compareTo(E o)

que necessariamente deverá existir na classe E. Nesse caso, para que isso possa ser
garantido, a classe E deve implementar a interface java.util.Comparable<E>, que
obriga a ter esse método.
Como deve funcionar o método compareTo:
Sejam x1 e x2 instâncias de E. Então, x1.compareTo(x2) deve retornar um inteiro negativo
se o objeto referenciado por x1 deva ficar antes de x2 na ordenação desejada, retornar um
inteiro positivo, caso x1 deva ficar depois de x2, e retornar o valor inteiro zero, caso x1 e x2
tenham a mesma posição relativa na ordenação desejada.

b) Se o construtor for da forma new TreeSet(c), onde c for do tipo


java.util.Comparator<T>, (onde T é a classe E, ou alguma superclasse de E, ver obs
adiante), então o objeto referenciado por c deverá ser de uma classe que implemente a
interface java.util.Comparator<T>. Essa interface obriga que seja definido um método
com a assinatura:
int compare(T x1, T x2)

Esse método deve ter funcionalidade similar à do método compareTo() visto acima:
Sejam x1 e x2 duas referências do tipo T. Então, compare(x1, x2) deve retornar um
inteiro negativo se o objeto referenciado por x1 deva ficar antes de x2 na ordenação
desejada, retornar um inteiro positivo, caso x1 deva ficar depois de x2, e retornar o valor
inteiro zero, caso x1 e x2 tenham a mesma posição relativa na ordenação desejada.
Dizemos que o objeto passado como parâmetro para o construtor do TreeSet é "um
comparator", ou seja, é uma instância de uma classe que implementa a interface
Comparator<T>. Na prática, essa classe é feita para conter apenas o método compare(), e
o método compare() implementa o critério de ordenação desejado.
Observação muito importante para uso de TreeSets - compatibilidade de equals() com
compare() ou compareTo():

Como sabemos, um conjunto não pode conter referências "duplicadas". Mas o que significa
exatamente isso?
No caso de TreeSets, quando se vai adicionar um novo elemento ao conjunto, o método
comparador é acionado para verificar a posição deste elemento em relação aos que já estão no
conjunto. Caso o resultado seja zero, indicando que o novo elemento deve ficar na "mesma"
posição que algum outro que já esteja no conjunto, esse novo elemento NÃO será adicionado ao
conjunto, sendo considerado uma repetição. Isso ocorrerá MESMO que o teste de equals() retorne
o valor false.
Por exemplo, suponha um conjunto de Aluno, e que nessa classe o método boolean
equals(Object obj) considere que dois alunos são "iguais" caso possuam o mesmo valor de
DRE. Se o critério de ordenação usado pelo comparador for a ordem alfabética dos nomes, e se o
método compare(), ou compareTo(), retornar zero para dois alunos homônimos (mesmo que
com DRE's diferentes), então somente um desses alunos poderá entrar no conjunto.
Nesse caso dizemos que o método equals() e o método comparador utilizado não são
"compatíveis". Para que sejam compatíveis, o método comparador somente deve retornar zero
para os mesmos casos em que o método equals() retornar o valor true.
Como conseguir isso?
É relativamente fácil: basta fazer com que, nos casos em que o critério de ordenação do
comparador indicar a mesma posição relativa, o valor a ser retornado pelo método
compare/compareTo seja dado pelo critério definido no método equals() da classe dos
elementos do conjunto.
Voltando ao exemplo anterior, caso o critério compare dois alunos com exatamente o mesmo
nome, o método não deve retornar zero, mas sim o resultado da comparação dos valores de DRE
de cada um. Assim, somente retornaria zero caso os dois DRE's fossem idênticos, indicando
realmente ser o "mesmo" aluno que estaria se tentando incluir no conjunto.
Exemplo completo de uma classe comparadora de Aluno, usando o critério de comparar os nomes
em ordem alfabética ascendente, mostrando também o método equals() usado na classe Aluno:
class Aluno {
......
// método equals retorna true quando DRE's são iguais:
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof Aluno)) return false;
Aluno aluno = (Aluno)obj;
return this.getDre().compareTo(aluno.getDre());
}
}
class ComparadorNome implements java.util.Comparator<Aluno>
public int compare(Aluno a1, Aluno a2) {
int res = a1.getNome().compareTo(a2.getNome());
if (res == 0) return a1.getDre().compareTo(a2.getDre());
return res;
}
}

Note que o método compare() acima jamais retornará zero com dois alunos de mesmo DRE,
mesmo que tenham exatamente o mesmo nome.
Outra observação importante: para poder ordenar um TreeSet<E> deve-se usar um
Comparator<T>, onde T deve ser do tipo E ou de algum supertipo de E (e não um subtipo):

Veja esse exemplo: ordenar um TreeSet<Pessoa>. Vamos supor que o TreeSet contenha
referência de Aluno e de Professor, subclasses de Pessoa. Evidentemente, o critério de
comparação usado não poderá usar nenhuma característica exclusiva de Aluno ou de Professor,
mas somente características comuns, que se encontram na classe Pessoa ou acima dela.
Poderemos usar um Comparator<Pessoa>, mas não um Comparator<AlunoGrad>.
Para indicar isso, o tipo do comparator a ser usado para construir um TreeSet<Pessoa> é escrito
da forma: Comparator<? super Pessoa>.
Ou seja, podemos comparar os elementos desse conjunto por nome, idade, etc, mas não por
nota. Um problema que surge aqui é que teremos que o método comparador terá que ter
compatibilidade com o método equals() existente na classe Pessoa ou acima dela.
Última observação: cuidado com a definição correta do método equals():
Para efeito de inclusão em sets, o método equals usado é o que tem a assinatura:
boolean equals(Object obj);

Esse é o método que precisa ser redefinido na classe dos elementos que queremos inserir em um
TreeSet. No caso do exemplo acima, note que NÃO definimos o método:
boolean equals(Aluno a);

O operador instanceof:
No método equals() usado no exemplo acima usamos o operador instanceof.
O operador instanceof compara um objeto com um tipo específico. É usado para testar se um
objeto é uma instância de uma classe, uma instância de uma subclasse, ou uma instância de uma
classe que implementa uma determinada interface.
Por exemplo, se Aluno é subclasse de Pessoa, e se Pessoa implementa Comparable, e se a
referência alu aponta para um objeto da classe Aluno, então:
alu instanceof Pessoa – dá true
alu instanceof Aluno – dá true
alu instanceof Comparable – dá true

Você também pode gostar