Introdução: Sobrecarga de Métodos e Tipos Genéricos em Java

Você precisa estar logado para dar um feedback. Clique aqui para efetuar o login
Para efetuar o download você precisa estar logado. Clique aqui para efetuar o login
Confirmar voto
0
 (6)  (0)

Veja nesse artigo como e quando utilizar sobrecarga de métodos e tipos genéricos e quais problemas cada um ajuda a resolver.

Introdução

O java nos permite trabalhar tanto com métodos sobrecarregados como com tipos genéricos, vamos ver qual a utilidade de ambos sempre mostrando primeiro o problema para depois apontar a solução. Será mostrado a utilização dos métodos sobrecarregados e dos tipos genéricos na resolução do seu respectivo problema.

Vamos ao problema:
Ao desenvolver uma aplicação surge a necessidade de se criar um método que receba por parâmetros o nome do cliente, endereco, bairro, cidade e estado. Então foi criado o método como abaixo:

public void setDadosPessoais(String nome, String endereco, 
String bairro, String cidade, String estado)
	{
		// codigo do método	
	}


Ao longo do projeto é percebido que nem sempre será necessário a inclusão do endereco, do bairro, da cidade e do estado. Então é solicitado ao programador que fosse feito um método que recebesse apenas o nome e o sobrenome do cliente. Então foi criado o método:

public void setDadosPessoaisNome(String nome)
	{
		// codigo do método
	}


Agora o sistema está aceitando a inserção de todos os dados pessoais, mas também aceita apenas o nome e o sobreNome do cliente. Pouco depois surgiu a necessidade de um método que recebesse apenas o nome, o endereço e o bairro. Então foi criado o método:

public void setDadosPessoaisNomeEnderecoBairro(String nome,String endereco,String bairro)
	{
		// codigo do método
	}


A medida que as solicitações aconteciam os métodos iriam crescendo e além de crescendo os nomes estavam ficando cada vez maiores. Para cada situação diferente um novo método e um nome diferente, como o exemplo abaixo:

•    nome - setDadosPessoaisNome
•    nome, endereco - setDadosPessoaisNomeEndereco
•    nome, endereco, bairro - setDadosPessoaisNomeEnderecoBairro
•    nome, bairro - setDadosPessoaisNomeBairro
•    nome, cidade - setDadosPessoaisNomeCidade
•    nome, estado - setDadosPessoaisNomeEstado
•    endereco, bairro - setDadosPessoaisEnderecoBairro
•    cidade, estado - setDadosPessoaisCidadeEstado
•    endereco - setDadosPessoaisEndereco
•    bairro - setDadosPessoaisBairro
•    bairro, cidade - setDadosPessoaisBairroCidade
•    cidade - setDadosPessoaisCidade

     Você pode observar que a medida que forem surgindo mais parâmetros para o método o nome tende a ficar cada vez mais longo e isso além de não ficar nada bonito ainda pode fazer gerar confusão , um monte de métodos com vários nomes diferentes um maior que o outro.
     Depois de criado todas as opções pedidas, apareceu um outro problema, foi solicitado que as variareis nome, endereco, bairro, cidade e estado fossem iniciadas no momento que a classe fosse instanciada e que o sistema teria que dar as mesmas opções que os métodos deram.
    E agora? Como fazer isso se o construtor deve ter o mesmo nome da classe?
    Veremos como resolver esse problema utilizando métodos   sobrecarregados.

Sobrecarregamento de Métodos

    Vimos que a medida que as solicitações iam ocorrendo, o nosso problema só aumentaria. Vamos ver a solução para a infinidade de nomes diferentes que precisamos criar.
            O java permite que você crie métodos com o mesmo nome, desde que eles tenham parâmetros diferentes. O sobrecarregamento de métodos é muito utilizado pelos construtores que precisam ter o mesmo nome da classe. No nosso exemplo estamos cadastrando dados para o cliente, então vamos utilizar o nome Cliente para a classe, conforme foi solicitado precisamos iniciar as variareis no momento que a classe for instanciada. Vamos ver um exemplo de definição de classe e depois instanciar a mesma para entender melhor o que acontece quando uma classe é instanciada.

public class Clientes
	{
		private String nome;
		private String endereco;
		private String bairro;
		private String cidade;
		private String estado;

		public void setNome(String nome)
			{
				this.nome = nome;
			}
		public String getNome()
			{
				return nome;
			}

		public void setEndereco(String endereco)
			{
				this.endereco = endereco;
			}
		
		public String getEndereco()
			{
				return endereco;
			}
		
		public void setBairro(String bairro)
			{
				this.bairro = bairro;
			}
		
		public String getBairro()
			{
				return bairro;
			}

		public void setCidade(String cidade)
			{
				this.cidade = cidade;
			}

		public String getCidade()
			{
				return cidade;
			}
		
		public void setEstado(String estado)
			{
				this.estado = estado;
			}

		public String getEstado()
			{
				return estado;
			}
	}


A classe cliente declara cinco variáveis do tipo String, nenhum construtor foi utilizado explicitamente, e nenhuma das variareis foi iniciada. Quando uma variável do tipo String é declarada e não é iniciada explicitamente, ela recebe o valor de null. Como estamos trabalhando com a classe String isso não é um problema, mas em alguns casos a falta de inicialização de variáveis gera uma exception. Vamos instanciar a classe clientes e imprimir o valor das variareis para entendermos melhor:

public class ClientesTest 
	{
		public static void main(String[] args) 
			{
				Clientes clientes = new Clientes();
				System.out.println(clientes.getNome());
			}
	}


Ao chamar o método getNome() da classe Clientes é impresso o valor null. Vamos ver o que acontece quando chamamos a classe StringBuilder sem inicializá-la:

public class ClientesTest 
	{
		public static void main(String[] args) 
			{
				Clientes clientes = new Clientes();
				System.out.println(clientes.getNome());
				/* Obtém uma referencia do StringBuilder e adiciona o string “Teste”*/
				clientes.getBuilder().append(“teste”); 
			}
	}


 Quando a classe ClientesTest adiciona o string “Teste” através do método append() é gerada a seguinte exceção:

Exception in thread "main" java.lang.NullPointerException at 
br.com.MetodosSobrecarregados.Problema.ClientesTest.main(ClientesTest.java:11)


        A exceção ocorre porque a variável que passa a referencia do StringBuilder para o método getBuilder() não foi inicializada.
        Para evitar esse tipo de problema utilizamos os construtores que são chamados no momento em que a classe é instanciada. Com os métodos sobrecarregados podemos definir as variareis que precisam ser inicializadas no momento em que a classe é instanciada. Vamos criar um construtor que inicie apenas a variável array da classe Clientes:

public Clientes()
	{
		/*variável é inicializada no momento em que a classe Clientes for 		instanciada.*/
		clientesBuilder = new StringBuilder(); 
	}



    Ao executarmos o sistema não será mais mostrada a exceção. Apenas será impresso o valor null, devido ao nome não ter sido inicializado. Até o momento não vimos ainda o sobrecarregamento de métodos, vamos ver agora oferecendo para a classe ClientesTest a opção de inicializar a variável nome juntamente com a variável array:

public Clientes() //1ª opção do construtor para a classe Clientes
	{
		clientesBuilder = new StringBuilder();
	}

public Clientes(String nome) //2ª opção do construtor para a classe Clientes
	{
		clientesBuilder = new StringBuilder();
		setNome(nome);
	}


    No exemplo acima temos o sobrecarregamento de método no construtor Clientes, o construtor pode ser chamado sem parâmetros ou com o parâmetro nome. Vejamos como fica a classe ClientesTest utilizando o construtor com o parâmetro nome:

public class ClientesTest 
	{
		public static void main(String[] args) 
			{
				//O nome Felipe é passado como parametro
				Clientes clientes = new Clientes("Felipe");

				System.out.println(clientes.getNome());
			}
	}


    Ao executar a classe ClientesTest, é impresso o nome Felipe ao invés de null, ou seja, a variável nome foi inicializada corretamente. O nome do construtor é igual ao nome da classe, podemos fazer a mesma coisa para os métodos, evitando uma infinidade de nomes diferente para um mesmo método, o que precisa ser diferente são apenas os parâmetros passados. Vejamos um exemplo de sobrecarregamento para o método setDadosPessoais. Será oferecido 4 opções para o método:
    
•    Atribuir valor ao nome do cliente.
•    Atribuir valor ao nome o cliente e ao endereço do cliente.
•    Atribuir valor ao nome, endereco e cidade do cliente.
•    Atribuir valor ao nome, endereco, cidade e estado do cliente.

           Vamos ao código:

//Opção com parametro nome
public void setDadosPessoais(String nome)
	{
		setNome(nome);
	}


//Opção com parametro nome e endereco
public void setDadosPessoais(String nome,String endereco)
	{
		setNome(nome);
		setEndereco(endereco);
	}

//Opção com parametro nome, endereco e cidade
public void setDadosPessoais(String nome,String endereco,String cidade)
	{
		setNome(nome);
		setEndereco(endereco);
		setCidade(cidade);
	}

//Opção com parametro nome, endereco,cidade e estado
public void setDadosPessoais(String nome,String endereco,String cidade,String estado)
	{
		setNome(nome);
		setEndereco(endereco);
		setCidade(cidade);
		setEstado(estado);
	}


    Os problemas citados anteriormente foram resolvidos utilizando os métodos sobrecarregados. Os códigos mostrados nesse artigo estão disponíveis para download. Existe um download para cada passo explicado aqui.
            Embora os métodos sobrecarregados ajudem bastante ainda teremos muitos métodos, se quisermos oferecer soluções para todas as possíveis inicializações, só para a inicialização de uma variável teríamos 4 métodos e o número só iria crescendo na medida que iriam crescendo as opções.
    
