HOWTO · Java
Estenda duas classes em Java
Saiba por que uma classe Java só pode estender uma classe e como interfaces e composição combinam comportamentos de vários tipos.
Nesta página
Uma classe Java não pode estender duas classes. Cada classe tem uma única superclasse direta; por isso, uma declaração como class Child extends First, Second não é válida em Java. Para combinar comportamentos de vários tipos, estenda uma classe e implemente quantas interfaces forem necessárias, ou mantenha referências a outros objetos e delegue tarefas a eles usando composição.
Por que uma classe Java não pode estender duas classes
Java usa herança simples para classes: cada classe pode indicar no máximo uma superclasse com extends. Assim, um objeto tem uma única cadeia de herança de classes, o que evita dúvidas sobre qual implementação ou estado da superclasse deve prevalecer quando dois pais definem o mesmo membro. O compilador rejeita uma declaração de classe que liste duas superclasses; essa é uma restrição verificada durante a compilação, não uma falha que ocorre enquanto o programa está em execução.
A sintaxe a seguir é intencionalmente inválida em Java e não compila: class Child extends First, Second {}. Use uma única superclasse quando a nova classe for realmente uma especialização desse tipo. A próxima seção mostra como contratos de interface podem descrever capacidades adicionais.
Use uma superclasse e implemente várias interfaces
Uma classe pode estender uma classe e implementar várias interfaces. A superclasse pode fornecer estado ou implementação compartilhados, enquanto cada interface declara uma capacidade que a classe se compromete a oferecer. As palavras-chave de Java são importantes: uma classe usa extends para sua superclasse e implements para interfaces. Uma interface pode estender várias interfaces, mas isso não dá a uma classe várias superclasses.
Este exemplo completo define dois contratos de interface e os implementa junto com uma superclasse:
public class CatalogItem extends Record implements Printable, Identifiable {
CatalogItem(String name) {
super(name);
}
@Override
public void printLabel() {
System.out.println(name());
}
@Override
public String id() {
return "item-" + name().toLowerCase();
}
public static void main(String[] args) {
CatalogItem item = new CatalogItem("Book");
item.printLabel();
System.out.println(item.id());
}
}
interface Printable {
void printLabel();
}
interface Identifiable {
String id();
}
class Record {
private final String name;
Record(String name) {
this.name = name;
}
String name() {
return name;
}
}
Book
item-book
CatalogItem herda de Record a implementação de name() e fornece os métodos exigidos pelas duas interfaces. Interfaces são úteis quando classes sem relação entre si precisam compartilhar um tipo ou contrato de capacidade. Elas não fornecem automaticamente estado nem uma implementação de classe como faria uma segunda superclasse.
Combine comportamentos de classes usando composição
A composição combina objetos por meio de uma relação de “tem um”: uma classe armazena referências a colaboradores e chama seus métodos. Isso é útil quando os comportamentos vêm de classes que também são úteis por conta própria ou quando o colaborador precisa poder ser substituído. A classe que contém os objetos controla quais operações expõe e pode delegar apenas o trabalho de que seus chamadores precisam.
Neste exemplo, SmartDevice contém um PowerController e um MessageSender e delega operações a eles:
public class SmartDevice {
private final PowerController power = new PowerController();
private final MessageSender sender = new MessageSender();
void start() {
power.powerOn();
}
void sendMessage(String message) {
sender.send(message);
}
public static void main(String[] args) {
SmartDevice device = new SmartDevice();
device.start();
device.sendMessage("Ready");
}
}
class PowerController {
void powerOn() {
System.out.println("Power on");
}
}
class MessageSender {
void send(String message) {
System.out.println("Sending: " + message);
}
}
Power on
Sending: Ready
SmartDevice não se torna um PowerController nem um MessageSender; ele usa ambos. Em um programa maior, os colaboradores podem ser fornecidos por meio de um construtor ou representados por interfaces, o que facilita substituir implementações e testar a classe que os contém de forma independente.
Escolha o padrão de reutilização adequado
Escolha herança de classes quando o novo tipo for realmente uma especialização de uma única superclasse e precisar herdar sua implementação. Acrescente interfaces quando um tipo precisar declarar capacidades compartilhadas com outras classes. Use composição quando os comportamentos precisarem ser montados a partir de partes, delegados ou alterados de forma independente.
Mantenha cada classe concentrada em uma responsabilidade clara e exponha apenas as operações de que seus chamadores precisam. Prefira contratos de interface pequenos a contratos abrangentes e evite duplicar comportamentos que deveriam pertencer a um colaborador compartilhado. Essas escolhas tornam as relações mais fáceis de entender, testar e adaptar quando os requisitos mudam. Em resumo, Java não permite que uma classe estenda duas classes; uma superclasse, vários contratos de interface e colaboradores compostos atendem às necessidades comuns de reutilização.