Desenvolvimento - Java

Utilizando Interfaces

As interfaces são um dos elementos que proporciona maior versatilidade ao Java. Porém, é um dos conceitos mais abstratos para quem está começando a aprender a linguagem, e muitas vezes não é utilizado todo seu potencial mesmo por programadores mais experientes.

por Vanessa Sabino



As interfaces são um dos elementos que proporciona maior versatilidade ao Java. Porém, é um dos conceitos mais abstratos para quem está começando a aprender a linguagem, e muitas vezes não é utilizado todo seu potencial mesmo por programadores mais experientes.

Antes de começar a discutir onde usá-las, vamos entender o que são. Uma interface pode ser vista como um protocolo de comportamento. Ela é simplesmente uma lista de métodos abstratos, podendo também incluir variáveis. Para utilizá-la, é criada uma classe que implemente-a. Esta classe será obrigada a definir todos os métodos das interfaces que está implementando, como mostrado no exemplo abaixo, em que temos as interfaces Leitor e Programador sendo implementadas pela classe ParticipanteForum.

Listagem 1: Classe implementando duas interfaces

    interface Leitor {
        String lendo();
    }

    interface Programador {
        void pensando(char[] ideias);
        String digitando();
    }

    class ParticipanteForum implements Leitor, Programador {
        String pensamento;
        public String lendo() { // método definido na interface Leitor
            return "Forum";
        }
        public void  pensando(char[] ideias) { // método definido na interface Programador
            pensamento = new String(ideias);
        }
        public String digitando() { // método definido na interface Programador
            return pensamento;
        }
        private String aprendendo() { // método exclusivo desta classe
            return "Java";
        }
    }

    public class Demonstracao {
        public static void main(String[] args) {
            ParticipanteForum participante = new ParticipanteForum ();  // instanciado o objeto
            Leitor leitor = participante;  // upcast para Leitor
            System.out.println("O participante está lendo " + leitor.lendo());
            Programador programador = participante; // upcast para Programador
            String java = "Java";
            programador.pensando(java.toCharArray());
            System.out.println ("E programando " + programador.digitando());
        }
    }

Neste exemplo, criamos um objeto "participante" da classe ParticipanteForum, e em seguida referenciamos este objeto por uma variável do tipo Leitor e depois do tipo Programador.

Uma analogia muito utilizada é dizer que a classe assina um contrato com a interface, se comprometendo a implementar seus métodos, e em troca os objetos desta classe poderão ser vistos como sendo do tipo definido pela interface (através de polimorfismo). De certa forma, interfaces são parecidas com classes abstratas, mas há algumas diferenças fundamentais:

  • Uma classe pode implementar várias interfaces (herança múltipla), mas apenas uma super classe;
  • Uma interface não pode implementar nenhum método, enquanto uma classe abstrata em geral possui alguns métodos concretos e outros abstratos.

Interfaces na API do Java

Dentro da API básica do J2SE encontramos vários exemplos interessantes do uso de interfaces.

O primeiro que as pessoas que estão iniciando no Java costumam encontrar é a interface Cloneable. Sua função é indicar que o método clone() pode ser utilizado para aquela classe. Na verdade, ela é necessária apenas porque o método clone() da classe Object realiza uma checagem em tempo de execução. Outro exemplo de interface de marcação é a Serializable, indicando se o objeto poderá ser serializado ou não.

Outro uso de interfaces é demonstrado no collections framework. Neste caso, temos algumas interfaces principais, como a List, Set e Map, e um maior número de classes implementando-as. Por exemplo, no caso da interface List, há quatro classes que podem ser utilizadas: AbstractList, ArrayList, LinkedList e Vector. Estas classes são agrupadas sob a mesma interface pois têm como característica comum serem uma lista ordenada de elementos. A interface define o comportamento básico que estas classes deverão implementar, tais como possuir o método add(Object o) para adicionar um elemento e o get(int index) para retornar o elemento de uma determinada posição. A diferença está em como irão realizar estas tarefas, pois cada uma está otimizada de uma forma. Por exemplo, se o programa frequentemente provê acesso aleatórioao a dados da lista, a ArrayList oferece um acesso rápido aos elementos individuais. Este acesso rápido se dá através de um custo de lentidão nas operações de adicionar e remover elementos no meio da lista. Se este segundo comportamento será o mais utilizado, então a classe LinkedList oferece uma melhor alternativa, provendo um rápido acesso sequencial, adições e exclusões, ao custo de um acesso aleatório mais lento. Mas você só precisará escolher entre uma classe ou outra na hora de instanciar o objeto, pois pode referenciá-lo através de uma variável do tipo List para utilizá-lo depois, como no exemplo abaixo:

Listagem 2: Instanciando classe LinkedList

List minhaLista = new LinkedList();
    /* O método add é parte da interface List
    e está implementado na classe LinkedList */
    minhaLista.add("elemento 1");

Aqui, criamos a variável minhaLista a partir da classe LinkedList, porém, se no decorrer do desenvolvimento percebermos que uma ArrayList teria uma melhor performance, basta trocar a classe na linha de código em que foi instanciado o objeto. Em todo resto a variável minhaLista é vista apenas como sendo do tipo List e utiliza apenas os métodos declarados nesta interface, o que poderá permanecer igual dado que ambas classes implementam a interface List. Assim, como o próprio tutorial na Sun diz, entendendo como usar as interfaces, você sabe a maior parte do framework.

