[Pesquisar este blog]

quinta-feira, 5 de abril de 2018

POO::Plena-25-Classes Internas e Anônimas

POO-P-24-Polimorfismo, upcasting e downcastinPOO-A-26-Tratamento Avançado de Exceções
Os mecanismos de definição de classe presentes todas as linguagens de programação OO permitem o aninhamento de classes, criando o que se denominam classes internas. Assim é possível criar uma classe contida em outra classe, que pode estar contida em outra e, assim, sucessivamente.

Classes aninhadas e internas

O aninhamento de classes permite que uma classe seja declarada como membro de outra, o que corresponde a uma sintaxe de natureza recursiva [4][6][7]. Toda classe aninhada (nested class) é membro de uma classe externa.

Tal como a classe externa, a classe aninhada pode conter campos, métodos, construtores e outras classes aninhadas, como mostra a figura anterior.

O exemplo que segue mostra uma classe denominada Externa, que possui alguns campos, métodos e construtores, públicos e privados, incluindo um membro que é uma classe aninhada pública denominada Aninhada.

public class Externa {
   private double valor1;
   public int valor2;

   public Externa() { this(1.5); }
   public Externa(double valor1) {
      this.valor1 = valor1;
   }

   public void exibir() {
      System.out.printf("%f, %d\n", valor1, valor2);
   }

   public class Aninhada {
      private float valor3;

      public Aninhada(float valor3) {
         this.valor3 = valor3;
      }

      public double produto() {
         return valor1 * valor2 * valor3;
      }
      public void exibir() {
         System.out.printf("%f, %d, %f\n",
            valor1, valor2, valor3);
      }
   }
}

A classe Aninhada, que é membro público da classe Externa, também possui campos, métodos e construtores, públicos e privados. As regras de acesso, isto é, a visibilidade dos seus membros é como sempre.

A classe aninhada, como membro de sua classe externa, possui acesso irrestrito a todos os membros  desta, independentemente de sua visibilidade declarada [6][7]. Os membros públicos da classe aninhada serão visíveis externamente a classe, enquanto os privados serão exclusivos da implementação da classe.

A instanciação de objetos da classe externa é feita como para qualquer classe comum, ou seja:

// instanciação da classe Externa
Externa externa1 = new Externa(20.16);
Externa externa2 = new Externa(14.04);

Já a instanciação de objetos da classe aninhada requer a existência de uma instância da sua classe externa para que um objeto do seu tipo possa ser instanciado. Isto evidencia o tipo de relacionamento de dependência que existe entre uma classe aninhada e a classe externa que a contém.

// instanciação da classe Aninhada
Externa.Aninhada aninhada1 = externa1. new Aninhada(1.2f);
Externa.Aninhada aninhada2 = externa2. new Aninhada(3.4f);

Na instanciação da classe Aninhada temos uma sintaxe que, de fato, não é familiar:
  • A declaração de um objeto da classe aninhada requer a qualificação completa: Externa.Aninhada.
  • A instanciação de objeto da classe aninhada requer uma instância da sua classe externa.
  • Além disso, se observa uma sintaxe não usual associada ao operador new!

Já o uso dos objetos, tanto da classe externa como da aninhada, é feito da maneira usual, como mostra o programa simples que segue:

public class TestaExternaAninhada {
   public static void main(String[] a) {
      // uso dos objetos da classe Externa
      externa1.valor2 = 1;
      externa1.exibir();

      // uso dos objetos da classe Aninhada
      System.out.println(aninhada1.produto());
      aninhada1.exibir();

      // uso dos objetos da classe Externa
      externa2.valor2 = 2;
      externa2.exibir();

      // uso dos objetos da classe Aninhada
      System.out.println(aninhada2.produto());
      aninhada2.exibir();
   }
}

