Desafio Java
Interface vs classe abstrata e herança múltipla de tipo
Enunciado
Modele um sistema com a classe abstrata Animal (com atributo nome e método abstrato emitirSom()) e as interfaces Corredor (método correr()) e Nadador (método nadar()). Crie as classes Cachorro (herda Animal, implementa Corredor e Nadador) e Peixe (herda Animal, implementa apenas Nadador). No main, crie um array de Animal, percorra-o, chame emitirSom() e, usando instanceof com pattern matching, chame os métodos específicos quando aplicável.
Requisitos
Animaldeve ser uma classe abstrata com atributonome, construtor e método abstratoemitirSom().CorredoreNadadordevem ser interfaces com os métodoscorrer()enadar(), respectivamente.Cachorrodeve herdar deAnimale implementarCorredoreNadador;Peixedeve herdar deAnimale implementar apenasNadador.- O
maindeve percorrer um array deAnimale usarinstanceofcom pattern matching para chamar métodos específicos, imprimindo mensagens claras.
Código inicial
abstract class Animal {
protected String nome;
Animal(String nome) {
this.nome = nome;
}
abstract void emitirSom();
}
interface Corredor {
void correr();
}
interface Nadador {
void nadar();
}
// TODO: implemente Cachorro e Peixe
public class Main {
public static void main(String[] args) {
// TODO: crie o array e percorra
}
}
Saída esperada
Cachorro Rex faz: Au au!
Rex esta correndo.
Rex esta nadando.
Peixe Nemo faz: Glub glub!
Nemo esta nadando.
Ver dica
Use instanceof Corredor c para verificar e já obter a variável tipada. Lembre-se de que a ordem das verificações pode importar se uma classe implementar múltiplas interfaces.
Mostrar solução
abstract class Animal {
protected String nome;
Animal(String nome) {
this.nome = nome;
}
abstract void emitirSom();
}
interface Corredor {
void correr();
}
interface Nadador {
void nadar();
}
class Cachorro extends Animal implements Corredor, Nadador {
Cachorro(String nome) {
super(nome);
}
@Override
void emitirSom() {
System.out.println("Cachorro " + nome + " faz: Au au!");
}
@Override
public void correr() {
System.out.println(nome + " esta correndo.");
}
@Override
public void nadar() {
System.out.println(nome + " esta nadando.");
}
}
class Peixe extends Animal implements Nadador {
Peixe(String nome) {
super(nome);
}
@Override
void emitirSom() {
System.out.println("Peixe " + nome + " faz: Glub glub!");
}
@Override
public void nadar() {
System.out.println(nome + " esta nadando.");
}
}
public class Main {
public static void main(String[] args) {
Animal[] animais = { new Cachorro("Rex"), new Peixe("Nemo") };
for (Animal animal : animais) {
animal.emitirSom(); // método abstrato implementado
// Pattern matching para interfaces
if (animal instanceof Corredor c) {
c.correr();
}
if (animal instanceof Nadador n) {
n.nadar();
}
}
}
}
Passo a passo
- A classe abstrata
Animaldefine o estado comumnomee o método abstratoemitirSom(). Construtor inicializa o nome. - As interfaces
CorredoreNadadordefinem contratos separados para habilidades específicas. CachorroestendeAnimal(herança de código e estado) e implementaCorredoreNadador(herança múltipla de tipo). Implementa todos os métodos.PeixeestendeAnimale implementa apenasNadador, pois peixes não correm.- No
main, criamos um array deAnimalcom instâncias deCachorroePeixe. O polimorfismo permite tratá-los comoAnimal. - No loop, chamamos
emitirSom(); cada objeto executa sua versão. - Usamos
instanceof Corredor ceinstanceof Nadador npara verificar se o objeto implementa a interface e, em caso positivo, chamar o método correspondente. O pattern matching já cria a variável tipada.
Por que funciona
A classe abstrata fornece a base comum (nome e som), enquanto as interfaces permitem que classes adquiram capacidades adicionais sem se prender a uma única hierarquia. Cachorro pode ser um Animal, um Corredor e um Nadador ao mesmo tempo. O instanceof com pattern matching permite verificar e usar essas capacidades de forma segura e concisa.
Erros comuns
- Tentar herdar de múltiplas classes:
class Cachorro extends Animal, Corredornão compila; Java só permite uma superclasse. Useimplementspara interfaces. - Esquecer de implementar todos os métodos abstratos: se
Peixenão implementarnadar(), o compilador exige que a classe seja abstrata. - Usar
instanceofsem pattern matching e depois fazer cast manual desnecessário:if (animal instanceof Corredor) { ((Corredor) animal).correr(); }funciona, mas é mais verboso e propenso a erros. Prefirainstanceof Corredor c. - Ordem incorreta de verificações: se uma classe implementa várias interfaces, verifique todas as que deseja chamar, não use
else ifa menos que queira apenas a primeira.
Outra forma de resolver
Poderia usar uma única interface Animal com métodos default para correr e nadar, mas isso forçaria todas as classes a ter esses métodos, mesmo que não façam sentido. A combinação de classe abstrata com interfaces segregadas é mais limpa e segue o princípio da segregação de interfaces.
Saída esperada
Cachorro Rex faz: Au au!
Rex esta correndo.
Rex esta nadando.
Peixe Nemo faz: Glub glub!
Nemo esta nadando.