Primeiras aulas do curso Java Polimorfismo: Entenda herança e interfaces

Java Polimorfismo: Entenda herança e interfaces

Introdução a herança - Introdução ao curso

Olá! Eu sou o Nico Steppat, programador Java desde 2001 e instrutor na Alura tanto nesta linguagem quanto em Python.

Neste curso, nosso foco será em herança e uso de interfaces. Tópicos como a reutilização de código, polimorfismo, reescrita e redefinição de métodos, reutilização por meio de recomposição ou herança, são todos temas abordados dentro deste curso, em detalhes.

A ideia é estudarmos cada um destes conceitos de forma prática, utilizando o código.

Veremos como funciona a sintaxe e, além disso, a razão pela qual existe.

Vocês estão convidados a assistirem as aulas deste curso de Java, com foco em uso de herança e interfaces. Vamos lá!

Introdução a herança - Revisão e a classe Funcionario

Você pode fazer o DOWNLOAD do projeto criado no curso anterior.


Bem-vindos ao curso de Java Parte 3! Nele, aprenderemos sobre herança e outros aspectos conceituais.

Antes de começarmos a programar, revisaremos o ambiente no qual estamos trabalhando.

Abriremos o terminal, no qual, digitando:

java -version

E pressionando "Enter", conseguimos ver a versão do Java que estamos utilizando. Neste caso, é o Java 9, ou como outros a chamam, 1.9. Não há problema em utilizar a versão 1.8 para os propósitos deste curso.

Além disso, utilizaremos o software Eclipse. Não há nenhuma especificidade de versão, isso fica a critério do aluno. Abriremos no workspace padrão, que é criado automaticamente pelo programa.

Se você já fez os cursos de Java partes 1 e 2, provavelmente criou outros projetos Java. Abriremos o último, de nome bytebank-encapsulado.

Para acompanhar, é possível fazer o download do projeto do curso anterior.

Nele, há a classe Cliente, e a classe Conta.

Nesta última, como podemos observar, temos os atributos:

public class Conta {

    private double saldo;
    private int agencia;
    private int numero;
    private Cliente titular;
    private static int total = 0;

    //código continua...

São privados, ou seja, ninguém fora da classe pode acessá-los. Em seguida, temos o construtor para inicializar, ou seja, criar o objeto:

//parte de cima do código omitida

public Conta(int agencia, int numero) {
    Conta.total++;
    System.out.println("O total de contas é " + Conta.total);
    this.agencia = agencia;
    this.numero = numero;
    this.saldo = 100;
    System.out.println("Estou criando uma conta  " + this.numero);

}

Abaixo, temos todos os métodos, como deposita, saca e transfere, além dos getters e setters. Por fim, temos o método da classe, que nos retorna o total das contas, com um atributo estático privado, o nome da classe:

//parte de cima do código omitida

public static int getTotal(){
    return Conta.total;
}

Além disso, associamos duas classes, Conta e Cliente, fazendo o que chamamos de composição, aprendemos o relacionamento entre classes, o que inclui a herança.

Ainda, foram feitos diversos testes, criando contas e passando parâmetros, realizando depósitos e saques, utilizando o conta., chamando o método para poder fazer algo com o objeto. Utilizando o set para estabelecer dados que não foram determinados pelo construtor, e imprimindo-os acessando as informações por meio de getters.

Ao final, foi inserido o acesso do método estático, com o nome da classe Conta com o C maiúsculo, não confundir com a referência conta em c minúsculo.

Nossa classe TestaContas, contém portanto o seguinte código:

public class TesteContas {

