Classes implementam interfaces


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.

 

O arcabouço “Collections”  da linguagem Java

Conforme já foi mencionado, o último material desta disciplina (Listas) está relacionado a um arcabouço (framework) da linguagem Java denominado Coleções (Collections). Este arcabouço possui vários elementos relacionados ao problema de tratamento de grupos (p.ex. conjuntos, multiconjuntos, listas) de elementos. Várias das classes deste arcabouço são definidas a partir de outras classes e muitas destas classes são definidas de forma abstrata, além disso este arcabouço faz uso de várias interfaces.

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.