Polimorfismo em Java

Polimorfismo em Java

1. Visão geral

Todas as linguagens de programação orientada a objetos (OOP) devem exibir quatro características básicas: abstração, encapsulamento, herança e polimorfismo.

Neste artigo, cobrimos dois tipos principais de polimorfismo:*static or compile-time polymorphism* and dynamic or runtime *polymorphism*. O polimorfismo estático é imposto em tempo de compilação, enquanto o polimorfismo dinâmico é realizado em tempo de execução.

2. Polimorfismo estático

De acordo comWikipedia, o polimorfismo estático é uma imitação depolymorphism which is resolved at compile time and thus does away with run-time virtual-table lookups.

Por exemplo, nossa classeTextFile em um aplicativo gerenciador de arquivos pode ter três métodos com a mesma assinatura do métodoread():

public class TextFile extends GenericFile {
    //...

    public String read() {
        return this.getContent()
          .toString();
    }

    public String read(int limit) {
        return this.getContent()
          .toString()
          .substring(0, limit);
    }

    public String read(int start, int stop) {
        return this.getContent()
          .toString()
          .substring(start, stop);
    }
}

Durante a compilação do código, o compilador verifica se todas as invocações do métodoread correspondem a pelo menos um dos três métodos definidos acima.

3. Polimorfismo dinâmico

Com polimorfismo dinâmico, oJava Virtual Machine (JVM) handles the detection of the appropriate method to execute when a subclass is assigned to its parent form. Isso é necessário porque a subclasse pode substituir alguns ou todos os métodos definidos na classe pai.

Em um aplicativo de gerenciador de arquivos hipotético, vamos definir a classe pai para todos os arquivos chamadosGenericFile:

public class GenericFile {
    private String name;

    //...

    public String getFileInfo() {
        return "Generic File Impl";
    }
}

Também podemos implementar uma classeImageFile que estendeGenericFile, mas substitui o métodogetFileInfo() e acrescenta mais informações:

public class ImageFile extends GenericFile {
    private int height;
    private int width;

    //... getters and setters

    public String getFileInfo() {
        return "Image File Impl";
    }
}

Quando criamos uma instância deImageFilee atribuímos a uma classeGenericFile, uma conversão implícita é feita. No entanto, a JVM mantém uma referência à forma real deImageFile.

The above construct is analogous to method overriding. Podemos confirmar isso invocando o métodogetFileInfo():

public static void main(String[] args) {
    GenericFile genericFile = new ImageFile("SampleImageFile", 200, 100,
      new BufferedImage(100, 200, BufferedImage.TYPE_INT_RGB)
      .toString()
      .getBytes(), "v1.0.0");
    logger.info("File Info: \n" + genericFile.getFileInfo());
}

Como esperado,genericFile.getFileInfo() aciona o métodogetFileInfo() da classeImageFile, conforme visto na saída abaixo:

File Info:
Image File Impl

4. Outras características polimórficas em Java

Além desses dois tipos principais de polimorfismo em Java, existem outras características na linguagem de programação Java que exibem polimorfismo. Vamos discutir algumas dessas características.

4.1. Coerção

A coerção polimórfica lida com a conversão implícita de tipos feita pelo compilador para evitar erros de tipo. Um exemplo típico é visto em uma concatenação de número inteiro e string:

String str = “string” + 2;

4.2. Sobrecarga do operador

Sobrecarga de operador ou método refere-se a uma característica polimórfica do mesmo símbolo ou operador com diferentes significados (formas), dependendo do contexto.

Por exemplo, o símbolo de mais (+) pode ser usado para adição matemática, bem como concatenaçãoString. Em ambos os casos, apenas o contexto (ou seja, tipos de argumento) determina a interpretação do símbolo:

String str = "2" + 2;
int sum = 2 + 2;
System.out.printf(" str = %s\n sum = %d\n", str, sum);

Resultado:

str = 22
sum = 4

4.3. Parâmetros polimórficos

O polimorfismo paramétrico permite que um nome de um parâmetro ou método em uma classe seja associado a diferentes tipos. Temos um exemplo típico abaixo, onde definimoscontent comoString e mais tarde comoInteger:

public class TextFile extends GenericFile {
    private String content;

    public String setContentDelimiter() {
        int content = 100;
        this.content = this.content + content;
    }
}

Também é importante observar quedeclaration of polymorphic parameters can lead to a problem known asvariable hiding onde uma declaração local de um parâmetro sempre substitui a declaração global de outro parâmetro com o mesmo nome.

Para resolver este problema, geralmente é aconselhável usar referências globais como a palavra-chavethis para apontar para variáveis ​​globais dentro de um contexto local.

4.4. Subtipos polimórficos