    public static void main(String[] args) {

        Conta conta = new Conta(1337, 23334);

        conta.deposita(200.0);

        System.out.println(conta.getSaldo());

        conta.setAgencia(570);

        System.out.println(conta.getAgencia());

        System.out.println("o total de contas é : " + Conta.getTotal());

    }
}

Tentaremos executar, e obtivemos o seguinte resultado:

O total de contas é 1
Estou criando uma conta 23334
300.0
570
o total de contas é : 1

Ou seja, os dados foram impressos no console sem nenhum problema.

Nesta primeira parte do curso, não continuaremos a utilizar a classe Conta, criaremos um novo projeto e novas classes.

Temos diversas maneiras de criar um novo projeto, aqui, utilizaremos o clique com o botão direito do mouse dentro da área "Package Explorer", e selecionaremos a opção "New > Java Project".

Daremos continuidade à ideia do Bytebank, portanto o nome do nosso novo projeto será bytebank-herdado. Concluiremos a criação do projeto.

Ao clicarmos com o botão direito do mouse sobre o nome do novo projeto, e selecionarmos a opção "Close Unrelated Projects", fará com que o Eclipse feche todos os outros projetos que não tenham relação com este. Surgirá um pop up perguntando se temos certeza disso, e basta confirmarmos.

Assim, ele fecha os demais projetos e garante que as classes serão criadas nos lugares corretos, e que serão executados os devidos testes.

Inicialmente, não trabalharemos com a Conta, criaremos uma nova classe, e nossa tarefa é representar um funcionário em nosso sistema. Portanto, ela se chamará Funcionario.

Clicaremos com o botão direito do mouse sobre a pasta src e selecionar "New > Class". Na janela de diálogo, preencheremos o nome, e finalizaremos o processo clicando em "Finish".

Este não é o único método para criação de classes, o Eclipse permite diversos outros.

Inicialmente, temos este código:

public class Funcionario {

}

Precisamos pensar em quais atributos o funcionário tem. No caso, teremos o nome, que será uma String privada. Além disso, ele terá um cpf, que também será uma String, e um salario, que será do tipo double:

public class Funcionario {

    private String nome;
    private String cpf;
    private double salario;
}

É melhor termos o menor número possível de atributos, contendo o mínimo necessário, assim evitamos de ter elementos que não serão utilizados.

Inicialmente, o Eclipse sinalizará que estes três atributos que criamos não estão sendo utilizados, isso porque ainda não criamos os métodos de gete set.

Como se tratam de métodos recorrentes, o Eclipse facilita sua criação. Podemos selecionar "Source > Generate Getters e Setters..." e surgirá uma janela na qual podemos selecionar para quais atributos desejamos gerar os getters e setters.

Automaticamente, o próprio programa detecta cpf, nome e salario. Selecionaremos a check box referente a cada um deles, com visibilidade pública, e concluiremos o processo clicando em "Ok".

Assim, temos o seguinte código:

public class Funcionario {

        private String nome;
        private String cpf;
        private double salario;

        public String getNome() {
            return nome;
        }

        public void setNome(String nome) {
            this.nome = nome;
        }

        public String getCpf() {
            return cpf;
        }

        public void setCpf(String cpf) {
            this.cpf = cpf;
        }

        public double getSalario() {
            return salario;
        }

        public void setSalario(double salario) {
            this.salario = salario;
        }
    }

Agora nosso chefe informou que todo funcionário recebe uma bonificação padrão, e que seu valor corresponde a 10% do salário. Precisamos portanto criar o método salario, com visibilidade pública, portanto, utilizaremos o modificador public.

O método devolverá a bonificação, que será um double, e se chamará getBonificacao. Em seu corpo, inseriremos o return, que nos retornará o valor do salário, this.salario, multiplicado por 0.1:

public class Funcionario {

        private String nome;
        private String cpf;
        private double salario;

        public double getBonificacao() {
            return this.salario * 0.1;
        }

        public String getNome() {
            return nome;
        }

        public void setNome(String nome) {
            this.nome = nome;
        }

        public String getCpf() {
            return cpf;
        }

        public void setCpf(String cpf) {
            this.cpf = cpf;
        }

        public double getSalario() {
            return salario;
        }

        public void setSalario(double salario) {
            this.salario = salario;
        }
    }

Como podemos observar, utilizamos um getter em getBonificacao mesmo não tendo criado o atributo bonificacao. Não há problema em fazer isso, ou seja, podemos criar nossos próprios getters ainda que não haja um atributo relacionado.

No caso, o valor será calculado, nos dando um resultado para a bonificação.

Salvaremos nosso arquivo, utilizando o atalho "CTRL + S". Criaremos um teste para verificarmos se funcionou.

Clicaremos com o botão direito do mouse sobre nosso pacote, e selecionaremos "New > Class". Daremos à classe o nome TesteFuncionario e, no próprio wizard de criação, podemos observar que há uma pergunta, logo abaixo da caixa de interfaces, que diz "Which method stubs would you like to create?", ou seja, ele já nos dá a opção de gerar um método main, é o que faremos, clicando na primeira caixa, que diz public static void main(String[] args).

Para concluir, clicaremos em "Finish".

Assim já temos uma classe e um método:

public class TesteFuncionario {

