Por que não há Herança Múltipla de Classes
Parte da seção Programação Orientada a Objetos do Journey de Java da Coddy — lição 26 de 87.
O Java impede deliberadamente que uma classe estenda mais de uma classe. Essa restrição existe devido a um problema conhecido como o Problema do Diamante.
Imagine se o Java permitisse herança múltipla. Considere este cenário hipotético:
// Este código NÃO é válido em Java
public class A {
public void greet() {
System.out.println("Hello from A");
}
}
public class B extends A {
@Override
public void greet() {
System.out.println("Hello from B");
}
}
public class C extends A {
@Override
public void greet() {
System.out.println("Hello from C");
}
}
// NÃO é permitido em Java!
public class D extends B, C { }Se você chamasse greet() em um objeto D, qual versão deveria ser executada? A de B ou a de C? Essa ambiguidade é o Problema do Diamante, nomeado após o formato de diamante formado pelo diagrama de herança.
A solução do Java é simples: permitir apenas a herança única de classes. No entanto, o Java fornece uma alternativa. Uma classe pode implementar múltiplas interfaces, sobre as quais você aprenderá em um capítulo futuro.
Interfaces resolvem o problema do diamante de forma diferente e oferecem flexibilidade sem a ambiguidade.
// Isso É válido em Java
public class Dog extends Animal implements Runnable, Comparable<Dog> {
// Pode implementar múltiplas interfaces
}Essa escolha de design mantém o modelo de herança do Java limpo e previsível, ao mesmo tempo em que permite uma poderosa reutilização de código através de interfaces.
Desafio
FácilVamos construir um sistema de player de música que demonstra a regra de herança única do Java e mostra como você ainda pode alcançar designs flexíveis dentro dessa restrição.
Imagine que você deseja criar um alto-falante inteligente (smart speaker) que combina recursos de um player de música e de um assistente de voz. Como o Java não permite estender múltiplas classes, você precisará projetar sua hierarquia cuidadosamente usando herança única.
Você criará quatro arquivos para organizar seu código:
AudioDevice.java: Crie a classe base que todos os dispositivos de reprodução de áudio compartilham. Ela deve ter:- Um campo privado para
deviceName(String) - Um construtor que aceita o nome do dispositivo
- Um método
getDeviceName()que retorna o nome - Um método
playSound()que imprime:[deviceName]: Playing audio
- Um campo privado para
MusicPlayer.java: Crie uma classe que estende AudioDevice e adiciona recursos específicos de música:- Um campo privado para
currentSong(String) - Um construtor que recebe o nome do dispositivo e usa
super(deviceName) - Um método
setSong(String song)que define a música atual - Sobrescreva
playSound()para imprimir:[deviceName]: Now playing - [currentSong]
- Um campo privado para
SmartSpeaker.java: Crie uma classe que estende MusicPlayer (não ambos MusicPlayer e alguma classe VoiceAssistant - isso seria herança múltipla!). O smart speaker baseia-se no player de música adicionando recursos de voz:- Um campo privado para
assistantName(String) - Um construtor que recebe o nome do dispositivo e o nome do assistente, usando
super(deviceName) - Um método
voiceCommand(String command)que imprime:[assistantName]: Processing "[command]" - Um método
getFullInfo()que imprime:[deviceName] with [assistantName] assistant
- Um campo privado para
Main.java: Demonstre sua cadeia de herança multinível. Você receberá três entradas: um nome de dispositivo (String), um nome de assistente (String) e um nome de música (String). Crie um SmartSpeaker, defina uma música e, em seguida, chame estes métodos em ordem:getFullInfo()playSound()voiceCommand("skip to next")
Você receberá três entradas: o nome do dispositivo, o nome do assistente e o nome da música.
Observe como o SmartSpeaker ganha recursos de música ao estender MusicPlayer em uma cadeia (AudioDevice -> MusicPlayer -> SmartSpeaker), em vez de tentar estender várias classes não relacionadas. Esta é a abordagem do Java para manter a herança limpa e inequívoca!
Folha de consulta
O Java permite apenas herança única - uma classe pode estender apenas uma outra classe. Isso evita o Problema do Diamante, onde surge ambiguidade se uma classe pudesse herdar o mesmo método de múltiplas classes pai.
// NÃO permitido em Java - herança múltipla
public class D extends B, C { } // ERRO!No entanto, o Java permite que uma classe implemente múltiplas interfaces como uma alternativa:
// Válido em Java
public class Dog extends Animal implements Runnable, Comparable<Dog> {
// Pode estender uma classe e implementar múltiplas interfaces
}O Java suporta herança multinível - criando cadeias de herança:
public class AudioDevice { }
public class MusicPlayer extends AudioDevice { }
public class SmartSpeaker extends MusicPlayer { }
// SmartSpeaker herda tanto de MusicPlayer quanto de AudioDeviceExperimente você mesmo
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// Ler as entradas
String deviceName = scanner.nextLine();
String assistantName = scanner.nextLine();
String songName = scanner.nextLine();
// TODO: Criar um SmartSpeaker com deviceName e assistantName
// TODO: Definir a música usando setSong()
// TODO: Chamar getFullInfo()
// TODO: Chamar playSound()
// TODO: Chamar voiceCommand("skip to next")
}
}
Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.
Todas as lições de Programação Orientada a Objetos
1Fundamentos de POO
Arquivos ExternosIntrodução à POOClasses vs ObjetosA Palavra-chave thisMétodosCampos (Atributos)Método ConstrutorSobrecarga de ConstrutorRecapitulação - Calculadora Simples4Herança
Herança Básica (extends)A Palavra-chave superSobrescrita de Método (@Override)Encadeamento de ConstrutoresA Classe ObjectHerança Simples e MultinívelPor que não há Herança Múltipla de ClassesRecapitulação - Hierarquia de Funcionários7Métodos Especiais e a Classe Object
Método toString()equals() e hashCode()Método clone()compareTo() e ComparableInterface ComparatorRecapitulação - Ordenação Personalizada2Modificadores de Acesso e Encapsulamento
Visão Geral dos Níveis de AcessoMétodos Getter e SetterOcultação de InformaçõesA Palavra-chave finalRecapitulação - Gerenciador de Conta Bancária5Polimorfismo
Fundamentos de Sobrecarga de MétodosSobrescrita de Métodos (Tempo de Execução)Upcasting e DowncastingO Operador instanceofClasses e Métodos AbstratosRecapitulação - Calculadora de Formas8Conceitos Avançados de OOP
Composição vs HerançaAgregação vs ComposiçãoClasses Internas, Aninhadas e AnônimasEnums e Métodos de EnumRecords (Java 16+)Classes Seladas (Java 17+)11Padrões de Projeto Parte 1
Introdução aos Padrões de ProjetoPadrão SingletonPadrão FactoryPadrão BuilderPadrão ObserverPadrão Strategy3Propriedades de Classe e Membros Estáticos
Variáveis de Instância vs EstáticasMétodos EstáticosBlocos EstáticosConstantes (static final)Recapitulação - Contador e Utilitário6Interfaces e Classes Abstratas
Introdução às InterfacesImplementando InterfacesImplementação de Múltiplas InterfacesDefault e Static em InterfacesClasses Abstratas vs InterfacesInterfaces FuncionaisRecapitulação - Sistema de Pagamento9Generics
Introdução a GenericsClasses GenéricasMétodos GenéricosParâmetros de Tipos DelimitadosWildcards (?, extends, super)Recapitulação - Container Genérico12Padrões de Projeto Parte 2
Padrão CommandPadrão AdapterPadrão DecoratorPadrão Template MethodPadrão StatePadrão CompositePadrão Iterator