O subtipo polimórfico permite comodamente atribuir vários subtipos a um tipo e esperar que todas as invocações no tipo disparem as definições disponíveis no subtipo.

Por exemplo, se temos uma coleção deGenericFiles e invocamos o métodogetInfo() em cada um deles, podemos esperar que a saída seja diferente dependendo do subtipo do qual cada item da coleção foi derivado :

GenericFile [] files = {new ImageFile("SampleImageFile", 200, 100,
  new BufferedImage(100, 200, BufferedImage.TYPE_INT_RGB).toString()
  .getBytes(), "v1.0.0"), new TextFile("SampleTextFile",
  "This is a sample text content", "v1.0.0")};

for (int i = 0; i < files.length; i++) {
    files[i].getInfo();
}

Subtype polymorphism is made possible by a combination ofupcasting and late binding. Upcasting envolve a conversão da hierarquia de herança de um supertipo para um subtipo:

ImageFile imageFile = new ImageFile();
GenericFile file = imageFile;

O efeito resultante do acima é que métodos específicos deImageFile- não podem ser chamados no novo upcastGenericFile. No entanto, os métodos no subtipo substituem os métodos semelhantes definidos no supertipo.

Para resolver o problema de não conseguir chamar métodos específicos de subtipo ao fazer a conversão para um supertipo, podemos fazer uma redução da herança de um supertipo para um subtipo. Isso é feito por:

ImageFile imageFile = (ImageFile) file;

Late bindingstrategy helps the compiler to resolve whose method to trigger after upcasting. No caso de imageFile#getInfo vsfile#getInfo no exemplo acima, o compilador mantém uma referência ao métodogetInfo deImageFile.

5. Problemas com polimorfismo

Vejamos algumas ambigüidades no polimorfismo que podem levar a erros de tempo de execução se não forem verificados corretamente.

5.1. Identificação de tipo durante o downcasting

Lembre-se de que anteriormente perdemos o acesso a alguns métodos específicos de subtipos após executar um upcast. Embora possamos resolver isso com um downcast, isso não garante a verificação de tipo real.

Por exemplo, se realizarmos um downcast upcast e subsequente:

GenericFile file = new GenericFile();
ImageFile imageFile = (ImageFile) file;
System.out.println(imageFile.getHeight());

Percebemos que o compilador permite um downcast deGenericFile emImageFile, embora a classe na verdade sejaGenericFilee nãoImageFile.

Consequentemente, se tentarmos invocar o métodogetHeight() na classeimageFile, obteremos umClassCastException, poisGenericFile não define o métodogetHeight():

Exception in thread "main" java.lang.ClassCastException:
GenericFile cannot be cast to ImageFile

Para resolver esse problema, a JVM executa uma verificação de informações do tipo em tempo de execução (RTTI). Também podemos tentar uma identificação de tipo explícita usando a palavra-chaveinstanceof assim:

ImageFile imageFile;
if (file instanceof ImageFile) {
    imageFile = file;
}

O acima ajuda a evitar uma exceçãoClassCastException em tempo de execução. Outra opção que pode ser usada é envolver o elenco em um blocotryecatch e capturar oClassCastException.

Deve-se observar queRTTI check is expensive devido ao tempo e recursos necessários para verificar com eficácia se um tipo está correto. Além disso, o uso frequente da palavra-chaveinstanceof quase sempre implica em um design ruim.

5.2. Problema de classe de base frágil

De acordo comWikipedia, base ou superclasses são consideradas frágeis se modificações aparentemente seguras em uma classe base podem causar o mau funcionamento das classes derivadas.

Vamos considerar uma declaração de uma superclasse chamadaGenericFile e sua subclasseTextFile:

public class GenericFile {
    private String content;

    void writeContent(String content) {
        this.content = content;
    }
    void toString(String str) {
        str.toString();
    }
}
public class TextFile extends GenericFile {
    @Override
    void writeContent(String content) {
        toString(content);
    }
}

Quando modificamos a classeGenericFile:

public class GenericFile {
    //...

    void toString(String str) {
        writeContent(str);
    }
}

Observamos que a modificação acima deixaTextFile em uma recursão infinita no métodowriteContent(), o que eventualmente resulta em um estouro de pilha.

Para resolver um problema de classe base frágil, podemos usar a palavra-chavefinal para evitar que as subclasses substituam o métodowriteContent(). A documentação adequada também pode ajudar. E por último mas não menos importante, a composição deve geralmente ser preferida à herança.

6. Conclusão

Neste artigo, discutimos o conceito básico de polimorfismo, com foco em vantagens e desvantagens.

Como sempre, o código-fonte deste artigo está disponívelover on GitHub.