Uma classe dá um nome para um conjunto de campos e métodos (funções e
procedimentos). Estes campos e métodos modelam um certo conceito. O interesse nas
classes ocorre, basicamente em duas situações. Primeiramente pelo fato de que
um comando só pode existir dentro de um elemento (p.ex. método) de uma classe.
Em segundo lugar pelo fato de que uma classe define um tipo. Podemos assim
definir variáveis que irão conter referências para instâncias da respectiva
classe.
Uma interface dá um
nome para um conjunto de constantes e operações (assinaturas de métodos). Estas
constantes e operações modelam um certo comportamento. O interesse nas interfaces
ocorre pelo fato de podermos definir "o que?" é feito sem
termos que definir "como?". Esta separação tem sido muito
importante no desenvolvimento de grandes sistemas de software. Uma interface
também define um tipo, mas não existem instâncias de interfaces.
|
Não existem instâncias de interfaces |
Uma classe se relaciona com
uma interface através da relação implements. Uma interface pode ser
implementada por mais de uma classe. Uma variável cujo tipo foi definido por
uma interface I somente poderá conter referências para instâncias de classes
que implementam a interface I (ou ainda de subclasses cujas superclasses
implementam a interface I). Portanto uma interface pode ser implementada por
uma ou mais classes e uma classe implementa uma ou mais interfaces. O exemplo
esquemático abaixo exemplifica isto. A interface I1 é implementada por duas
classes e I2 é implementada por uma classe. A classe C1 implementa duas
interfaces e a classe C2 implementa uma interface.
interface I1{...}
interface I2{...}
class C1 implements I1,
I2{...}
class C2 implements I1{...}
O compromisso de uma classe
C relacionada através de implements com uma interface I consiste em definir como
métodos todas as operações definidas na interface.
O exemplo abaixo procura
ilutrar uma possível interpretação da noção de interface (conjunto de elementos
comuns entre áreas, sistemas, etc). De forma abreviada, podemos ter:
interface aparelhoDeSom{
void liga(); void desliga(); void
aumentaVolume(int valor) ; void diminuiVolume(int valor);
}
class CD implements aparelhoDeSom{
“implementação de todos os métodos
especificados em aparelhoDeSom”;
toca(){...} ejetaBandejaCD(){...}
avanca(){...} retrocede(){... } ...
}
class Radio implements aparelhoDeSom{
“implementação de todos os métodos
especificados em aparelho de som”;
procuraProximaEstacao(){...}
procuraEstacaoAnterior(){...} ...
}
class K7 implements aparelhoDeSom{
“implementação de todos os métodos
especificados em aparelho de som”;
toca(){...}
}
...
aparelhoDeSom x=new CD();
x.liga(); ((CD)x).toca();
Radio r=new Radio();
x=r;
x.liga();
((Radio)x).procuraProximaEstacao();
O programa abaixo ilustra
estas idéias:
class tst{
public static void main(String[]
arg){
AP x=new CD();
x.liga();
((CD)x).toca();
x=new Radio();
x.liga();
((Radio)x).toca();
}
}
interface AP{void liga();}
class CD implements AP{
public void
liga(){System.out.println("ligaCD");}
public void
toca(){System.out.println("tocaCD");}
}
class Radio implements AP{
public void
liga(){System.out.println("ligaRadio");}
public void
toca(){System.out.println("tocaRadio");}
}
No exemplo abaixo o
comportamento de um dado é descrito por uma interface que contém duas
assinaturas de métodos correspondentes a uma função e um procedimento. O dado
pode "rolar" e "mostrar a face". Esta interface é
implementada por uma classe "honesta" e uma classe
"viciada".
interface Idado{
//Podemos rolar um dado ou podemos saber qual é a face atual
void rolar(); // sorteia nova face; um melhor nome: rolaDado()
int mostrarFace(); //devolve valor da face corrente; melhor nome:
mostraFace()
}
class
Cdado implements Idado{
private int face=geraFace();
public void rolar(){face=geraFace();};
public int mostrarFace(){return face;}
private static int geraFace(){
return (int)Math.round(Math.random()*6+0.5);
}
}
class
Cdado_viciado implements Idado{
public void rolar(){}
public int
mostrarFace(){return 2;}
}
class prog{
public static void main(String[] args){
Idado dado1, dado2, dado3;
dado1= new Cdado();
dado2= new Cdado();
dado3= new Cdado_viciado();
System.out.println("Dado 1 =
"+dado1.mostrarFace());
System.out.println("Dado 2 = "+dado2.mostrarFace());
dado1.rolar();
dado2.rolar(); dado2.rolar(); //rola o dado 2 duas
vezes!
System.out.println("Dado 1 =
"+dado1.mostrarFace());
System.out.println("Dado 2 =
"+dado2.mostrarFace());
System.out.println("Dado 3 = "+dado3.mostrarFace());
dado3.rolar();
System.out.println("Dado 3 =
"+dado3.mostrarFace());
}
}
Em uma interface podem ser
definidas constantes e métodos de instâncias (não podem ser definidos métodos
estáticos).
Considere o trecho de
programa abaixo:
interface I1{ public void m(); }
interface I2{ public void m(); }
class C implements I1, I2 {
public void m(){ System.out.println("Sou eu: m!"); }
}
class prog{
public static void main(String[] args){
I1 v1=new C();
I2 v2=new C();
v1.m();
v2.m();
}
}
Este programa compila corretamente. Os projetistas de Java se depararam com as
seguintes opções:
1) Proibir colisão de nomes de métodos;
2) Permitir a colisão: métodos de mesma assinatura são todos implementados por um
método correspondente da classe relacionada;
3) Permitir a colisão e criar um mecanismo de desambiguamento.
Os projetistas adotaram a
opção (2). Apesar da relação entre classe e interface ser de
"implementação" (a classe C implementa a interface I) devemos impor
uma certa disciplina na relação entre classe e interface. Devemos pensar sempre
que uma instância de C corresponde a uma instância de I, ou ainda todo C
"é um" I. Isto impõe uma disciplina de programação que evita o
uso da relação entre classe e interface de uma forma anômala. Esta disciplina
do “é um” talvez seja muito estrita, se quisermos flexibilizar um pouco podemos
pensar que a interface na verdade define um comportamento (através do conjunto
dos métodos) e se a classe C implementa a interface I devemos pelo menos poder
dizer que uma instância de C sabe comportar da forma I.
Parte dos problemas com
interfaces relaciona-se com o fato delas também definirem constantes.
Considere o trecho de programa abaixo:
interface I1{ int i=3; public void m(); }
interface I2{ int i=4; public void m(); }
class C implements I1,
I2 {
public void m(){ System.out.println(i); }
}
class prog{
public static
void main(String[] args){
I1
v1=new C();
I2
v2=new C();
v1.m();
v2.m();
}
}
Este programa não compila.
Os projetistas de Java definiram que não pode haver ambiguidade na referência
para constantes. Java fornece o mecanismo usual de referência a membros
estáticos e podemos construir um programa compilável:
interface
I1{ int i=3; public void
m(); }
interface I2{ int i=4; public void m(); }
class C implements I1, I2 {
public void m(){ System.out.println(I1.i); }
}
class prog{
public static void
main(String[] args){
I1 v1=new
C();
I2 v2=new
C();
v1.m();
v2.m();
}
}
No programa acima foi
escolhida a constante i da interface I1.
Abaixo
mostramos algumas destas interfaces, classes abstratas e classes.
A interface
raiz das coleções é a interface Collection:
interface Collection{
boolean add(Object o); //adiciona
um objeto
void clear(); //esvazia a coleção
boolean isEmpty(); //verifica se a
coleção está vazia
Iterator iterator(); //cria uma
instância de iterador
int size(); //retorna o número de elementos
Object[] toArray(); retorna um
arranjo de referências
…
}
O método iterator deve retornar uma referência do tipo Iterator (iterador).
Abaixo mostramos a definição da interface Iterator:
interface Iterator{
boolean hasNext();//verifica se
existe um próximo elemento
Object next(); //retorna a
referência do próximo elemento
void remove(); // remove da coleção
o elemento “corrente”
}
Os elementos prédefinidos
da linguagem Java ficam em um elemento da linguagem denominado pacote
(package). Vários dos elementos prédefinidos da linguagem Java (p.ex. classe
String) ficam em um pacote denominado java.lang e este pacote é implicitamente
importado no ambiente Java. As classes e interfaces do arcabouço “Collection”
foram definidas em um pacote denominado java.util. O pacote java.util não é
importado automaticamente no ambiente java. Um programa Java, quando quer usar
classes e interfaces deste arcabouço deve importar este pacote de forma
explícita. O programa abaixo mostra uma das formas de como pode ser feita esta
importação e também ilustra o uso das duas interfaces mostradas acima. É
utilizada a classe LinkedList para a instanciação de objetos do tipo
Collection. Esta classe será discutida mais à frente na disciplina:
import java.util.*;
class colecao{
public static void main(String[]
arg){
Collection c=new LinkedList();
c.add(new Integer(1));
c.add(new String
("abc"));
c.add(new Double(3.14));
System.out.println("Colecao
tem "+c.size()+" elementos");
for(Iterator i=c.iterator();
i.hasNext(); )
System.out.println(i.next());
}
}
Observe que no programa
acima foi feito uso da invocação implícita do método toString() na chamada do
método next(). A linha
System.out.println(i.next());
pode ser reescrita como sendo:
System.out.println(i.next().toString());
Exercícios:
1.
Faça
um programa onde CPF (apresentado
anteriormente como classe) seja definido como sendo uma interface. Defina duas
classes de implementação para a interface CPF.
2.
Faça
um programa onde PecaDomino (apresentada anteriormente como classe) seja
definida como sendo uma interface. Defina duas classes de implementação
para a interface PecaDomino.
3.
Dê
um exemplo sintético que ilustra a flexibilidade introduzida pelo uso de interfaces
como tipo das variáveis.
4.
Discuta
as semelhanças e diferenças entre interfaces e classes.