Cada objeto da classe aninhada tanto faz referência ao seu objeto correspondente da classe externa, como tem acesso ao conteúdo específico desta instância, como ilustrado na figura que segue.
O resultado ilustrado nesta figura mostra:
  • Linha_1:
    valores 20.16 e 1, armazenados nos campos valor1 e valor2 da instância da classe Externa denominada externa1, exibidos pelo seu método exibir();
  • Linha_2:
    valor 24.19..., resultado do método produto() acionado para a instância da classe Aninhada chamada aninhada1  (valor1*valor2*valor3=20.16*1*1.2);
  • Linha_3:
    valores 20.16, 1 e 1.2, resultado do método exibir() acionado para a instância da classe Aninhada denominada aninhada1;
  • Linha_4:
    valores 14.04 e 2, armazenados nos campos valor1 e valor2 da instância da classe Externa denominada externa2, exibidos pelo seu método exibir();
  • Linha_5:
    valor 95472..., resultado do método produto() acionado para a instância da classe Aninhada chamada aninhada2  (valor1*valor2*valor3=14,04*2*3.4);
  • Linha_6:
    valores 14,04, 2 e 3.4, resultado do método exibir() acionado para a instância da classe Aninhada denominada aninhada2.

Com isso vemos que é possível utilizar membros de instâncias de classes aninhadas como de qualquer classe, apesar de que a forma de instanciação é um pouco diferente.

Como a existência de instâncias das classes aninhadas depende de uma instância da classe externa, esse tipo de classe aninhada recebe o nome (mais comum) de classe interna ou inner class. A classe externa, por analogia, é conhecida como outter class [4][6][7].

O uso destes objetos, após sua instanciação, é semelhante a qualquer outro, como já vimos.

Classes aninhadas estáticas

Uma classe aninhada pode ser declarada como um membro estático de sua classe externa, num outro uso do modificador static [4][6][7]. Desta forma a classe aninhada estática tem acesso irrestrito apenas aos membros estáticos da classe externa, independente de sua visibilidade.

public class OutraExterna {
   private static int valor1;
   public int valor2;

   public static class AninhadaEstatica {
      public void exibir () {
         System.out.println("valor1 = "+ valor1);
      }
   }
}

Classes aninhadas estáticas não são consideradas classes internas, pois não existe uma relação de dependência forte como aquela existente nas classes internas, ou seja, como nas classes aninhadas como membros não estáticos.

Outro aspecto particular é que a instanciação de classes aninhadas estáticas independe de instâncias da classe externa, sendo feito como segue:

OutraExterna.AninhadaEstatica aninhadaEstatica;
aninhadaEstatica = new OutraExterna.AninhadaEstatica();

Aqui é possível ver que a declaração e instanciação de um objeto de uma classe aninhada estática requer apenas sua qualificação completa.

O exemplo que segue mostra um programa que cria uma lista arbitrariamente longa de palavras. As palavras são armazenadas numa lista ligada simples, cujos nodos são objetos da classe No, uma classe aninhada estática. Como não se pretende que esta classe No seja utilizada por outros programas, seu caráter auxiliar e privado mostra como é conveniente sua declaração como classe aninhada estática.

public class Lista {
   // classe aninhada estática
   private static class No {
      public String nome;
      public No proximo;

      @SuppressWarnings("unused")
      public No() {
         this(null, null); // aciona construtor parametrizado
      }
      public No(String n, No p) {
         nome = n;
         proximo = p;
      }
   }

   public static void main (String arg[]) {
      No lista = null; // lista vazia (sem nós)
      Scanner sc = new Scanner(System.in); // prepara console
      do { // laço para solicitação de dados
         System.out.println("Digite uma palavra (. encerra):");
         String aux = sc.next();
         if (!aux.equals(".")) {
            No novo = new No(aux, lista);
            lista = novo;
         }
      } while (!aux.equals("."));
      System.out.println("Lista das Palavras:");
      No no = lista; // variável auxiliar para navegar lista
      while (no!=null) {
         System.out.print(no.nome + " ");
         no = no.proximo;
      }
      System.out.println("<.>");
   }
} // Fim da classe Lista


Classes anônimas

Uma classe anônima (anonymous class) é uma classe interna, de papel auxiliar, declarada sem nome e definida como uma subclasse ou como a realização de uma interface específica [4][6][7].

Tem o propósito de servir para a instanciação de um único objeto que, provavelmente, não será utilizado em outro contexto, portanto não requerendo que seja armazenada uma referência para o objeto criado.

Considere a situação que segue, de definição de duas classes semelhantes destinadas a criação de uma thread.

// Uma subclasse da classe Thread
public class A extends Thread {
   public void run () {
   /* código de run omitido */
   }
}
// A realização da interface Runnable
public class B implements Runnable {
   public void run () {
   /* código de run omitido */
   }
}

Objetos das classes A e B poderiam ser instanciados como:

// Uma instância de A
A objeto1 = new A ();

// Uma instância de B
B objeto2 = new B ();

Mas, se apenas um objeto destas classes é usado, a definição de A ou B, como classes independentes, aninhadas ou internas é desnecessária, pois não serão reutilizadas para criação de outros objetos.

O uso de classes anônimas é conveniente nestas situações, onde é possível substituir as classes A e B criadas anteriormente pelo código que segue:

// Classe anônima derivada de Thread
// e que equivale a classe A.
Thread objeto1 = new Thread ( ) {
   public void run () {
   /* código de run omitido */
   }
};

// Classe anônima que realiza Runnable
// e que equivale a classe B.
Runnable objeto2 = new Runnable ( ) {
   public void run () {
   /* código de run omitido */
   }
};

Aqui o uso de um construtor é complementado com um bloco declarativo de código que:
  • implementa métodos abstratos necessários, ou
  • substitui métodos existentes da superclasse indicada; ou
  • realiza uma interface diretamente implementando os métodos nela especificados.

Deve ser ressaltado que o bloco declarativo não é acompanhado de um nome específico, ou seja, temos uma classe sem nome, ou seja, anônima.

Esta construção é conveniente quando é requerido apenas um objeto da classe, pois como a classe criada é anônima, não será possível utilizá-la novamente para criar outros objetos.

Um uso bastante típico de classe anônimas é no registro de objetos processadores de eventos (listeners) em interfaces gráficas (GUI - Graphical User Interfaces), como nos fragmentos que seguem:

// Instância de botão presente na GUI
JButton button1 = new JButton("Fazer Ação");

// Registro do listener: acionamento do botão
// provoca invocação do método
:
button1.addActionListener( new ActionListener() {
   @Override
   public void actionPerformed(ActionEvent evt) {
      // método acionado pelo botão
      acao_Botao1(evt);
   }
});
:
protected void acao_Botao1(ActionEvent evt) {
   // Ação correspondente do botão
}

A classe anônima utilizada neste exemplo realiza a interface java.awt.event.ActionListener, que requer apenas a implementação do método actionPerformed(Event), o qual "desvia" o acionamento do botão para outro método, declarado como membro da classe externa.

Dado que são elementos auxiliares, as classes aninhadas somente devem ser criadas quando sua implementação se limita a dois ou três métodos simples. Classes aninhadas extensas pioram a legibilidade dos programas e devem ser evitadas.

Ao mesmo tempo, as classes anônimas são muito utilizadas: na criação de iterators (classes para navegação de coleções); e no processamento de eventos em aplicações dotadas de interfaces gráficas. Nestas situações, permitem a definição rápida de subclasses e objetos que implementam interfaces específicas de maneira simples.

POO-P-24-Polimorfismo, upcasting e downcastinPOO-A-26-Tratamento Avançado de Exceções

Referências Bibliográficas

