JavaRush /Blogue Java /Random-PT /Polimorfismo em Java

Polimorfismo em Java

Publicado no grupo Random-PT
Perguntas sobre OOP são parte integrante de uma entrevista técnica para o cargo de desenvolvedor Java em uma empresa de TI. Neste artigo falaremos sobre um dos princípios da POO - polimorfismo. Vamos nos concentrar em aspectos que são frequentemente questionados durante as entrevistas e também fornecer pequenos exemplos para maior clareza.

O que é polimorfismo?

Polimorfismo é a capacidade de um programa usar objetos com a mesma interface de forma idêntica, sem informações sobre o tipo específico desse objeto. Se você responder à pergunta o que é polimorfismo dessa forma, provavelmente será solicitado que você explique o que quer dizer. Mais uma vez, sem fazer muitas perguntas adicionais, coloque tudo em ordem para o entrevistador.

Polimorfismo em Java em uma entrevista - 1
Podemos começar com o fato de que a abordagem OOP envolve a construção de um programa Java baseado na interação de objetos baseados em classes. As aulas são desenhos pré-escritos (templates) de acordo com os quais serão criados os objetos do programa. Além disso, uma classe sempre possui um tipo específico, que, com um bom estilo de programação, “informa” sua finalidade pelo nome. Além disso, pode-se notar que, como Java é uma linguagem fortemente tipada, o código do programa sempre precisa indicar o tipo do objeto ao declarar variáveis. Acrescente a isso que a digitação estrita aumenta a segurança do código e a confiabilidade do programa e permite evitar erros de incompatibilidade de tipo (por exemplo, uma tentativa de dividir uma string por um número) no estágio de compilação. Naturalmente, o compilador deve “conhecer” o tipo declarado - pode ser uma classe do JDK ou uma que nós mesmos criamos. Observe ao entrevistador que ao trabalhar com código de programa, podemos usar não apenas objetos do tipo que atribuímos ao declarar, mas também seus descendentes. Este é um ponto importante: podemos tratar muitos tipos como se fossem apenas um (desde que esses tipos sejam derivados de um tipo base). Isso também significa que, tendo declarado uma variável do tipo superclasse, podemos atribuir-lhe o valor de um de seus descendentes. O entrevistador vai gostar se você der um exemplo. Selecione algum objeto que possa ser comum (base) para um grupo de objetos e herde algumas classes dele. Classe base:
public class Dancer {
    private String name;
    private int age;

    public Dancer(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void dance() {
        System.out.println(toString() + "I dance like everyone else.");
    }

    @Override
    public String toString() {
        return "Я " + name + ", to me " + age + " years. " ;
    }
}
Nos descendentes, substitua o método da classe base:
public class ElectricBoogieDancer extends Dancer {
    public ElectricBoogieDancer(String name, int age) {
        super(name, age);
    }
// overriding the base class method
    @Override
    public void dance() {
        System.out.println( toString() + "I dance electric boogie!");
    }
}

public class BreakDankDancer extends Dancer{

    public BreakDankDancer(String name, int age) {
        super(name, age);
    }
// overriding the base class method
    @Override
    public void dance(){
        System.out.println(toString() + "I'm breakdancing!");
    }
}
Um exemplo de polimorfismo em Java e o uso de objetos em um programa:
public class Main {

