7
respostas

Duvida sobre polimorfismo

Em um caso generico onde temos um objeto pai Funcionario e um filho Gerente em algumas etapas do curso foi instanciado o objeto dessa forma:

Funcionario f = new Gerente();

Fiquei em duvida se esse exemplo foi só para representar o polimorfismo ou se há algum caso que realmente se inicializa o objeto assim pois você consegue utilizar o polimorfismo normalmente se tivesse inicializado da forma:

Gerente f = new Gerente();

E assim você consegue acessar os metodos da classe gerente, coisa que não consegue da outra forma.

7 respostas

Boa tarde, João! Como vai?

A primeira coisa que é preciso entender é a definição de polimorfismo! Que na realidade nada mais é do que a possibilidade de referenciar objetos de forma mais genérica! Ou seja, no seu exemplo, seria a possibilidade de referenciar um objeto da classe Gerente com uma referência do tipo Funcionario!

Sendo assim, o polimorfismo só está sendo usado no seu primeiro exemplo, onde é feito:

Funcionario f = new Gerente();

O ganho do polimorfismo é que agora, se vc tiver uma classe Diretor que também é filha de Funcionario, vc pode também referenciar objetos dessa classe com referências do tipo Funcionario!

Exemplo prático:

Desejo criar um método que calcula o imposto de renda de um funcionário qualquer. A solução é:

public double calculaIR(Funcionario funcionario) {
     // Faz o cálculo.
}

Veja que dessa forma o método pode receber objetos do tipo Gerente ou Diretor e irá funcionar corretamente! O que não seria possível se eu recebesse especificamente um objeto do tipo Gerente! Daí meu método só funcionaria para os gerentes e não para os diretores!

Pegou a ideia? Qualquer coisa é só falar!

Grande abraço e bons estudos, meu aluno!

Boa tarde, Gabriel, o que não entendo é que eu consigo compilar exatamente o que você falou mas instanciando o objeto normalmente como no exemplo abaixo:

(Naquele cenário padrão do curso tendo a super classe Funcionario e os filhos Diretor e Gerente)

Criei uma classe para efetuar o calculo total de impostos.

public class CalculaTaxas {

    private double taxasgov;
    private double vales;

    public double calcularTotal(Funcionario f) {
        this.taxasgov = f.getSalario() * 0.1;
        this.vales = 100 + (f.getSalario() * 0.01);
        return taxasgov + vales;
    }    
}

E na hora do teste:

public class Bytebank {

    public static void main(String[] args) {

        Funcionario f = new Funcionario();
        Gerente g = new Gerente();
        Diretor d = new Diretor();
        CalculaTaxas calculador = new CalculaTaxas();

        f.setSalario(4000);
        g.setSalario(8000);
        d.setSalario(15000);

        System.out.println(calculador.calcularTotal(f));
        System.out.println(calculador.calcularTotal(g));
        System.out.println(calculador.calcularTotal(d));


    }
}

Compila normalmente e o resultado impresso sai correto.

O que não estou entendendo é a inicialização do objeto dessa forma

Funcionario f = new Gerente();

Não entendi a vantagem que tem sobre iniciar normalmente com

Gerente f = New Gerente();

Eu consigo esse mesmo resultado do polimorfismo

João Lucas, boa tarde!

Na herança, vimos que todo Gerente é um Funcionário, pois é uma extensão deste. Podemos nos referir a um Gerente como sendo um Funcionário. Se alguém precisa falar com um Funcionário do banco, pode falar com um Gerente! Porque? Pois Gerente é um Funcionário. Essa é a semântica da herança.

Gerente gerente = new Gerente();
Funcionario funcionario = gerente; 
funcionario.setSalario(5000.0);

Polimorfismo é a capacidade de um objeto poder ser referenciado de várias formas. (cuidado, polimorfismo não quer dizer que o objeto fica se transformando, muito pelo contrário, um objeto nasce de um tipo e morre daquele tipo, o que pode mudar é a maneira como nos referimos a ele).

Espero ter ajudado e bons estudos!

Para entender completamente o polimorfismo é preciso entender alguns aspectos de arquitetura de computadores e programação em baixo nível. Tudo em um programa de computador pode ser referenciado por seu endereço de memória, inclusive funções. Em C/C++, por exemplo, temos ponteiros de funções. Em assembly é comum usar "labels" marcando a primeira instrução de uma função para invoca-la por meio de saltos. Ocorre que, em alto nível, um tipo é meramente uma interface.

// Exemplo em java
interface Conta {
    void deposita(double valor);
    void saca(double valor); 
}

// Equivalente em C
struct Conta {
    void (*deposita)(double);
    void (*saca)(double);
};

Perceba que nenhuma das duas construções diz como a função deve se comportar, mas apenas qual a assinatura esperada; retorna void e aceita um único parâmetro do tipo double. Qual o método concreto a ser executado é definido em tempo de execução a depender do objeto que se está usando. Chamamos isso de "late binding".