Entendendo os Tipos Genéricos

    Em java podemos utilizar um método utilitário para executar uma tarefa em comum independente da variável passada como parâmetro, imagine uma situação em que o método precise passar um parâmetro e imprimir na tela uma resposta de acordo com o parâmetro utilizado. Poderíamos utilizar os métodos sobrecarregados para impedir a criação de nomes distintos como abaixo:

1º problema:

public static void configuraValor(String valor)
	{
		System.out.println(“String impressa com êxito.”);
	}

public static void configuraValor(int valor)
	{
		System.out.println(34);
	}

public static void configuraValor(double valor)
	{
		System.out.println(32.23);
	}

    
    Podemos observar que os nomes dos métodos foram reduzidos a um só, o que mudam são os parâmetros passados, os métodos compartilham entre-si o método System.out.println() que imprime o tipo passado como parâmetro. Como utilizamos os métodos sobrecarregados, teremos três métodos, uma para o tipo String, outro para o tipo int e outro para o tipo double. O problema é que não é aproveitado o que é comum entre os métodos, a medida que forem surgindo mais tipos terão que ser feitos novos métodos sobrecarregados.  Vamos ver primeiro um outro exemplo para depois a solução para os dois problemas.

2º problema:

    Imagine uma classe chamada Operacao que instancie 5 classes: Clientes, Funcionarios, Fornecedores, Voluntarios e Patrocinadores. Todas as classes possuem um método chamado getInformacao que retorna os dados pessoais das mesmas.

new Clientes().getInformacao();
new Funcionarios().getInformacao();
new Fornecedores().getInformacao();
new Voluntarios().getInformacao();
new Patrocinadores().getInformacao();


    Cada classe vai retornar um número especifico de informações, podemos perceber que todas as classes tem um método getInformacao(), a classe Operacao instancia cada classe para receber essas informações, tratar essas informações e imprimir para o usuário final. Para as 5 classes precisariamos ter 5 métodos sobrecarregados, visto que a classe Operação vai receber as informações tratar os dados e imprimir o resultado. Vejamos um exemplo:

public getInformacao(Clientes clientes)
	{
		String informacao = clientes.getInformacao();

		if (informacao != null)
			{
				System.out.println(informacao);
			}
			
	}

public getInformacao(Funcionarios funcionarios)
	{
		String informacao = clientes.getInformacao();

		if (informacao != null)
			{
				System.out.println(informacao);
			}
	}


    Os outros métodos sobrecarregados encontram-se no exemplo disponível para download, existem 5 métodos um para cada classe. Temos alguns pontos a serem obervados nesse exemplo.

•   Os códigos se repetem nos métodos sobrecarregados.
•   Foi preciso criar métodos para o mesmo número de classes que é preciso trabalhar, se for preciso instanciar 5 classes, são necessários 5 métodos, se for preciso 50 classes são necessários 50 métodos.
•    A medida que forem crescendo o número de métodos, o sistema fica mais suscetível a erros, imagine que você tenha 50 classes e você precise alterar seu código no método getInformacao da classe Operacao, você precisaria fazer 50 alterações, uma em cada método sobrecarregado.
    
    Podemos resolver esses problemas utilizando os tipos genéricos. Veremos a solução para os dois problemas mostrados aqui. Vamos a resolução do primeiro problema.

Solução para o 1º problema:

  public static  void configuraValorG(T valor)
	{	
		if (valor instanceof String)
			{
				valor = (T)"É uma string";
			}
		else if (valor instanceof Integer)
			{
  				valor = (T)(Integer)20;
			}
		else if (valor instanceof Double)
			{
				valor = (T)(Double)5.90;
			}
				
		System.out.println(valor);
	}

 
    Inicialmente tínhamos três métodos sobrecarregados, um para cada tipo de dados. Resolvemos esse problema transformando os três métodos em um só. Vejamos a explicação do novo método genérico.

Linha 1: Para utilizar os tipos sobre carregados preciso passar uma variável entre o sinal de maior e menor antes do tipo de retorno, foi utilizada a variável T, que entre os sinais ficou assim: <T>. Para o parâmetro valor foi passado como tipo a letra  T, mesma letra passada como variável entre os sinais de maior e menor. Se você utilizar a letra E como variável o cabeçalho do método ficaria assim:

public static  void configuraValor(E valor).


Linha 3: Foi utilizado o operador instanceof para saber se o tipo passado é do tipo String.