    public static void main(String[] args) {
        Dancer dancer = new Dancer("Anton", 18);

        Dancer breakDanceDancer = new BreakDankDancer("Alexei", 19);// upcast to base type
        Dancer electricBoogieDancer = new ElectricBoogieDancer("Igor", 20); // upcast to base type

        List<Dancer> discotheque = Arrays.asList(dancer, breakDanceDancer, electricBoogieDancer);
        for (Dancer d : discotheque) {
            d.dance();// polymorphic method call
        }
    }
}
mainMostre no código do método o que está nas linhas:
Dancer breakDanceDancer = new BreakDankDancer("Alexei", 19);
Dancer electricBoogieDancer = new ElectricBoogieDancer("Igor", 20);
Declaramos uma variável do tipo superclasse e atribuímos a ela o valor de um dos descendentes. Provavelmente, você será questionado por que o compilador não reclamará da incompatibilidade entre os tipos declarados à esquerda e à direita do sinal de atribuição, já que Java possui tipagem estrita. Explique que a conversão de tipo ascendente funciona aqui - uma referência a um objeto é interpretada como uma referência à classe base. Além disso, o compilador, tendo encontrado tal construção no código, faz isso de forma automática e implícita. Com base no código de exemplo, pode-se mostrar que o tipo de classe declarado à esquerda do sinal de atribuição possui Dancervários formulários (tipos) declarados à direita BreakDankDancer,. ElectricBoogieDancerCada um dos formulários pode ter seu próprio comportamento exclusivo para funcionalidades comuns definidas no método da superclasse dance. Ou seja, um método declarado em uma superclasse pode ser implementado de forma diferente em seus descendentes. Nesse caso, estamos lidando com substituição de método, e é exatamente isso que cria uma variedade de formas (comportamentos). Você pode ver isso executando o código do método principal para execução: Saída do programa Sou Anton, tenho 18 anos. Eu danço como todo mundo. Meu nome é Alexey, tenho 19 anos. Eu danço break! Meu nome é Igor, tenho 20 anos. Eu danço o boogie elétrico! Se não usarmos substituição nos descendentes, não obteremos um comportamento diferente. BreakDankDancerPor exemplo, se comentarmos ElectricBoogieDancero método de nossas classes dance, a saída do programa será assim: Meu nome é Anton, tenho 18 anos. Eu danço como todo mundo. Meu nome é Alexey, tenho 19 anos. Eu danço como todo mundo. Meu nome é Igor, tenho 20 anos. Eu danço como todo mundo. e isso significa que BreakDankDancersimplesmente ElectricBoogieDancernão faz sentido criar novas classes. Qual é exatamente o princípio do polimorfismo Java? Onde está escondido usar um objeto em um programa sem saber seu tipo específico? Em nosso exemplo, esta é uma chamada de método d.dance()em um objeto ddo tipo Dancer. Polimorfismo Java significa que o programa não precisa saber que tipo será o objeto BreakDankDancerou objeto ElectricBoogieDancer. O principal é que é descendente da classe Dancer. E se falamos de descendentes, deve-se notar que herança em Java não é apenas extends, mas também implements. Agora é hora de lembrar que Java não suporta herança múltipla – cada tipo pode ter um pai (superclasse) e um número ilimitado de descendentes (subclasses). Portanto, interfaces são usadas para adicionar múltiplas funcionalidades às classes. As interfaces reduzem o acoplamento de objetos a um pai em comparação com a herança e são amplamente utilizadas. Em Java, uma interface é um tipo de referência, portanto, um programa pode declarar o tipo como uma variável do tipo de interface. Este é um bom momento para dar um exemplo. Vamos criar a interface:
public interface Swim {
    void swim();
}
Para maior clareza, vamos pegar objetos diferentes e não relacionados e implementar uma interface neles:
public class Human implements Swim {
    private String name;
    private int age;

    public Human(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public void swim() {
        System.out.println(toString()+"I swim with an inflatable ring.");
    }

    @Override
    public String toString() {
        return "Я " + name + ", to me " + age + " years. ";
    }

}

public class Fish implements Swim{
    private String name;

    public Fish(String name) {
        this.name = name;
    }

    @Override
    public void swim() {
        System.out.println("I'm a fish " + name + ". I swim by moving my fins.");

    }

public class UBoat implements Swim {

    private int speed;

    public UBoat(int speed) {
        this.speed = speed;
    }

    @Override
    public void swim() {
        System.out.println("The submarine is sailing, rotating the propellers, at a speed" + speed + " knots.");
    }
}
Método main:
public class Main {