class ContaPoupanca implements Conta {
...
}

class ContaCorrente implements Conta {
...
}

Cada uma dessas classes implementa a interface de modo distinto, mas ambas são contas pois aderem ao contrato estabelecido pelo tipo Conta. Desse modo temos que a forma abaixo é, não somente válida, mas também preferível.

Conta conta = new ContaCorrente();

Quando chamarmos um método, o objeto sabe qual a implementação chamar. Se quisermos mudar o comportamento, basta que mudemos a instanciação da classe, mas a interface permanece inalterada.

Graças a esse desacoplamento entre implementação e interface, podemos escrever código deste tipo:

class CaixaEletronico {
    private Conta conta;

    void transferenciaPara(Conta conta) {
        ...
    }
}

O qual abstrai o tipo específico empregado. É sempre preferível programar orientado a interfaces que a tipos concretos, pois assim tornamos o código mais desacoplado e, portanto, flexível a alterações futuras.

Daniel, muito obrigado pela explicação, até ai eu consigo acompanhar. O que eu não entendo, é que pelo menos com este compilador do java eu consigo executar os beneficios do poliformismo. Observe no comentário que eu fiz acima mostrando um código com uma classe CalculaTaxas, eu inicializei os objetos Gerente e Diretor (Que são filhos da classe Funcionario) da forma normal ou seja: Em vez de como apresentado no poliformismo

Funcionario g = new Gerente();

Eu iniciei

Gerente g = new Gerente();

E inicializando assim eu consegui obter os resultados do poliformismo sem a necessidade de ter instanciado o objeto daquela outra forma. O que não ficou claro para mim é essa inicialização de objetos, quando usar já que da outra forma eu consegui compilar todos os "beneficios" que apontavam para mim.

Então, a questão é que o polimorfismo é uma técnica de generalização de tipos concretos por meio de interfaces abstratas. Seu uso é fundamental dentro da lógica de programação orientado a objetos, e principalmente quando fazemos uso de frameworks.

Pense no seguinte problema:

interface Forma {
    void desenhaCentradoEm(double x, double y);
}

class Quadrado implements Forma {
...
}

class Circulo implements Forma {
...
}

class Triangulo implements Forma {
...
}

class Canvas {
    void adiciona(Forma forma, double x, double y) {
        forma.desenhaCentradoEm(x, y);
    }
}

Observe que cada uma das formas possui uma lógica de desenho específica. Mas a classe Canvas consegue resolver o problema de desenha-las de forma generalista com o mesmo código, isso porque os aspectos específicos de cada uma são resolvidos pelo método desenhaCentradoEm, o qual possui implementação distinta em cada uma delas.

Podemos portanto fazer algo como:

canvas.adiciona(new Quadrado(), 10, 20);
canvas.adiciona(new Triangulo(), 15, 10);
canvas.adiciona(new Circulo(), 5, 5);

Não sei se havia entendido exatamente qual a tua dúvida. Tu querias saber se seria possível fazer algo como o código abaixo?

...

Quadrado quadrado = new Quadrado();

...

canvas.adiciona(quadrado, 10, 20);

Sim, é perfeitamente válido, pois todo quadrado é uma forma, mas nem toda forma é um quadrado. O chamado uppercasting é sempre uma operação segura, e portanto, realizada automaticamente.

Há várias situações em que isso é desejável; por exemplo, quando um sub-tipo de uma classe abstrata necessariamente faz uso de uma determinada implementação de uma interface. Dessa forma, quando você instancia um objeto pelo seu tipo concreto, você pode chamar seus métodos específicos, que adicionem outras funcionalidades não descritas na interface pública, não obstante pode também passa-lo para qualquer método que espere seu super-tipo.

Contudo, pense no caso de uma estrutura de dados, como por exemplo uma lista. Você poderia querer em dado momento mudar de uma ArrayList para uma LinkedList. Se você construiu toda a interface da sua aplicação usando um tipo concreto, seu código é muito menos flexível e, portanto, menos reutilizável. Por este motivo é sempre melhor usar uma interface mais abstrata a uma implementação concreta quando se trata de dados ou métodos públicos.

abstract class Documento {
...
}

class Pessoa {
    private List<Documento> documentos;

    void adiciona(Documento documento);
    void remove(Documento document);
}

Perceba que o código anterior nos dá mais liberdade que algo como:

class Cpf {
...
}

class Pessoa {
    private ArrayList<Cpf> documentos;

    void adiciona(Cpf cpf);
    void remove(Cpf cpf);
}

Mesmo porque, de forma legítima, uma pessoa só pode ter um CPF. Por outro lado, uma pessoa pode ter até 27 RGs, habilitação de trânsito, passaporte, carteira de conselho profissional, etc.

Quer mergulhar em tecnologia e aprendizagem?

Receba a newsletter que o nosso CEO escreve pessoalmente, com insights do mercado de trabalho, ciência e desenvolvimento de software