Polimorfismo

Polimorfismo é uma característica importante em qualquer linguagem, especialmente se a linguagem é orientada a objetos. O polimorfismo é muitas vezes confundido com o próprio conceito de orientação a objetos, mas embora dependa dele em certa medida, é, na realidade, um conceito separado. Linguagens que não são orientadas a objetos também têm polimorfismo.

O polimorfismo está presente de diferentes maneiras. Estas são aquelas mais relevantes:

Variável Polimórfica

Este tipo de polimorfismo permite atribuir objetos de tipos (classes, interfaces, …​) diferentes a uma mesma variável. Para que isto seja possível deve existir uma relação de hierarquia entre o tipo da variável e o tipo do objeto, de tal modo que o tipo da variável seja o mesmo ou mais abstrato que o tipo do objeto.

Este tipo do polimorfismo é na realidade uma forma de encapsulamento que, por sua vez, é uma concretização do Princípio de Separação de Responsabilidade, e é necessária para uma linguagem orientada a objeto completa. A variável polimórfica também está no cerne o Principio de Substituição de Liskov.

No exemplo a seguir a variável texto não é polimórfica já que String é uma classe que não pode ser derivada. Não é possível estender String e, portanto, não é possível criar tipos menos abstratos que String.

	 String texto;
	 texto = "Apenas uma String";

No exemplo seguinte, a varável texto é polimórfica já que CharSequence é uma interface, o que, por definição, significa que é possível que exista um conjunto de possibilidades para os objetos que podem ser atribuídos a essa variável. Porque String, StringBuffer e StringBuilder implementam CharSequence é possivel escrevermos:

	CharSequence texto;
	texto = "Apenas uma String" ;
	texto = new StringBuffer ( "Ou um StringBuffer" ) ;
	texto = new StringBuilder ( "Ou um StringBuilder" ) ;

Utilizar interfaces e classes abstratas como os tipos das variáveis garante explicitamente que a variável é polimórfica. Utilizar classes abstratas e sobretudo interfaces como tipos de variáveis é considerado uma boa prática e é conhecido pela expressão programar para interfaces. Na realidade, isto apenas significa : utilize variáveis polimórficas por padrão e sempre que possível.

Sombreamento

Sombreamento (shadowing) é a capacidade de poder definir duas, ou mais, variáveis com o mesmo nome em escopos diferentes. O código a seguir apresenta o exemplo clássico:

public class UmaClasse {

 	String nome; // variável no escopo "classe"

	public void setName ( String nome ){ // variável no escopo "método"
	    this.nome = nome;
	}
}

O sombreamento permite que o mesmo nome seja utilizado para duas, ou mais, variáveis diferentes. No caso, a variável nome definida na classe e a variável nome definida no método. O detalhe com o uso de sombreamento é que as variáveis de maior escopo podem interagir com as de menor escopo. Contudo, como elas têm o mesmo nome, é necessário distingui-las. Para isso, é utilizada a palavra reservada this que representa o objeto corrente e contém, portanto, variáveis de escopo de classe. Caso o this não fosse utilizado junto de nome, o compilador assume que você está se referindo à variável de menor escopo; no caso a definida no método. Isso não é uma falha. É a utilidade do sombreamento.

O compilador Java é um tanto esperto e avisa o programador de falhas básicas. Uma delas é a tentativa de atribuir uma variável a ela própria. Isso é um código que não tem nenhum propósito e o compilador o avisará quando detectar essa situação.

Por isso se você escrever o código seguinte:

public class UmaClasse {

	String nome; // variável no escopo "classe"

	public void setName ( String nome ){ // variável no escopo "método"
		nome = nome;
	}
}

O compilador apresentará um aviso na linha 6 dizendo que a variável está sendo atribuída a ela própria. Isso demonstra que o compilador escolhe sempre a variável de menor escopo.

Sobrecarga

Sobrecarga (overload) é a capacidade de poder definir dois, ou mais métodos, numa mesma classe, ou suas derivadas, com o mesmo nome.

Para que exista sobrecarga não é necessário que a linguagem seja orientada a objetos e, por isso, à semelhança do sombreamento, a sobrecarga é normalmente apresentada com uma característica da linguagem em vez de uma forma de polimorfismo.

Embora os métodos possam ter o mesmo nome, eles têm obrigatoriamente que ter uma assinatura diferente. A assinatura de um método inclui o nome e o numero de parametros e seus tipos.

Eis alguns exemplos:

public int calculaIdade ( int ano , int mes, int dia ) ;
public int calculaIdade ( Date data ) ;
public int calculaIdade ( Calendar data ) ;

Sobrescrita

Sobrescrita (overriding) é a capacidade de poder redefinir a implementação de um método que já foi definido e implementado em uma classe superior na hierarquia de herança.

Para que exista sobrescrita é necessário que o método seja definido com a exata assinatura que existe na classe superior.

public class Somador {