[1] JAMSA, K.; KLANDER, L.. Programando em C/C++: a bíblia. São Paulo: Makron Books, 1999.
[2] PAGE_JONES, M.. Fundamentos do Desenho Orientado a Objeto com UML. São Paulo: Makron Books, 2001.
[3] SOMMERVILLE, I.. Software Engineering. 6th. Ed. Harlow: Pearson, 2001.
[4] DEITEL, H.M.; DEITEL, P.J.. Java: como programar. 6a. Ed. São Paulo: Pearson Prentice-Hall, 2005.
[5] SAVITCH, W.. C++ Absoluto. São Paulo: Pearson Addison-Wesley, 2004.
[6] JANDL JR., P. Introdução ao C++. São Paulo: Futura, 2003.
[7] JANDL JR., P.. Java - guia do programador. 3a. ed. São Paulo: Novatec, 2015.
[8] RUMBAUGH, J.; BLAHA, M.; PREMERLANI, W.; EDDY, F.; LORENSEN, W.. Object-oriented modeling and design. Englewoods Cliffs: Prentice-Hall, 1991.
[9] STROUSTRUP, B.. The C++ Programming Language. 3rd Ed. Reading: Addison-Wesley, 1997.
[10] LANGSAM, Y.; AUGENSTEIN, M. J.; TENENBAUM, A. M.. Data structures using C and C++. 2nd Ed. Upper Saddle River: Prentice-Hall, 1996.
[11] WATSON, K.; NAGEL, C.; PEDERSEN, J.H.; REID, J.D.; SKINNER, M.; WHITE, E.. Beginning Microsoft Visual C# 2008. Indianapolis: Wiley Publishing, 2008.
[12] GAMMA, E.; HELM, R.; JOHNSON, R.; VLISSIDES, J.. Design Patterns: Elements of Reusable Object-Oriented Software. Reading, MA: Addison-Wesley, 1995.

segunda-feira, 2 de abril de 2018

POO::Plena-24-Polimorfismo, upcasting e downcasting

POO-P-23-PolimorfismoPOO-A-25-Classes internas e anônimas
O polimorfismo é a característica mais importante da OO e consiste no mecanismo pelo qual vários tipos de objetos, ou seja, formas diferentes de implementação, podem ser tratadas como sendo de um tipo comum.
Todas as linguagens orientadas a objetos possuem uma classe que constitui a raiz de sua hierarquia de tipos. A partir desta classe são criadas outras por meio da herança, a qual pode ser usada de maneira implícita ou explícita. Na plataforma Java a raiz da hierarquia é a classe java.lang.Object; enquanto que no C# a classe-raiz é System.Object.

Desta maneira, qualquer que seja a hierarquia de classes considerada, todas as classes ali existentes são subclasses de Object, sendo assim, uma variável do tipo Object pode armazenar referências de qualquer tipo de objeto.

O polimorfismo permite que um objeto seja, transparentemente, tratado como qualquer outro tipo ascendente (superclasses da qual é derivado), e também como sendo do seu tipo verdadeiro, isto é, como foi instanciado de fato. Isso possibilita a generalização, ou seja, uma operação que faz o contrário da herança, que permite a especialização.

Como vimos, um objeto cuja classe é derivada de outra é uma composição de objetos em camadas. A camada mais externa corresponde ao tipo real do objeto (por exemplo Gerente) e nela se encontram as adições providas por esta classe (sua especialização em relação à camada anterior). A camada mais interna é sempre aquela que corresponde ao tipo Object, onde estão presentes seus elementos. As camadas intermediárias são aquelas fornecidas pelos descendentes de Object até a camada mais externa. A figura que segue ilustra esta estrutura em camadas de um objeto de uma possível classe Comissionado.

Conforme o tipo da referência usada para acessar um objeto, determina-se quais camadas poderão efetivamente ser usadas. Se utilizamos uma referência do tipo real (de instanciação), temos acesso a todas os membros públicos deste tipo e de todos os tipos ancestrais (ascendentes). Na figura acima, a referência do tipo Comissionado permite acessar todos os membros públicos do objeto presentes na camada mais externa (Comissionado), na camada intermediária (Funcionario) e também na camada mais interna (Object).

Mas se a referência é do tipo Funcionario, só é possível acessar os membros desta camada (Funcionario) e das mais internas (no caso apenas Object). Uma referência de tipo Funcionário não consegue acessar nada presente em camadas mais externas do que seu tipo, porque sua classe não prevê sua existência, pois foram adicionadas nas especializações (subclasses) posteriores.

Da mesma maneira, uma referência do tipo Object só consegue acessar a porção referente a este tipo presente em objetos de qualquer classe.

Por conta da construção dos objetos em múltiplas camadas e do comportamento dos diferentes tipos de referências em relação às camadas presentes nos objetos, temos duas importantes operações decorrentes do polimorfismo:
  • up type casting (ou apenas upcasting) e
  • down type casting (ou apenas downcasting).


Polimorfismo e upcasting