    public static void main(String[] args) {
        Swim human = new Human("Anton", 6);
        Swim fish = new Fish("whale");
        Swim boat = new UBoat(25);

        List<Swim> swimmers = Arrays.asList(human, fish, boat);
        for (Swim s : swimmers) {
            s.swim();
        }
    }
}
O resultado da execução de um método polimórfico definido em uma interface nos permite ver diferenças no comportamento dos tipos que implementam essa interface. Eles consistem em diferentes resultados de execução de métodos swim. Depois de estudar nosso exemplo, o entrevistador pode perguntar por que, ao executar o código demain
for (Swim s : swimmers) {
            s.swim();
}
Os métodos definidos nessas classes são chamados para nossos objetos? Como você seleciona a implementação desejada de um método ao executar um programa? Para responder a essas perguntas, precisamos falar sobre vinculação tardia (dinâmica). Por ligação queremos dizer estabelecer uma conexão entre uma chamada de método e sua implementação específica nas classes. Essencialmente, o código determina qual dos três métodos definidos nas classes será executado. Por padrão, Java usa ligação tardia (em tempo de execução, e não em tempo de compilação, como é o caso da ligação antecipada). Isso significa que ao compilar o código
for (Swim s : swimmers) {
            s.swim();
}
o compilador ainda não sabe de qual classe o código é Human, Fishou se Uboatele será executado no swim. Isso será determinado somente quando o programa for executado graças ao mecanismo de despacho dinâmico - verificando o tipo do objeto durante a execução do programa e selecionando a implementação desejada do método para este tipo. Se lhe perguntarem como isso é implementado, você pode responder que ao carregar e inicializar objetos, a JVM constrói tabelas na memória e nelas associa variáveis ​​​​com seus valores e objetos com seus métodos. Além disso, se um objeto é herdado ou implementa uma interface, primeiro é verificada a presença de métodos substituídos em sua classe. Se houver, eles estão vinculados a este tipo, caso contrário, é pesquisado um método que esteja definido na classe um nível superior (no pai) e assim sucessivamente até a raiz em uma hierarquia multinível. Falando sobre polimorfismo em OOP e sua implementação em código de programa, notamos que é uma boa prática usar descrições abstratas para definir classes base usando classes abstratas e também interfaces. Esta prática é baseada no uso de abstração - isolando comportamentos e propriedades comuns e encerrando-os dentro de uma classe abstrata, ou isolando apenas o comportamento comum - nesse caso criamos uma interface. Construir e projetar uma hierarquia de objetos baseada em interfaces e herança de classes é um pré-requisito para cumprir o princípio do polimorfismo OOP. Em relação à questão do polimorfismo e inovações em Java, podemos citar que ao criar classes e interfaces abstratas, a partir do Java 8, é possível escrever uma implementação padrão de métodos abstratos em classes base utilizando a palavra-chave default. Por exemplo:
public interface Swim {
    default void swim() {
        System.out.println("Just floating");
    }
}
Às vezes eles podem perguntar sobre os requisitos para declarar métodos em classes base para que o princípio do polimorfismo não seja violado. Tudo é simples aqui: esses métodos não devem ser estáticos , privados e finais . Private disponibiliza o método apenas na classe e você não pode substituí-lo no descendente. Static torna o método propriedade da classe, não do objeto, portanto o método da superclasse sempre será chamado. Final tornará o método imutável e oculto de seus herdeiros.

O que o polimorfismo nos oferece em Java?

A questão sobre o que o uso do polimorfismo nos proporciona provavelmente também surgirá. Aqui você pode responder brevemente, sem se aprofundar muito no mato:
  1. Permite substituir implementações de objetos. É nisso que se baseia o teste.
  2. Fornece capacidade de expansão do programa - torna-se muito mais fácil criar uma base para o futuro. Adicionar novos tipos com base nos existentes é a maneira mais comum de expandir a funcionalidade de programas escritos no estilo OOP.
  3. Permite combinar objetos com um tipo ou comportamento comum em uma coleção ou array e manipulá-los uniformemente (como em nossos exemplos, fazendo todos dançarem - um método danceou nadarem - um método swim).
  4. Flexibilidade na criação de novos tipos: você pode optar por implementar um método de um pai ou substituí-lo em um filho.

Palavras de despedida para a jornada

O princípio do polimorfismo é um tópico muito importante e amplo. Abrange quase metade da OOP do Java e uma boa parte dos fundamentos da linguagem. Você não conseguirá definir esse princípio em uma entrevista. A ignorância ou a incompreensão disso provavelmente encerrará a entrevista. Portanto, não tenha preguiça de testar seus conhecimentos antes do teste e atualizá-los se necessário.
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION