Olá! Hoje falaremos sobre um conceito importante em Java: interfaces. A palavra provavelmente é familiar para você. Por exemplo, a maioria dos programas de computador e jogos possuem interfaces. Num sentido amplo, uma interface é uma espécie de “controle remoto” que conecta duas partes interagindo entre si. Um exemplo simples de interface da vida cotidiana é o controle remoto de uma TV. Ele conecta dois objetos, uma pessoa e uma TV, e realiza diversas tarefas: aumentar ou diminuir o volume, mudar de canal, ligar ou desligar a TV. Um lado (a pessoa) precisa acessar a interface (pressionar o botão do controle remoto) para que o outro lado execute a ação. Por exemplo, para a TV mudar de canal para o próximo. Nesse caso, o usuário não precisa conhecer o dispositivo da TV e como é implementado o processo de mudança de canal dentro dela. Tudo o que o usuário tem acesso é a interface . A principal tarefa é obter o resultado desejado. O que isso tem a ver com programação e Java? Direto :) Criar uma interface é muito semelhante a criar uma classe normal, só que em vez da palavra
class
especificamos a palavra interface
. Vamos dar uma olhada na interface Java mais simples e descobrir como ela funciona e para que é necessária:
public interface Swimmable {
public void swim();
}
Criamos uma interface Swimmable
que pode nadar . É algo parecido com o nosso controle remoto, que possui um “botão”: o método swim()
é “nadar”. Como podemos usar esse “ controle remoto ”? Para este propósito, o método, ou seja, o botão do nosso controle remoto precisa ser implementado. Para utilizar uma interface, seus métodos devem ser implementados por algumas classes do nosso programa. Vamos criar uma classe cujos objetos se encaixem na descrição “sabe nadar”. Por exemplo, a classe pato é adequada Duck
:
public class Duck implements Swimmable {
public void swim() {
System.out.println("Duck, swim!");
}
public static void main(String[] args) {
Duck duck = new Duck();
duck.swim();
}
}
O que vemos aqui? Uma classe Duck
está associada a uma interface Swimmable
usando a palavra-chave implements
. Se você se lembra, usamos um mecanismo semelhante para conectar duas classes em herança, só que havia a palavra “ estende ”. “ public class Duck implements Swimmable
” pode ser traduzido literalmente para maior clareza: “uma classe pública Duck
implementa a interface Swimmable
.” Isto significa que uma classe associada a uma interface deve implementar todos os seus métodos. Observação: em nossa classe, Duck
assim como na interface , Swimmable
existe um método swim()
e dentro dele existe algum tipo de lógica. Este é um requisito obrigatório. Se apenas escrevêssemos “ public class Duck implements Swimmable
” e não criássemos um método swim()
na classe Duck
, o compilador nos daria um erro: Duck não é abstrato e não substitui o método abstrato swim() em Swimmable Por que isso acontece? Se explicarmos o erro usando o exemplo de uma TV, descobrimos que estamos dando a uma pessoa um controle remoto com botão “mudar de canal” de uma TV que não sabe mudar de canal. Neste ponto, pressione o botão o quanto quiser, nada funcionará. O próprio controle remoto não muda de canal: apenas dá um sinal para a TV, dentro da qual é implementado um complexo processo de mudança de canal. O mesmo acontece com o nosso pato: ele deve saber nadar para poder ser acessado pela interface Swimmable
. Se ela não souber fazer isso, a interface Swimmable
não conectará os dois lados - a pessoa e o programa. Uma pessoa não será capaz de usar um método swim()
para fazer um objeto Duck
flutuar dentro de um programa. Agora você viu com mais clareza para que servem as interfaces. Uma interface descreve o comportamento que as classes que implementam essa interface devem ter. “Comportamento” é uma coleção de métodos. Se quisermos criar vários mensageiros, a maneira mais fácil de fazer isso é criando uma interface Messenger
. O que qualquer mensageiro deveria ser capaz de fazer? De forma simplificada, receba e envie mensagens.
public interface Messenger{
public void sendMessage();
public void getMessage();
}
E agora podemos simplesmente criar nossas classes de mensageiro implementando esta interface. O próprio compilador nos “obrigará” a implementá-los dentro das classes. Telegrama:
public class Telegram implements Messenger {
public void sendMessage() {
System.out.println("Sending a message to Telegram!");
}
public void getMessage() {
System.out.println("Reading the message in Telegram!");
}
}
Whatsapp:
public class WhatsApp implements Messenger {
public void sendMessage() {
System.out.println("Sending a WhatsApp message!");
}
public void getMessage() {
System.out.println("Reading a WhatsApp message!");
}
}
Viber:
public class Viber implements Messenger {
public void sendMessage() {
System.out.println("Sending a message to Viber!");
}
public void getMessage() {
System.out.println("Reading a message in Viber!");
}
}
Que benefícios isso proporciona? O mais importante deles é o acoplamento fraco. Imagine que estamos projetando um programa no qual coletaremos dados de clientes. A classe Client
deve possuir um campo indicando qual mensageiro o cliente utiliza. Sem interfaces pareceria estranho:
public class Client {
private WhatsApp whatsApp;
private Telegram telegram;
private Viber viber;
}
Criamos três campos, mas um cliente pode facilmente ter apenas um mensageiro. Só não sabemos qual. E para não ficar sem comunicação com o cliente, é preciso “empurrar” todas as opções possíveis para a aula. Acontece que um ou dois deles sempre estarão lá null
e não são necessários para que o programa funcione. Em vez disso, é melhor usar nossa interface:
public class Client {
private Messenger messenger;
}
Este é um exemplo de “acoplamento fraco”! Em vez de especificar uma classe de mensageiro específica na classe Client
, simplesmente mencionamos que o cliente possui um mensageiro. Qual deles será determinado durante o curso do programa. Mas por que precisamos de interfaces para isso? Por que eles foram adicionados ao idioma? A pergunta é boa e correta! O mesmo resultado pode ser alcançado usando herança comum, certo? A classe Messenger
é a classe pai e , Viber
e Telegram
são WhatsApp
os herdeiros. Na verdade, é possível fazê-lo. Mas há um problema. Como você já sabe, não existe herança múltipla em Java. Mas existem várias implementações de interfaces. Uma classe pode implementar quantas interfaces desejar. Imagine que temos uma turma Smartphone
que possui um campo Application
- um aplicativo instalado em um smartphone.
public class Smartphone {
private Application application;
}
O aplicativo e o mensageiro são, obviamente, semelhantes, mas ainda assim são coisas diferentes. O Messenger pode ser móvel e desktop, enquanto o Application é um aplicativo móvel. Portanto, se utilizássemos herança, não poderíamos adicionar um objeto Telegram
à classe Smartphone
. Afinal, uma classe Telegram
não pode herdar de Application
e de Messenger
! E já conseguimos herdá-lo Messenger
e adicioná-lo à classe neste formato Client
. Mas uma classe Telegram
pode implementar facilmente ambas as interfaces! Portanto, em uma classe Client
podemos implementar um objeto Telegram
como Messenger
e em uma classe Smartphone
como Application
. Veja como isso é feito:
public class Telegram implements Application, Messenger {
//...methods
}
public class Client {
private Messenger messenger;
public Client() {
this.messenger = new Telegram();
}
}
public class Smartphone {
private Application application;
public Smartphone() {
this.application = new Telegram();
}
}
Agora podemos usar a classe Telegram
como quisermos. Em algum lugar ele atuará no papel de Application
, em algum lugar no papel de Messenger
. Você provavelmente já percebeu que os métodos nas interfaces estão sempre “vazios”, ou seja, não possuem implementação. A razão para isso é simples: uma interface descreve o comportamento, não o implementa. “Todos os objetos de classes que implementam a interface Swimmable
devem poder flutuar”: isso é tudo que a interface nos diz. Como exatamente um peixe, pato ou cavalo nadará é uma questão para as classes Fish
, Duck
e Horse
, e não para a interface. Assim como mudar de canal é tarefa de uma TV. O controle remoto simplesmente oferece um botão para fazer isso. No entanto, Java8 tem uma adição interessante: métodos padrão. Por exemplo, sua interface possui 10 métodos. 9 deles são implementados de forma diferente em classes diferentes, mas um é implementado da mesma forma em todas. Anteriormente, antes do lançamento do Java8, os métodos dentro das interfaces não tinham nenhuma implementação: o compilador gerava imediatamente um erro. Agora você pode fazer assim:
public interface Swimmable {
public default void swim() {
System.out.println("Swim!");
}
public void eat();
public void run();
}
Usando a palavra-chave default
, criamos um método na interface com uma implementação padrão. Precisaremos implementar os outros dois métodos, eat()
e run()
nós mesmos, em todas as classes que implementaremos Swimmable
. Não há necessidade de fazer isso com o método swim()
: a implementação será a mesma em todas as classes. A propósito, você já se deparou com interfaces mais de uma vez em tarefas anteriores, embora não tenha percebido :) Aqui está um exemplo óbvio: Você trabalhou com interfaces List
e Set
! Mais precisamente, com suas implementações - ArrayList
, LinkedList
e HashSet
outros. O mesmo diagrama mostra um exemplo quando uma classe implementa várias interfaces ao mesmo tempo. Por exemplo, LinkedList
ele implementa as interfaces List
e Deque
(fila dupla face). Você também está familiarizado com a interface Map
, ou melhor, com suas implementações - HashMap
. A propósito, neste diagrama você pode ver uma característica: as interfaces podem ser herdadas umas das outras. A interface SortedMap
é herdada de Map
e Deque
é herdada de queue Queue
. Isto é necessário se você quiser mostrar a conexão entre interfaces, mas uma interface é uma versão estendida de outra. Vejamos um exemplo com uma interface Queue
- uma fila. Ainda não examinamos as coleções Queue
, mas elas são bastante simples e organizadas como uma linha normal de uma loja. Você pode adicionar elementos apenas ao final da fila e retirá-los apenas do início. A certa altura, os desenvolvedores precisavam de uma versão expandida da fila para que elementos pudessem ser adicionados e recebidos de ambos os lados. Foi assim que uma interface foi criada Deque
- uma fila bidirecional. Ele contém todos os métodos de uma fila regular, porque é o “pai” de uma fila bidirecional, mas novos métodos foram adicionados.
GO TO FULL VERSION