Linha 5: Foi atribuído a variável valor uma String qualquer, no exemplo foi passado “É uma string”. Podemos observar que é necessário fazer um Cast para T, isso é necessário para que o compilador não gere erros, já que os tipos genéricos são passados em tempo de execução.

Linha 7:  Da mesma forma da linha 3, foi utilizado o operador instanceof , porém não foi utilizado o tipo int, devido aos tipos genéricos só suportarem classes não é permito passar variáveis primitivas (int, double, boolean, float, …), para a variável int foi utilizada a classe Integer.

Linha 9: Além do Cast para a variável T, ainda é feito um Cast para a classe Integer, devido a possibilidade de receber como parâmetro um int ao invés de um Integer. Caso eu não queira tratar isso no método, eu tenho que ter certeza que será passado como parâmetro uma variável do tipo Integer.

Linha 16: É passado ao método System.out.println() a variável valor, onde seu tipo é definido em tempo real.

    Com os tipos genéricos reduzimos os métodos sobrecarregados a um método só, dessa forma podemos compartilhar o método System.out.println(). Qualquer alteração nesse método, como a impressão da variável valor antes do mesmo será feita uma vez só, ao contrário dos métodos sobrecarregados que teriam que ser feitos em cada método específico, podendo gerar erros entre os métodos.
    Só para entendermos melhor, vamos ao mesmo método utilizando como variável de tipo a letra E ao invés de T :
public static  void configuraValor(E valor)
	{	
		if (valor instanceof String)
			{
				valor = (E)"É uma string";
			}
		else if (valor instanceof Integer)
			{
				valor = (E)(Integer)20;
			}
		else if (valor instanceof Double)
			{
				valor = (E)(Double)5.90;
			}
				
		System.out.println(valor);
	}


    Como foi dito anteriormente, caso deseje alterar a letra entre os sinais de maior e menor, não esqueça de alterar no corpo do método também.

Solução para o 2º problema:

  public  void  getInformacao(T valores)
	{	
		if (((Informacoes) valores).getInformacao() != null)
			{							  			   	               
				System.out.println(((Informacoes)valores).getInformacao());
			}
	}


    O que muda nessa resolução é o Cast utilizando a Interface Informacoes para obter o método comum entre todas as classes. Na linha 3 utilizamos a variável genérica e verificamos se existe algum valor para a classe. O parâmetro passado é uma classe instanciada. Independente se seja 1, 5 ou 50 classes o método continua sendo 1 e, qualquer alteração no método genérico é valido  em todas as classes. O código completo está disponível para download.

Explicação sobre os arquivos para download

    Os códigos explicados neste arquivos encontram-se disponíveis para download, os mesmos foram feitos no eclipse, para utilizá-los basta importar o projeto Artigos.

            Existem 4 pacotes:
•    br.com.metodossobrecarregados.problema: referente a não utilização dos métodos sobrecarregados
•    br.com.metodossobrecarregados.solucao: referente a resolução do problema utilizando os métodos sobrecarregados
•    br.com.tiposgenericos.primeiroproblema: referente ao problema e a solução mostrada no artigo para o primeiro problema
•    br.com.tiposgenericos.segundoproblema: referente ao problema e a solução mostrada no artigo para o segundo problema

    Os códigos podem ser lidos diretamente (fora do eclipse) através de qualquer editor de textos, acesse a pasta Artigos > src > br > com e escolha o pacote:
•    1º pasta: métodos sobrecarregados → contém os códigos para o pacote de métodos sobrecarregados, contem duas subpastas:
•    problema: contém os códigos com a não utilização dos métodos sobrecarregados
•    solução: contém a solução utilizando os métodos sobrecarregados
•    2º pasta: tipos genéricos → contém os códigos para o pacote de tipos genéricos, contem duas subpastas:
•    primeiro problema: contém os códigos para o problema e a solução do primeiro problema do artigo
•    segundo problema: contém os códigos para o problema e a solução do segundo problema do artigo

Testando as aplicações

    Para testar os pacotes, é preciso executar as classes principais (main), segue a lista das classes executoras para cada pacote:
•    br.com.metodossobrecarregados.problema: ClientesTest.java
•    br.com.metodossobrecarregados.solucao: ClientesTest.java
•    br.com.tiposgenericos.primeiroproblema: ExecutaValores.java
•    br.com.tiposgenericos.segundoproblema: Duas classes principais, uma para o problema e outra para a solução. Seguem:
•    Problema: ExecutaOperacaoMetodosSobrecarregados.java
•    Solução: ExecutaOperacaoTiposGenericos.java

    Aqui finalizamos o artigo sobre sobrecarregamento de métodos e tipos genéricos. Até a próxima.
    

 
Você precisa estar logado para dar um feedback. Clique aqui para efetuar o login
Receba nossas novidades
Ficou com alguma dúvida?