    public static void main(String[] args) {

    }
}

Em seguida, inseriremos nosso primeiro funcionário. Primeiro, temos que definir a referência à classe Funcionario, ao qual daremos o nome de nico:

public class TesteFuncionario {

    public static void main(String[] args) {

        Funcionario nico =

    }
}

Isto receberá um new, para criar o objeto, chamando o construtor Funcionario:

public class TesteFuncionario {

    public static void main(String[] args) {

        Funcionario nico = new Funcionario();

    }
}

Retornando à classe Funcionario, vemos que não foi criado nenhum construtor, faremos isso agora:

public class Funcionario {

    private String nome;
    private String cpf;
    private double salario;

    public Funcionario() {

    }

// Código continua embaixo...

Ele sempre recebe o nome da classe, entretanto, diferente do método, não retorna nada. Temos public seguido direto pelo nome da classe, sem nenhum tipo de retorno definido, como double, por exemplo.

Mesmo sem termos criado este construtor, nosso código estava funcionando. Isso porque, se nenhum construtor for criado, o compilador insere automaticamente o construtor padrão.

O construtor padrão é aquele que não recebe nenhum parâmetro.

Trabalharemos novamente com a classe TesteFuncionario. Daremos um nome para este funcionário, utilizando o setNome, bem como um CPF, por meio do setCpf', e ainda, um salário, com o setSalario:

public class TesteFuncionario {

    public static void main(String[] args) {

        Funcionario nico = new Funcionario();
        nico.setNome("Nico Steppat");
        nico.setCpf("223355646-9");
        nico.setSalario(2600.00);

    }
}

Com isso, podemos imprimir algumas informações, utilizando os getters. Testaremos com o nome a bonificação:

public class TesteFuncionario {

    public static void main(String[] args) {

        Funcionario nico = new Funcionario();
        nico.setNome("Nico Steppat");
        nico.setCpf("223355646-9");
        nico.setSalario(2600.00);

        System.out.println(nico.getNome());
        System.out.println(nico.getBonificacao());

    }
}

Salvaremos e executaremos. Clicamos com o botão direito do mouse sobre a tela, e selecionaremos "Run As > Java Application". No console, é impresso:

Nico Steppat
260.0

Funcionou.

Todos os conceitos vistos nesta aula foram para revisão, nas próximas aulas adentraremos no conceito de herança, e quais problemas ela é capaz de solucionar.

Introdução a herança - Tipos de funcionários

Olá! Nesta aula, continuaremos a trabalhar com a classe Funcionario, seguindo as aulas anteriores. Os mesmos princípios utilizados na classe Conta foram aplicados em Funcionario por meio do teste realizado.

Nosso chefe nos apresentou um novo requisito. Além de funcionários, teremos que representar, também, os gerentes.

Por princípio, um funcionário não é fundamentalmente diferente de um gerente, já que ambos têm características em comum, como nome, CPF e salário. A ideia é aproveitarmos a classe Funcionario, tendo em mente estas semelhanças.

Entretanto, temos um problema: a bonificação não será a mesma! O gerente certamente ganhará mais que 10% de bônus, que é quanto o funcionário comum ganha. Neste caso, a sua bonificação será de um salário inteiro a mais.

Faremos uma cópia da classe Funcionario, que chamaremos de FuncionarioTeste, apenas para podermos alterá-la livremente, sem perder suas características originais. Podemos fazer isto utilizando o atalho "Ctrl + C" e "Ctrl + V".

A partir de agora, trabalharemos com a classe FuncionarioTeste.

Para determinarmos se estamos lidando com um funcionário comum, ou um gerente, adicionaremos um atributo a mais, que chamaremos de tipo, e será inicializado em 0, que significa funcionário comum, como podemos ver no comentário, e assim por diante, onde cada número representa uma função diferente:

public class FuncionarioTeste {

        private String nome;
        private String cpf;
        private double salario;
        private int tipo = 0; //0 = Funcionário comum; 1 = Gerente; 2 = Diretor