O uptype casting, upcasting ou coerção para supertipo é a operação disponível na orientação a objetos que permite que uma referência de superclasse utilize a porção correspondente nos objetos descendentes de sua hierarquia. Em outras palavras, uma referência de um certo tipo pode acessar sua camada correspondente em qualquer objeto de tipo derivado, isto é, em qualquer subclasse.

Observe novamente a hierarquia de objetos. Funcionario (que é uma classe derivada de Object) dá origem a várias classes, tais como Caixa, Gerente, Motorista ou Comissionado.

Uma referência do tipo Gerente permite acessar todas as camadas presentes nos objetos instanciados como deste tipo, assim como apenas uma referência do tipo Comissionado permite acessar todos os elementos deste tipo, como ilustrado na figura que segue.

Referências deste tipo podem ser obtidas como no fragmento abaixo:

Gerente g2 = new Gerente(2345);
Comissionado c2 = new Comissionado(3456);

No entanto, como um Gerente é um Funcionario (é subclasse deste tipo e possui uma camada interna correspondente), uma referência do tipo Funcionario pode ser usada para acessar tal camada (e as interiores) em qualquer objeto do tipo Gerente ou Comissionado, pois é garantido que tais objetos possuem a camada interior correspondente a Funcionario.

Por meio do polimorfismo, uma referência de tipo Funcionario pode acessar objetos de tipos diferentes, embora tal acesso seja limitado a camada correspondente à referência, portanto de objetos cujos tipos são subclasses do tipo da referência. Assim é válido fazer:

// Funcionario é superclasse de Gerente
// Gerente é subclasse de Funcionario
Funcionario f2 = g2;
Funcionario f3 = new Gerente(2346);

// Funcionario é superclasse de Comissionado
// Comissionado é subclasse de Funcionario
Funcionario f4 = c2;
Funcionario f5 = new Comissionado(3457);

Aqui foi usado o upcasting, ou seja, uma referência de um tipo ancestral (Funcionario) foi usada para acessar a camada correspondente em objetos de seus subtipos (Gerente e Comissionado). Assim, objetos de tipos diferentes são acionados por um mesmo conjunto de métodos, ou seja, recebem os mesmos tipos de mensagens. Esta situação é ilustrada na figura que segue.

Quando o tipo de referência usada não é compatível com a hierarquia do objeto, o compilador sinaliza o erro e impede que o código seja compilado, evitando que objetos sejam incorretamente manipulados por referências inadequadas.

Já referências de tipo Object, por meio do upcasting, podem acessar qualquer tipo de objeto, pois qualquer tipo é subclasse de Object e todo objeto possui como camada mais interior aquela que corresponde a Object, como mostra a figura que segue.

Assim temos que é possível fazer:

Object o1 = new Gerente(2367);
Object o2 = new Comissionado(3489);
Object o3 = f2;
Object o4 = f5;
Object o5 = new String("Upcasting");

Isto significa que, sob certos aspectos, uma operação de up type casting é como uma operação implícita de coerção (conversão de tipo) que possibilita o uso mais genérico de um objeto, mas só é válida quando utiliza subclasse do tipo real do objeto.

O upcasting é um mecanismo polimórfico de generalização, que permite o uso de um objeto por meio de referências de seus supertipos, embora as operações estejam limitadas as características da superclasse usada. Isto é conveniente para tratar famílias de objetos, pois uma referência de superclasse permite manipular qualquer um de seus subtipos, ao invés de requerer referências de tipos específicos.

Isto flexibiliza o armazenamento de objetos em arrays e outras estruturas de dados; possibilita que métodos retornem objetos de tipos diferentes (mas de uma mesma família; além de facilitar alterações no projeto de sistemas.

Por exemplo, no fragmento de código que segue, um array de tipo Funcionario pode conter objetos de qualquer tipo derivado de Funcionario:

// cria array de Funcionario
Funcionario folha[] = new Funcionario[3];

// instancia e ajusta objeto tipo funcionário
Funcionario f = new Funcionario(1234);
f.setSalarioBase(850);

// atribui objeto Funcionario ao array de mesmo tipo
folha[0] = f;

// instancia e ajusta objeto tipo Comissionado
Comissionado c = new Comissionado(2345);
f.setSalarioBase(650);

// atribui objeto Comissionado ao array
folha[1] = c;

// instancia objeto Gerente, atribuindo-o ao array, ajustando-o
folha[2] = new Gerente(3456);
folha[2].setSalarioBase(4500);

Nos elementos [1] e [2] do array, cujo tipo declarado é Funcionario, ocorre o upcasting, pois objetos de subclasses de Funcionario são ali armazenados.

O uso do array de Funcionario permite generalizar o tratamento de funcionários de tipos diferentes, como no outro fragmento que segue:

double folhaBaseTotal = 0;
for(int i=0; i<folha.length; i++) {
   folhaBaseTotal += folha[i].getSalarioBase();
}
System.out.println("Folha total = " + folhaBaseTotal);

No laço existente, a despeito do tipo real, todas as características do tipo Funcionario estão disponíveis por meio das referências existentes no array de tipo Funcionario. No entanto, ao acionar método getSalarioBase(), disponível na interface do tipo Funcionario, é de fato acionada a implementação existente na classe efetiva do objeto armazenado naquela posição, pois esta substitui (method overriding) a definida em Funcionario. Assim, este laço de tratamento generalizado acaba usado a operação definida nas subclasses de Funcionario, sem qualquer tratamento condicional.

O upcasting permite, assim, criar operações genéricas para lidar com múltiplos tipos de objetos, relacionados numa hierarquia de classes por meio de um ancestral comum, como segue:

double getFolhaBaseTotal(Funcionario[] folha ){
   double folhaBaseTotal = 0;
   for(int i=0; i<folha.length; i++) {
      folhaBaseTotal += folha[i].getSalarioBase();
   }
   return folhaBaseTotal;
}

O método getFolhaBaseTotal(Funcionario[]) recebe um array de tipo Funcionario. Como antes, a despeito do tipo real, todas as características do tipo Funcionario estão disponíveis por meio das referências existentes no array de tipo Funcionario. Mas, dada a sobreposição de métodos, serão usadas as implementações dos tipos específicos. Então, mesmo que surjam novos subtipos derivados de Funcionario, esta operação continuará a funcionar, sem necessidade de qualquer alteração.

Polimorfismo e downcasting

O polimorfismo também dispõe de uma operação que permite converter uma referência de superclasse em outra de subclasse, portanto mais especializada, que é chamada de down type casting, downcasting ou coerção para subtipo.


Como mostra a figura, o downcasting é uma operação inversa ao upcasting. Enquanto o upcasting ocorre quando se utiliza uma referência de supertipo (ancestral) para o tratamento de um objeto; no downcasting é usada uma referência de subtipo (descendente) para o tratamento de um objeto.

Aqui, a referência desejada como resultado do downcasting passa a utilizar uma camada mais externa do objeto, decorrente da especialização. Mas como o compilador não pode garantir que o objeto referenciado possua tais camadas adicionais, esta operação deve ser explicitamente indicada pelo programador, como uma espécie de autorização para sua realização.

Observe no fragmento de código que segue, como uma referência f3, de tipo Funcionario ou mesmo Object, é transformada em um objeto de tipo Comissionado (que é uma de subclasses); ou como uma referência de Object é transformada em outra do tipo String:

Comissionado c = (Comissionado) f3;
String str = (String) obj;

A transformação de tipo, down type casting ou coerção possibilita transformar uma referência do objeto em outra de seu tipo real ou de tipo mais próximo. Como nem sempre isso será possível, porque o objeto não foi, de fato, criado como algo daquele tipo, a compilação do código requer a explicitação da conversão por meio da indicação do tipo de destino entre parêntesis.

Se a referência não pode ser transformada na tipo indicado, o downcasting provoca o lançamento da exceção ClassCastException, como nestes exemplos inválidos:

// Upcasting OK
Funcionario f4 = new Gerente (6789);
// Downcasting INVALIDO
Comissionado c = (Comissionado) f4;

Para evitar esse tipo de exceção, existe o operador instanceof.

Operador instanceof

O operador especial instanceof, que retorna um resultado booleano, permite verificar, em tempo de execução, se um objeto é de um tipo específico ou de suas subclasses. A sua sintaxe é:

<objeto> instanceof <Tipo>

O uso deste operador retorna true, quando o objeto é uma instância direta do tipo indicado ou de alguma de suas subclasses; ou false, quando o objeto não é uma instancia do tipo indicado ou de qualquer uma de suas subclasses.

O uso do operador instanceof permite evitar erros em operações de downcasting, mas sua aplicação não pode ser generalizada, devendo empregar tipos específicos. Um exemplo:

Funcionario func = new Gerente(5678);
if (func instanceof Gerente) {
   Gerente g = (Gerente) func; // downcasting
   System.out.println("Gerente depto: " + g.getGerencia() );
}

Outro exemplo:

Integer n = new Integer (123456789);
if (n instanceof Number) {
   count= n.intValue();
}

Com o uso de instanceof é possível garantir a execução de operações de downcasting sem a ocorrência de exceções ClassCastException decorrentes de coerção inválida de objetos.

Considerações finais

O polimorfismo é o mecanismo principal da OO que pode se manifestar tanto em tempo de compilação, quanto em tempo de execução.

Em tempo de compilação o polimorfismo aparece como a sobrecarga de métodos (method overload), também como sobrecarga de operadores em algumas linguagens OO, também sendo chamado de ligação precoce (early binding). Já em tempo de execução, aparece como sobreposição de métodos (method overriding), que é decorrência do uso do mecanismo da herança onde a versão mais especializada de um método, presente em subclasses, é acionada, situação conhecida como ligação tardia (late binding).

Assim, com o polimorfismo é possível:
  • Que subclasses forneçam implementações diferentes de métodos com uma mesma assinatura (ou seja, a sobreposição de métodos ou method overriding), provendo então comportamentos distintos, modificados em cada classe derivada conforme a especialização desejada.
  • Que referências de superclasse acionem métodos de subclasse comuns à superclasse (sobrepostos) em tempo de execução, ou seja, invoquem métodos diferentes, mas com a mesma assinatura, possibilitando que tipos diferentes recebam as mesmas mensagens.

Finalmente, grande parte da flexibilidade e elegância das linguagens de programação OO decorrem do polimorfismo.

POO-P-23-Polimorfismo
POO-A-25-Classes internas e anônimas

Referências Bibliográficas

[1] JAMSA, K.; KLANDER, L.. Programando em C/C++: a bíblia. São Paulo: Makron Books, 1999.
[2] PAGE_JONES, M.. Fundamentos do Desenho Orientado a Objeto com UML. São Paulo: Makron Books, 2001.
[3] SOMMERVILLE, I.. Software Engineering. 6th. Ed. Harlow: Pearson, 2001.
[4] DEITEL, H.M.; DEITEL, P.J.. Java: como programar. 6a. Ed. São Paulo: Pearson Prentice-Hall, 2005.
[5] SAVITCH, W.. C++ Absoluto. São Paulo: Pearson Addison-Wesley, 2004.
[6] JANDL JR., P. Introdução ao C++. São Paulo: Futura, 2003.
[7] JANDL JR., P.. Java - guia do programador. 3a. ed. São Paulo: Novatec, 2015.
[8] RUMBAUGH, J.; BLAHA, M.; PREMERLANI, W.; EDDY, F.; LORENSEN, W.. Object-oriented modeling and design. Englewoods Cliffs: Prentice-Hall, 1991.
[9] STROUSTRUP, B.. The C++ Programming Language. 3rd Ed. Reading: Addison-Wesley, 1997.
[10] LANGSAM, Y.; AUGENSTEIN, M. J.; TENENBAUM, A. M.. Data structures using C and C++. 2nd Ed. Upper Saddle River: Prentice-Hall, 1996.
[11] WATSON, K.; NAGEL, C.; PEDERSEN, J.H.; REID, J.D.; SKINNER, M.; WHITE, E.. Beginning Microsoft Visual C# 2008. Indianapolis: Wiley Publishing, 2008.
[12] GAMMA, E.; HELM, R.; JOHNSON, R.; VLISSIDES, J.. Design Patterns: Elements of Reusable Object-Oriented Software. Reading, MA: Addison-Wesley, 1995.