Mais um exemplo interessante é o da interface ResultSet. Ela tem a função de representar uma tabela com os dados retornados da execução de uma query em um banco de dados. Métodos para manipular seus dados são declarados nesta interface, tais como next() para ir para o próximo registro e getString(String columnName) para retornar o valor de um campo. Porém, nenhuma classe incluída no J2SE implementa esta interface. O que acontece neste caso é os fabricantes dos bancos de dados implementarem o ResultSet em seus drivers. Desta forma, o desenvolvedor precisa apenas trabalhar com um objeto do tipo ResultSet, não importando o funcionamento interno da classe de cada fabricante. Novamente, as interfaces facilitando nossa vida, diminuindo a necessidade do entendimento dos detalhes das implementações individuais e permitindo uma troca de fabricante com maior facilidade. A interface de ResultSet inclui, além dos métodos, diversos campos (constantes), que são utilizados para passar parâmetros para o método que retornará o ResultSet. Estes foram apenas alguns exemplos para demonstrar os benefícios que uma interface pode trazer. Existem vários outros que podem ser estudados no J2SE.

Mas agora, vamos ver algumas considerações importantes para utilizar suas próprias interfaces.

Criando interfaces

A declaração da interface é semelhante a de classes abstratas. Elas podem ser declaradas públicas, ou então ficam com acesso padrão, ou seja, visível dentro do mesmo pacote. A herança para as interfaces funciona de forma um pouco diferente. Uma interface extends uma ou mais interfaces. Se você tentar estender uma classe ou implementar uma interface em outra interface, seu programa não irá compilar.

Todos os métodos de uma interface são implicitamente públicos e abstratos, tanto faz você colocar estes modificadores explicitamente ou não, e o compilador acusará um erro se você tentar declará-los de qualquer outra forma. Considerando que a implementação do método cabe às classes e não à interface, você também não pode utilizar modificadores como static, final, native, strictfp ou synchronized. Variáveis declaradas em interfaces sempre serão public static final, ou seja, constantes.

Ao planejar uma interface é importante lembrar-se de colocar todos os métodos que serão necessários logo no início. Aumentar a definição de métodos depois é sempre perigoso, pois poderá causar um impacto em classes que já estavam implementando a interface e agora terão que adicionar também os novos métodos para conseguirem ser compiladas.

Implementando interfaces

É necessário implementar os métodos das interfaces apenas na primeira classe concreta da hierarquia. Desta forma, classes abstratas têm a opção de implementá-los ou não. Todos os métodos herdados de interfaces devem ser declarados public, seguindo as regras tradicionais de herança (não é permitido diminuir o acesso em uma classe filha). De forma similar, os métodos da classe filha não podem declarar exceções que estejam fora do escopo da exceção do método da interface. Por exemplo, se o método da interface lançar uma Exception, o método implementado poderá estar lançando qualquer exceção. Já se na interface a declaração for mais específica, lançando uma SQLException, o compilador acusará um erro ao tentar implementar o método lançando uma IOException ou uma Exception genérica. No caminho inverso não há problema, os métodos das classes podem deixar de lançar alguma exceção que esteja declarada na interface.

Quando usar

A aplicação mais óbvia das interfaces é para simular herança múltipla, mas não é a única, como visto nos exemplos do próprio J2SE.

No primeiro exemplo (Cloneable) vimos interfaces de marcação. Na prática, você pode querer criar um método e restringir seu uso a classes que aleguem ter uma certa conscientização sobre seu funcionamento, "assinando o contrato". Então você obriga que esta classe implemente a interface utilizando um código semelhante a:

Listagem 3: Implementação de método com geração de exceção

public void metodoRestrito(Object obj) throws Exception
    if (obj instanceof InterfaceMarcacao) {
        // Codigo realizado pelo método
    } else {
        throw new Exception ("Favor assinar o termo de responsabilidade");
    }

Os outros exemplos mostram as vantagens mais importante de interfaces. Seu uso desde o início de um projeto pode poupar muito tempo no futuro. Criando uma interface para os métodos principais de uma classe e utilizando esta interface para acessá-los torna mais fácil o processo de manutenção do código caso esta classe precise ser substituída posteriormente, como demonstrado no exemplo do collections framework.

Além disso, interfaces também podem deixar seu código mais reusável, uma vez que uma única classe pode trabalhar com várias outras através de uma única interface. No caso do ResultSet vimos que é possível beneficiar-se de seus métodos sem ao menos saber que classe estamos usando. Desta forma, é mais fácil dividir a tarefa entre vários desenvolvedores de um projeto. Desde que todos respeitem as interfaces, a integração entre o trabalho de cada um ocorrerá sem grandes problemas.

No próprio modelo MVC, ter uma interface comum para as actions da camada model facilita muito o desenvolvimento do controller. Cada action implementa uma interface em comum, e então o controller chama os métodos diretamente dela.

As interfaces podem ser utilizadas até para tarefas mais simples, como declarar constantes a serem utilizadas por outras classes, visto que todos seus campos são sempre public static final. Por exemplo, criamos a interface Enderecos:

Listagem 4: Interface Enderecos

public interface Enderecos
    {
        String linhaDeCodigo = "http://www.linhadecodigo.com";
        String alemDoJava = "http://www.java.blogger.com.br";
    }

E depois para utilizar o endereço em outra classe basta colocar Enderecos.alemDoJava ou Enderecos.linhaDeCodigo.

Conclusões

Interfaces são uma ferramenta importante da linguagem Java, melhorando a qualidade do seu código quando bem utilizadas.

Elas permitem um encapsulamento de comportamento, ocultando qual classe está realizando uma tarefa específica. Isso é possível porque a classe que implementa uma interface é obrigada a seguir o protocolo definido nesta.

Desta forma, as principais vantagens conseguidas através de seu uso são uma manutenção mais simples do código e uma maior reusabilidade do mesmo, aproveitando melhor os benefícios da programação orientada a objetos.

Vanessa Sabino

Vanessa Sabino