        public double getBonificacao() {
            return this.salario * 0.1;
        }

//Código continua abaixo...

Ao criarmos um funcionário, ele sempre terá o valor 0 atribuído.

Abaixo, criaremos um método setTipo:

public class FuncionarioTeste {

        private String nome;
        private String cpf;
        private double salario;
        private int tipo = 0; //0 = Funcionário comum; 1 = Gerente; 2 = Diretor

        public double getBonificacao() {
            return this.salario * 0.1;
        }

        public void setTipo(int tipo) {
            this.tipo = tipo;
        }

//Código continua abaixo...

Criaremos também um getTipo:

public class FuncionarioTeste {

        private String nome;
        private String cpf;
        private double salario;
        private int tipo = 0; //0 = Funcionário comum; 1 = Gerente; 2 = Diretor

        public double getBonificacao() {
            return this.salario * 0.1;
        }

        public void setTipo(int tipo) {
            this.tipo = tipo;
        }

        public int getTipo() {
            return tipo;
        }

//Código continua abaixo...

Alternativamente, poderíamos ter utilizado o menu superior, em "Source > Generate Getters and Setters...".

Com isso, já é possível determinarmos e sabermos o tipo do funcionário. Em seguida, daremos mais especificidade à bonificação. Para isso, utilizaremos um if, com condições para cada uma das funções, que são representadas por um número cada. Começando pelo funcionário comum:

public class FuncionarioTeste {

        private String nome;
        private String cpf;
        private double salario;
        private int tipo = 0; //0 = Funcionário comum; 1 = Gerente; 2 = Diretor

        public double getBonificacao() {

            if(this.tipo == 0) { // Funcionário comum
                return this.salario * 0.1;
            }
            return this.salario * 0.1;
        }

        public void setTipo(int tipo) {
            this.tipo = tipo;
        }

        public int getTipo() {
            return tipo;
        }

//Código continua abaixo...

Em seguida, para lidarmos com outro tipo, utilizaremos o else:

public class FuncionarioTeste {

        private String nome;
        private String cpf;
        private double salario;
        private int tipo = 0; //0 = Funcionário comum; 1 = Gerente; 2 = Diretor

