Olá! Meu nome é Jacqueline Oliveira, sou engenheira de software e instrutora aqui na Alura. Quero dar as boas-vindas a este curso sobre herança, polimorfismo e interfaces.
Audiodescrição: Jacqueline se descreve como uma mulher de pele branca, com cabelos longos e reflexos louros. Veste uma blusa rosa, e a parede ao fundo está iluminada em azul.
Neste curso, abordaremos os pilares de herança e polimorfismo e sua importância na programação orientada a objetos. Discutiremos como utilizar as classes abstratas e o papel desse tipo de classe no contexto de herança e polimorfismo. Exploraremos o polimorfismo de sobrecarga e de sobrescrita. Por fim, analisaremos a utilização, definição e os pontos positivos do uso de interfaces.
Este é um conteúdo extremamente relevante para quem está praticando programação orientada a objetos. Nos encontraremos em breve para iniciar essa prática!
Vamos iniciar nossos estudos e práticas abordando o conceito de herança!
A herança permite que uma classe filha aproveite atributos e métodos de uma classe pai. Teremos uma classe que conterá informações generalistas sobre uma classe similar, como, por exemplo, funcionários. Funcionários possuem informações em comum, como salário e nome, e podemos ter classes com especializações adicionais, como gerente e desenvolvedor.
No contexto de uma empresa que desenvolve software, essas classes filhas herdarão todos os atributos e métodos da classe funcionário e terão suas especializações.
Por exemplo, um gerente terá um bônus que nem todo funcionário possui. Já um desenvolvedor pode ter uma linguagem de programação, formação, pós-graduação ou cursos extras que, talvez, não sejam relevantes para um gerente. Assim, podemos criar uma classe generalista e suas subclasses especializadas.
Funcionario
Vamos aplicar isso na prática. No IntelliJ, temos uma classe Funcionario
para representar o exemplo mencionado, contendo apenas nome e salário como atributos. Há, ainda, um método construtor que recebe nome e salário para criar uma nova instância de Funcionario
.
Além disso, existem dois métodos: exibirInformacoes
, que exibe as informações de nome e salário, e reajustarSalario
, que ajusta o salário com base em um percentual fornecido.
package br.com.alura;
public class Funcionario {
private String nome;
private double salario;
public Funcionario(String nome, double salario) {
this.nome = nome;
this.salario = salario;
}
public void exibirInformacoes() {
System.out.printf("\nFuncionario %s - Salário: %.2f",
nome, salario);
}
public void reajustarSalario(double percentual) {
salario += salario * (percentual / 100);
System.out.printf("\nNovo salario de %s é %.2f ", nome, salario);
}
}
Gerente
Nosso objetivo é criar especializações de Funcionario
. Queremos uma especialização para gerente e outra para desenvolvedor, mas não faria sentido criar uma nova classe repetindo nome, salário e todos os métodos novamente. A herança serve justamente para evitar essa duplicação de código. Assim, conseguimos organizar nossa aplicação sem replicar código desnecessariamente.
Vamos criar uma classe Gerente
que herdará as características de Funcionario
. Para isso, no nosso pacote br.com.alura
, utilizaremos o atalho "Alt + Insert" para criar uma nova classe Java chamada Gerente
.
package br.com.alura;
public class Gerente {
}
Agora, precisamos indicar que Gerente
é um Funcionario
. O termo "é um" é crucial para definir uma herança. Para afirmar que uma herança é bem-sucedida e aplicada corretamente, precisamos dizer que uma classe é um tipo da outra. Assim, um gerente é um funcionário, o que valida a herança.
Para que Gerente
herde de Funcionario
, usamos a palavra reservada extends
.
public class Gerente extends Funcionario {
}
Ao fazer isso, o IntelliJ sublinha essa linha em vermelho, pois, ao afirmar que Gerente
estende Funcionario
, e considerando que Funcionario
possui um construtor que exige nome e salário para criar uma instância, precisamos passar as mesmas informações ao criar um Gerente
.
O IntelliJ sugere a opção "Create constructor matching super", que cria um construtor compatível com a superclasse. Ao clicar nessa opção, ele cria um construtor para Gerente
, onde precisamos passar nome e salário.
public class Gerente extends Funcionario {
public Gerente(String nome, double salario) {
super(nome, salario);
}
}
Na classe Gerente
, incluiremos o atributo adicional bonus
.
public class Gerente extends Funcionario {
private double bonus;
public Gerente(String nome, double salario) {
super(nome, salario);
}
}
Então, temos algumas alternativas para definir esse bônus. Uma opção seria passá-lo diretamente no construtor, adicionando mais um parâmetro. Outra possibilidade é criar os métodos assessores, conhecidos como get
e set
.
Neste exemplo específico, optaremos por usar os métodos get
e set
, em vez de incluir o bônus no construtor. Para facilitar, utilizaremos a própria IDE na criação desses métodos. Basta pressionar "Alt + Insert" dentro da classe, selecionar a opção "Getter and setter" e escolher a propriedade do bônus.
public class Gerente extends Funcionario {
private double bonus;
public Gerente(String nome, double salario) {
super(nome, salario);
}
public double getBonus() {
return bonus;
}
public void setBonus(double bonus) {
this.bonus = bonus;
}
}
Pronto, os métodos get
e set
já foram gerados automaticamente.
Gerente
Com essas informações, vamos entender como criar uma nova instância de Gerente
. Na classe principal, o procedimento será muito semelhante ao que já fizemos com funcionários em cursos anteriores. Primeiro, declaramos o tipo, que é Gerente
, definimos o nome da variável, que também será gerente
, e a instanciamos com new Gerente
.
Na criação da instância, precisamos passar o nome, como "Mario", e o salário, como 15000, por exemplo.
public class Principal {
public static void main(String[] args) {
Gerente gerente = new Gerente("Mario", 15000);
}
}
Feito isso, ao chamar algo como gerente.
, o sistema já me permite acessar o método exibirInformações
. Mas repare: na classe Gerente
, não temos esse método exibirInformações
. Isso acontece porque ele está sendo herdado diretamente da classe Funcionário
.
public class Principal {
public static void main(String[] args) {
Gerente gerente = new Gerente("Mario", 15000);
gerente.exibirInformações();
}
}
Então, se executaramos a aplicação agora, obteremos o seguinte:
Funcionario Mario - Salário: 15000,00
Podemos aplicar o mesmo procedimento para reajuste.
public class Principal {
public static void main(String[] args) {
Gerente gerente = new Gerente("Mario", 15000);
gerente.exibirInformações();
gerente.reajustarSalario(2);
}
}
Ao executar, obtemos:
Funcionario Mario - Salário: 15000,00
Novo salario de Mario é 15300,00
Podemos adicionar mais funcionalidades. Por exemplo, podemos definir um bônus específico para o Gerente
.
public class Principal {
public static void main(String[] args) {
Gerente gerente = new Gerente("Mario", 15000);
gerente.exibirInformações();
gerente.reajustarSalario(2);
gerente.setBonus(1000);
}
}
A partir daí, poderíamos ter métodos específicos para lidar com esses bônus. Por exemplo, no método exibirInformações
, poderíamos mostrar não apenas o salário, mas também o valor do bônus. Mas vamos deixar isso para um pouco mais adiante, quando falarmos sobre polimorfismo e abordarmos os conceitos de sobrecarga e sobrescrita.
Desenvolvedor
Herdamos para o Gerente
as informações de salário, nome e os métodos já existentes. Vamos criar mais uma classe para ilustrar o uso da herança: a classe Desenvolvedor
.
package br.com.alura;
public class Desenvolvedor {
}
A classe Desenvolvedor
também estenderá Funcionario
.
public class Desenvolvedor extends Funcionario {
}
Para o Desenvolvedor
, adicionaremos um atributo stack
, que representa a linguagem de programação ou a área de atuação, como back-end ou front-end.
public class Desenvolvedor extends Funcionario {
private String stack;
}
Após criar esse atributo, para que possamos montar o construtor sem que a IDE apresente erros, vamos criá-lo manualmente, incluindo o campo stack
. Para isso, basta pressionar "Alt + Insert" nessa classe, selecionar a opção "Constructor" e incluir o campo stack
.
Repare que, inicialmente, o construtor apresenta apenas esse atributo. Ao confirmar com "OK", a IDE gera automaticamente o construtor da classe Desenvolvedor
, incluindo os parâmetros nome
, salário
e stack
.
Para os campos nome
e salário
, o construtor chama a superclasse com o super
. Já para o stack
, que é uma característica exclusiva da classe Desenvolvedor
, o valor é atribuído diretamente com this.stack = stack
.
public class Desenvolvedor extends Funcionario {
private String stack;
public Desenvolvedor(String nome, double salario, String stack) {
super(nome, salario);
this.stack = stack;
}
}
Desenvolvedor
Agora, vamos até a nossa classe principal para criar também um desenvolvedor.
Primeiro, declaramos a variável desenvolvedor
e a inicializamos com uma nova instância da classe Desenvolvedor
, passando três parâmetros: o nome "Carla", um salário de "12000" e a stack
que será "Backend Java".
public class Principal {
public static void main(String[] args) {
Gerente gerente = new Gerente("Mario", 15000);
gerente.exibirInformações();
gerente.reajustarSalario(2);
gerente.setBonus(1000);
Desenvolvedor desenvolvedor = new Desenvolvedor("Carla", 12000, "Backend Java");
}
}
Ao chamar desenvolvedor.exibirInformacoes
, ele exibirá também as informações de Carla.
public class Principal {
public static void main(String[] args) {
Gerente gerente = new Gerente("Mario", 15000);
gerente.exibirInformações();
gerente.reajustarSalario(2);
gerente.setBonus(1000);
Desenvolvedor desenvolvedor = new Desenvolvedor("Carla", 12000, "Backend Java");
desenvolvedor.exibirInformacoes();
}
}
Funcionario Mario - Salário: 15000,00
Novo salario de Mario é 15300,00
Funcionario Carla - Salário: 12000,00
Ambas as classes, Gerente
e Desenvolvedor
, aproveitaram códigos já existentes para Funcionarios
. Esses códigos são gerais e aplicáveis a qualquer funcionário, permitindo exibir informações, realizar reajustes e manter atributos como nome
e salário
.
Essa é a ideia central da herança: aproveitar código sem reescrever, evitando erros e inconsistências entre classes que representam funcionários.
A herança é um tema abrangente, e na sequência, abordaremos classes abstratas, polimorfismo e outros conceitos relacionados à herança. Esperamos que tenha gostado. Pratique criando as classes junto conosco, observe os métodos e atributos utilizados. Até o próximo vídeo!
Vamos nos aprofundar em mais um pilar da orientação a objetos: o polimorfismo. O polimorfismo permite que o mesmo método tenha comportamentos diferentes dependendo de qual objeto o está chamando.
Perceberemos isso na prática ao ter métodos com o mesmo nome, por exemplo, e ao chamá-los, o método que será chamado dependerá do tipo de instância que foi criada.
Voltando ao IntelliJ, na classe Principal
, alteraremos a definição e a criação do gerente e do desenvolvedor. Em vez de declarar Gerente gerente
, vamos declarar Funcionario gerente
, porque ele é um funcionário, é um gerente, e estamos instanciando-o com new Gerente
.
Principal.java
:
public static void main(String[] args) {
Funcionario gerente = new Gerente("Mario", 15000);
// Código omitido
}
Observaremos que o setBonus()
dará erro, pois declaramos como sendo Funcionario
, e nessa classe não temos o método setBonus()
.
Para o desenvolvedor, será a mesma coisa. Vamos apagar a definição Desenvolvedor
e colocaremos Funcionario
. Assim, Funcionario gerente
recebe new Gerente
e Funcionario desenvolvedor
recebe um new Desenvolvedor
.
public static void main(String[] args) {
// Código omitido
Funcionario desenvolvedor = new Desenvolvedor("Carla", 12000, "Backend Java");
}
setBonus()
No momento em que definimos de forma genérica que esse gerente é um Funcionario
, não conseguimos fazer essa atribuição para o setBonus()
sem fazer um typecast. Precisamos fazer um cast, adicionando um (Gerente)
para gerente
, permitindo que ele entenda que deve utilizar o setBonus()
.
public static void main(String[] args) {
// Código omitido
((Gerente) gerente).setBonus(1000);
}
Com esse typecast, estamos forçando-o a entender que essa pessoa colaboradora é um gerente — ou seja, que esse gerente é da classe Funcionario
, mas que sua subclasse é Gerente
.
Até aqui, não falamos de polimorfismo, apenas que ambos são declarados como pessoas colaboradoras.
Suponha que queiramos exibir as informações do gerente de forma diferente, porque o gerente tem bônus. Na classe Gerente
, precisamos sobrescrever o método exibirInformacoes()
. O polimorfismo de sobrescrita é exatamente isso: colocamos um método com a mesma definição do método da superclasse, mas alteramos seu comportamento.
Por exemplo, abaixo do método setonus()
, ao digitar exibirInformacoes
, a IDE já gera esse método usando a anotação @Override
.
Gerente.java
:
@Override
public void exibirInformacoes() {
super.exibirInformacoes();
}
A IDE cria um método com o mesmo nome, public void exibirInformacoes
, anotado com @Override
, para deixar claro que estamos sobrescrevendo o método original da classe Funcionario
. Dentro desse método, a IDE sugere que chamemos super.exibirInformacoes()
, para exibir as informações da mesma forma que é feito no Funcionario
.
Não é isso que queremos. Portanto, apagaremos o super.exibirInformacoes()
e faremos outro System.out.printf()
, no qual colocaremos, por exemplo, "Gerente: %s - Salário: %.2f - Bônus: %.2f"
, passando os três atributos: nome
, salario
e bonus
.
@Override
public void exibirInformacoes() {
System.out.printf("Gerente: %s - salário %.2f - bônus: %.2f",
nome, salario, bonus);
}
Ao começar a digitar nome
, perceberemos que ele não será encontrado. Isso ocorre porque nome
não está definido na classe gerente
, mas na classe funcionário
. Para referenciar nome
, salario
e bonus
, não usaremos os métodos acessores get()
— precisaremos chamar super.nome
, e assim por diante.
@Override
public void exibirInformacoes() {
System.out.printf("Gerente: %s - salário %.2f - bônus: %.2f",
super.nome);
}
Entretanto, isso não funciona porque usamos um atributo privado. Quando o atributo é privado, ele só pode ser utilizado pela classe original, que é Funcionario
. Se quisermos dar acesso também às subclasses, precisamos acessar a classe Funcionario
e trocar a visibilidade desses atributos para protected
.
Funcionario.java
:
protected String nome;
protected double salario;
Voltando à classe Gerente
, conseguiremos usar nome
, salario
e bonus
.
Gerente.java
:
@Override
public void exibirInformacoes() {
System.out.printf("Gerente: %s - salário %.2f - bônus: %.2f",
nome, salario, bonus);
}
Resumindo, para permitir que a classe e suas subclasses tenham acesso às informações sem os métodos acessores, usamos o padrão de visibilidade protected
no encapsulamento.
Principal
Na classe Principal
, vamos exibir os dados do gerente. Já temos gerente.exibirInformacoes()
. Quando o usarmos, perceberemos que ele não chamará mais o exibirInformacoes()
da classe funcionário
, que mostra apenas a pessoa colaboradora e o salário: ele chamará o método exibirInformacoes()
da classe Gerente
, que mostra gerente, salário e bônus.
Esse é um exemplo de polimorfismo de sobrescrita, onde sobrescrevemos o comportamento do método exibirInformacoes
.
Vamos executar a classe principal para observar isso.
Na exibição de informações, foi mostrado o gerente Mário, com salário e o bônus zero.
Gerente: Mario - salário 15000,00 - bônus: 0,00
O bônus está zerado porque foi configurado após a exibição das informações. Se tivéssemos configurado antes, ele mostraria o bônus corretamente.
O importante é perceber que chamamos o método da classe correta, que é Gerente
.
Desenvolvedor
Vamos sobrescrever também esse método para a pessoa desenvolvedora, para que possamos ver a diferença entre eles. Na classe Desenvolvedor
, após o restante do código, adicionaremos a opção de exibir informações.
Foi gerado um @Override
, e faremos um printf()
, colocando apenas as informações básicas: uma quebra de linha, um "Desenvolvedor: %s - salário %0.2f - Stack: %s"
e os atributos nome
, salario
e stack
.
Podemos reparar que, após usar o
protected
na classeFuncionario
, todas as subclasses — incluindoDesenvolvedor
— possuem acesso aos atributos.
Desenvolvedor.java
:
@Override
public void exibirInformacoes() {
System.out.printf("\nDesenvolvedor: %s salário %.2f - Stack: %s",
nome, salario, stack);
}
Voltando à classe Principal
, no método main
, moveremos o setBonus()
para antes da exibição, para visualizar melhor.
Principal.java
:
((Gerente) gerente).setBonus(1000);
gerente.exibirInformacoes();
gerente.reajustarSalario(2);
Configuramos o bônus, exibimos as informações do gerente, reajustamos o salário e, em seguida, exibimos as informações da pessoa desenvolvedora. Podemos executar a aplicação e visualizar as definições separadas:
Gerente: Mario - salário 15000,00 - bônus: 1000,00
Novo salario de Mario é 15300,00
Desenvolvedor: Carla salário 12000,00 - Stack: Backend Java
o gerente Mário é mostrado com salário antigo e o novo. Abaixo, a desenvolvedora Carla aparece com seu salário.
Aplicamos o polimorfismo por sobrescrita, conhecido como Override
, pois os métodos têm o mesmo nome e assinatura, mas em tempo de execução é analisado o tipo de pessoa colaboradora. Ambas são declaradas como Funcionario
, mas o Java determina se a instância é de Gerente
ou de Desenvolvedor
, chamando o método exibirInformacoes()
correspondente.
Agora, vamos entender o polimorfismo de sobrecarga. Nele, temos métodos com o mesmo nome, mas assinaturas diferentes, para que o Java saiba qual método chamar.
Suponhamos que no reajustarSalario()
queiramos dois modelos: um em que passamos o percentual, aplicado sobre o salário, e outro em que não passamos nada, acrescentando sempre 500 reais ao salário.
Na classe Funcionario
, abaixo do primeiro reajustarSalario()
, criaremos um novo método com o mesmo nome. Ao invés de passar um valor percentual como parâmetro entre parênteses, manteremos o método apenas com o nome.
Esse método vai acrescentar um valor fixo ao salário. Colocaremos salario += 500
, ou seja, somar 500 reais ao valor atual do salário.
Depois, copiaremos a linha que imprime no terminal para mostrar qual é o novo salário após esse reajuste. Mudaremos a frase impressa para "Salário com dissídio" para justificar o valor fixo de 500 reais.
Funcionario.java
:
public void reajustarSalario(){
salario += 500;
System.out.printf("\nSalário com dissídio de %s é %.2f ", nome, salario);
}
Se pedirmos para reajustar o salário passando o percentual, o primeiro método será chamado. Caso contrário, o método sem parâmetros será chamado.
Na classe Principal
, no método main()
, o gerente já é reajustado com percentual de 2. Para a desenvolvedora Carla, que começa com salário de 12 mil, faremos um reajuste sem passar parâmetros.
Abaixo da instância de Desenvolvedor
, chamaremos o método reajustarSalario()
no objeto desenvolvedor
, sem passar nenhum parâmetro. Ao exibir as informações na linha seguinte, o salário dela deverá ser 12.500.
Principal.java
:
Funcionario desenvolvedor = new Desenvolvedor("Carla", 12000, "Backend Java");
desenvolvedor.reajustarSalario();
desenvolvedor.exibirInformacoes();
Ao executar a aplicação, observaremos que o gerente Mário, com salário inicial de 15 mil, foi reajustado para 15.300. Para Carla, o método do "Salário com dissídio" foi aplicado, resultando em 12.500, com acréscimo de 500 reais.
Gerente: Mario - salário 15000,00 - bônus: 1000,00
Novo salario de Mario é 15300,00
Salário com dissídio de Carla é 12500,00
Desenvolvedor: Carla salário 12500,00 - Stack: Backend Java
Essas são as formas de polimorfismo que podem ser usadas com a herança: polimorfismo de sobrescrita e de sobrecarga. São muito úteis, pois evitam a criação de inúmeros métodos diferentes para cada situação. Podemos criar o método reajustarSalario()
com parâmetros diferenciados.
Ao usar métodos no Java, temos várias opções de assinaturas, o que é uma prática comum e uma forma ampla de utilizar polimorfismo, tanto de sobrescrita quanto de sobrecarga. Esses conceitos são importantes no nosso dia a dia como pessoas desenvolvedoras.
No próximo vídeo, abordaremos classes abstratas e interfaces.
O curso Praticando Java: herança, polimorfismo e interfaces possui 44 minutos de vídeos, em um total de 20 atividades. Gostou? Conheça nossos outros cursos de Java em Programação, ou leia nossos artigos de Programação.
Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:
Impulsione a sua carreira com os melhores cursos e faça parte da maior comunidade tech.
1 ano de Alura
Assine o PLUS e garanta:
Formações com mais de 1500 cursos atualizados e novos lançamentos semanais, em Programação, Inteligência Artificial, Front-end, UX & Design, Data Science, Mobile, DevOps e Inovação & Gestão.
A cada curso ou formação concluído, um novo certificado para turbinar seu currículo e LinkedIn.
No Discord, você tem acesso a eventos exclusivos, grupos de estudos e mentorias com especialistas de diferentes áreas.
Faça parte da maior comunidade Dev do país e crie conexões com mais de 120 mil pessoas no Discord.
Acesso ilimitado ao catálogo de Imersões da Alura para praticar conhecimentos em diferentes áreas.
Explore um universo de possibilidades na palma da sua mão. Baixe as aulas para assistir offline, onde e quando quiser.
Acelere o seu aprendizado com a IA da Alura e prepare-se para o mercado internacional.
1 ano de Alura
Todos os benefícios do PLUS e mais vantagens exclusivas:
Luri é nossa inteligência artificial que tira dúvidas, dá exemplos práticos, corrige exercícios e ajuda a mergulhar ainda mais durante as aulas. Você pode conversar com a Luri até 100 mensagens por semana.
Aprenda um novo idioma e expanda seus horizontes profissionais. Cursos de Inglês, Espanhol e Inglês para Devs, 100% focado em tecnologia.
Transforme a sua jornada com benefícios exclusivos e evolua ainda mais na sua carreira.
1 ano de Alura
Todos os benefícios do PRO e mais vantagens exclusivas:
Mensagens ilimitadas para estudar com a Luri, a IA da Alura, disponível 24hs para tirar suas dúvidas, dar exemplos práticos, corrigir exercícios e impulsionar seus estudos.
Envie imagens para a Luri e ela te ajuda a solucionar problemas, identificar erros, esclarecer gráficos, analisar design e muito mais.
Escolha os ebooks da Casa do Código, a editora da Alura, que apoiarão a sua jornada de aprendizado para sempre.