	public int calculaSoma ( int inicio, int fim ){

		int soma = 0 ;
		for ( int i = inicio ; i <= fim ; i++ ){
		 soma += i;
		}
		return soma;
	}

}

public class SomadorInteligente extends Somador {

	public int calculaSoma ( int inicio, int fim ){

		int umAteInicio = inicio ( inicio + 1 ) / 2 ;
		int umAteFim = fim ( fim + 1 ) / 2 ;

		return umAteFim – umAteInicio;
	}

 }

O método calculaSoma em SomadorInteligente sobrescreve o método calculaSoma em Somador redefinindo a logica de soma.

Tipos Genéricos

Tipos genéricos permitem estabelecer relações fortes entre os tipos, mas sem especificar o tipo real que será utilizado. Tipos genéricos são uma forma de polimorfismo de variáveis, mas onde as variáveis se referem a tipos e não a instancias.

Esta forma de parametrização possibilita que as classes sejam criadas utilizando uma outra classe ou grupo de classes sem necessidade de fazer casting explícito e possibilitando maior controle sobre o funcionamento da classe. Tipos genéricos é uma parametrização do tipo e portanto é utilizando em qualquer lugar onde tipo é usado. Por exemplo, na definição de uma variável.

List<Number> numbers = new ArrayList<Number> () ;

Tipo generico, à semelhança da variável polimorfica, é também um forma de encapsulamento.

Auto-boxing e Auto-unboxing

Java, como outras linguagens, suporta tipos primitivos,ou seja, tipos de variáveis que não são objetos. Em algumas situações é necessário converter esses valores primitivos para objetos. Isso é conhecido como boxing (colocar na caixa). O processo inverso é chamado unboxing (retirar da caixa). Por exemplo, converter um int para um Integer pode ser feito assim:

	int inteiroPrimitivo = 5;
	Integer inteiroObjecto = Integer.valueOf(inteiroPrimitivo);

Auto-boxing e Auto-umboxing acontece quando próprio compilador faz essa operação. Por exemplo, ao obter o numero de uma lista.

	int inteiroPrimitivo = 5;
	List<Integer> lista = new ArrayList<Integer>(); // listas são de objetos Integer

	list.add(inteiroPrimitivo); // boxing. o int é convertido para Integer pelo compilador

	int inteiro = list.get(0); // unboxing. o integer é convertido de volta para int

Numero de argumentos indefinido (var args)

Algumas vezes é útil passar vários argumentos de um certo tipo para um método. Normalmente isso é feito pela utilização de arrays ou coleções. Contudo, nem sempre isso é prático do ponto de vista do programador.

O ideal seria passar os argumentos como se fossem argumentos individuais e capturá-los depois como um array. Essa funcionalidade conhecida como var args (variable arguments) é uma forma de polimorfismo já que a forma como o programador invoca o método é diferente da forma com que ele trabalha o resultado, contudo os dados são os mesmos.

public class Vector {

	public void setElements ( int … elements ){

	  for ( int i = 0 ; i < elements.length; i++ ){
	   // faz algo com o elemento
	  }
	}

}

 // uso

 Vector v;
 v.setElements ( 4 , 8 , 15 , 16 , 23 , 42 );

Categorias de Polimorfismo

Alguns dos tipos de polimorfismo escondem o real funcionamento do programa por serem forma de encapsulamento. Para alguns tipos de polimorfismo a a única forma de saber exatamente o que está acontecendo é analisar o programa enquanto está funcionando.

Os outros tipos cujo efeito no programa é claro mesmo quando o programa não está funcionando, ou seja, pode ser compreendido diretamente da análise do código compõem a categoria designada: Polimorfismo Estático.

Muitas das funcionalidades do polimorfismo estático não dependem do conceito de objeto e podem ser encontrados em outras linguagens, mesmo nas não orientadas a objetos. Talvez por isso não seja comum encontrar referência a essas capacidades como tipos de polimorfismo e são normalmente apresentadas como funcionalidades da linguagem. Em contrapartida, as funcionalidades de polimorfismo dinâmico dependem, quase todas, dos conceitos de objeto e herança (ou de alguma forma de hierarquia). Muitas vezes elas se confundem com os próprios conceitos de herança e orientação a objetos e são normalmente apresentadas como parte integrante desse paradigma.

Eis um sumário da características integrantes do polimorfismo dinâmico:

  • Variáveis Polimórficas

  • Tipos genéricos

Eis um sumário da caracteristicas integrantes do polimorfismo estático:

  • Sobrecarga

  • Sobrescrita

  • Sombreamento

  • Auto-(un)boxing

  • var args

Bibliografia

[1] Ralph Johnson, Erich Gamma, John Vlissides, Richard Helm Design Patterns: Elements of Reusable Object-Oriented Software: Addison-Wesley (2005)

[2] Steven John Metsker Design Patterns Java Workbook: Addison-Wesley Professional (2002)

Scroll to Top