        public double getBonificacao() {

        if(this.tipo == 0) { // Funcionário comum;
            return this.salario * 0.1;
        } else if(this.tipo == 1) { // Gerente;
            return this.salario;
        }

        public void setTipo(int tipo) {
            this.tipo = tipo;
        }

        public int getTipo() {
            return tipo;
        }

//Código continua abaixo...

Por fim, se não for funcionário, nem gerente, ele será um diretor, e terá uma terceira forma de bonificação:

public class FuncionarioTeste {

        private String nome;
        private String cpf;
        private double salario;
        private int tipo = 0; //0 = Funcionário comum; 1 = Gerente; 2 = Diretor

        public double getBonificacao() {

            if(this.tipo == 0) { // Funcionário comum;
                return this.salario * 0.1;
            } else if(this.tipo == 1) { // Gerente;
                return this.salario;
            } else {
                return this.salario + 1000.0;
            }

        }

        public void setTipo(int tipo) {
            this.tipo = tipo;
        }

        public int getTipo() {
            return tipo;
        }

//Código continua abaixo...

O que fizemos foi criar, para cada tipo de funcionário, uma regra de bonificação diferente.

Para testarmos, criaremos uma nova classe de teste. Clicando com o botão direito do mouse sobre nosso pacote, e selecionando "New > Class", a nomearemos Teste e já lhe atribuiremos um método main:

public class Teste {

    public static void main(String[] args) {

    }

}

Em seguida, criaremos um funcionário hipotético, f1:

public class Teste {

    public static void main(String[] args) {
        FuncionarioTeste f1 = new FuncionarioTeste();

    }

}

Ao criarmos ele, temos que ter em mente que ele nascerá, por padrão, como um funcionário comum, já que nos atributos lhe foi atribuído um valor inicial de 0, que corresponde ao funcionário comum.

Isso significa que, se fizermos um System.out.println para getTipo, deveríamos receber um retorno 0.

Definiremos um salário, f1.setSalario no valor de R$3.000,00, além de calcularmos a bonificação, com o getBonificacao:

public class Teste {

    public static void main(String[] args) {
        FuncionarioTeste f1 = new FuncionarioTeste();
        f1.setSalario(3000.0);
        System.out.println(f1.getTipo());
        System.out.println(f1.getBonificacao());
    }
}

Salvaremos e executaremos. Clicaremos com o botão direito do mouse sobre a tela, e selecionaremos "Run As > Java Application". No console, veremos:

0
300.0

O 0 significa que estamos lidando com o funcionário comum, e que a sua bonificação é de R$300,00.

Testaremos com um novo funcionário, que chamaremos de f2, e que será um gerente, ou seja, será do tipo 1, seu salário será de R$5.000,00, e imprimiremos as informações, assim como fizemos para o funcionário f1:

public class Teste {

    public static void main(String[] args) {
        FuncionarioTeste f1 = new FuncionarioTeste();
        f1.setSalario(3000.0);
        System.out.println(f1.getTipo());
        System.out.println(f1.getBonificacao());

        FuncionarioTeste f2 = new FuncionarioTeste();
        f2.setTipo(1);
        f2.setSalario(5000.0);
        System.out.println(f2.getTipo());
        System.out.println(f2.getBonificacao());
    }
}

Vamos executar, e veremos que, no console, foi impresso o seguinte:

0
300.0
1
5000.0

Ou seja, funcionou, temos as respectivas classificações, no caso, 0 e 1, e as respectivas bonificações.

Mas quais problemas este sistema poderia nos acarretar?

Primeiro, temos três tipos de cargos definidos: o funcionário, gerente, e o diretor. Vamos imaginar que tivéssemos mais vinte outros tipos, por exemplo, ou quantos mais fossem, teríamos sempre que ter o controle da referência de cada cargo, ou seja, seria necessário sabermos qual número corresponde exatamente a qual função, sendo que é possível ter inúmeras funções.

Como isso não é explícito em nosso código, é exigido que façamos este processo mentalmente, e isto não é uma boa prática de programação.

Idealmente, se estamos falando de uma espécie "gerente", deve existir uma classe Gerente.

Em segundo lugar, se retornarmos à classe Funcionario, percebemos que, para controlar o tipo de função temos muitos ifs, e isto também não é uma boa prática de programação.

Eles são um recurso útil, e em certos momentos, necessários, mas neste caso eles tendem a nunca parar de crescer. Isso porque, sempre que tivermos um novo tipo, ou seja, uma nova função, ou cargo, teremos que criar um novo if.

Conforme nosso quadro de funcionários cresça, é provável que outros métodos precisem de ifs.

Por exemplo, e se quisermos criar um getTipo que nos retorne um String?

//Código de cima omitido

public String getTipo() {
    return tipo;
}

//Código abaixo omitido

Como implementaríamos este método?

Teríamos que repetir todos os ifs criados em getBonificacao no corpo do getTipo, para podermos devolver o String adequado para cada tipo.

Como isto não seria prático, retornaremos o código ao modo como estava:

//Código de cima omitido

public int getTipo() {
    return tipo;
}

//Código abaixo omitido

Com isso, conseguimos observar que, cada método que seja mais específico, ou mais sofisticado, e que envolva o tipo de Funcionario, demandará mais ifs, que não têm limites. Isto não é bom para nosso programa.

Nosso objetivo é ter um código estável, ou seja, que uma vez escrito, não precise ser reescrito. Cada mudança no código é sempre um perigo, temos que buscar evitar ao máximo quaisquer alterações futuras, especialmente aquelas que envolvam pedaços maiores do código.

O exemplo do if foi citado, mas não é o único. Imaginemos, por exemplo, que o gerente possui uma senha que um funcionário não tem acesso - como isso pode ser apresentado?

Para isso, teríamos de criar um novo atributo:

public class FuncionarioTeste {

        private String nome;
        private String cpf;
        private double salario;
        private int tipo = 0; // 0 = Funcionário comum; 1 = Gerente; 2 = Diretor
        private int senha;

Que temos que inicializar, por meio de um método. Entretanto, este atributo é específico para o gerente e para o diretor, e não deve atingir o funcionário comum - como podemos limitar isso se estamos generalizando em uma única classe?

O mesmo pode acontecer com os métodos. Por exemplo, digamos que há um método do tipo boolean que serve para autenticação:

//Código de cima omitido

public boolean autentica(int senha) {
    if(this.senha == senha) {
        return true;
    } else {
            return false;
    }
}
//Código abaixo omitido

Este método deve atingir somente o gerente, jamais o funcionário comum - já que este nem senha possui. Como podemos então esconder o método, se ele foi inserido na mesma classe? Não é possível.

Há características e funcionalidades que são específicas a um determinado tipo, e não são iguais para todos os Funcionarios. Esta abordagem, de termos todas as informações concentradas em uma única classe, não consegue ser sustentada por muito tempo - a partir do momento que nosso sistema ganha complexidade, ela para de funcionar.

Nas próximas aulas, criaremos classes específicas.

Nesta aula, foi apresentado o problema que surge e nos motiva a utilizar a herança, é ela quem nos ajudará a separar as funcionalidades.

Sobre o curso Java Polimorfismo: Entenda herança e interfaces

O curso Java Polimorfismo: Entenda herança e interfaces possui 252 minutos de vídeos, em um total de 74 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:

Aprenda Java acessando integralmente esse e outros cursos, comece hoje!

  • 1241 cursos

    Cursos de programação, UX, agilidade, data science, transformação digital, mobile, front-end, marketing e infra.

  • Certificado de participação

    Certificado de que assistiu o curso e finalizou as atividades

  • App para Android e iPhone/iPad

    Estude até mesmo offline através das nossas apps Android e iOS em smartphones e tablets

  • Projeto avaliado pelos instrutores

    Projeto práticos para entrega e avaliação dos professores da Alura com certificado de aprovação diferenciado

  • Acesso à Alura Start

    Cursos de introdução a tecnologia através de games, apps e ciência

  • Acesso à Alura Língua

    Reforço online de inglês e espanhol para aprimorar seu conhecimento

Premium

  • 1241 cursos

    Cursos de programação, UX, agilidade, data science, transformação digital, mobile, front-end, marketing e infra.

  • Certificado de participação

    Certificado de que assistiu o curso e finalizou as atividades

  • App para Android e iPhone/iPad

    Estude até mesmo offline através das nossas apps Android e iOS em smartphones e tablets

  • Projeto avaliado pelos instrutores

    Projeto práticos para entrega e avaliação dos professores da Alura com certificado de aprovação diferenciado

  • Acesso à Alura Start

    Cursos de introdução a tecnologia através de games, apps e ciência

  • Acesso à Alura Língua

    Reforço online de inglês e espanhol para aprimorar seu conhecimento

12X
R$75
à vista R$900
Matricule-se

Premium Plus

  • 1241 cursos

    Cursos de programação, UX, agilidade, data science, transformação digital, mobile, front-end, marketing e infra.

  • Certificado de participação

    Certificado de que assistiu o curso e finalizou as atividades

  • App para Android e iPhone/iPad

    Estude até mesmo offline através das nossas apps Android e iOS em smartphones e tablets

  • Projeto avaliado pelos instrutores

    Projeto práticos para entrega e avaliação dos professores da Alura com certificado de aprovação diferenciado

  • Acesso à Alura Start

    Cursos de introdução a tecnologia através de games, apps e ciência

  • Acesso à Alura Língua

    Reforço online de inglês e espanhol para aprimorar seu conhecimento

12X
R$100
à vista R$1.200
Matricule-se

Max

  • 1241 cursos

    Cursos de programação, UX, agilidade, data science, transformação digital, mobile, front-end, marketing e infra.

  • Certificado de participação

    Certificado de que assistiu o curso e finalizou as atividades

  • App para Android e iPhone/iPad

    Estude até mesmo offline através das nossas apps Android e iOS em smartphones e tablets

  • Projeto avaliado pelos instrutores

    Projeto práticos para entrega e avaliação dos professores da Alura com certificado de aprovação diferenciado

  • Acesso à Alura Start

    Cursos de introdução a tecnologia através de games, apps e ciência

  • Acesso à Alura Língua

    Reforço online de inglês e espanhol para aprimorar seu conhecimento

12X
R$120
à vista R$1.440
Matricule-se
Procurando planos para empresas?

Acesso por 1 ano

Estude 24h/dia onde e quando quiser

Novos cursos todas as semanas