O Java oferece uma implementação padrão para o método toString através da classe java.lang.Object que é herdada por todas as classes Java. No entanto, o que ela retorna não é muito intuitivo. O seu retorno é composto pelo nome da classe seguido por um arroba (@) e pela representação do código de hash em hexadecimal sem sinal como, por exemplo, Cep@163b91, onde Cep seria o nome da classe seguida pelo arroba e pelo código hash. Segue abaixo o padrão seguido por toString da classe Object:

public String toString() {  
	return getClass().getName() + "@" 
              + Integer.toHexString(hashCode());  
}

O método toString deve retornar uma representação concisa, mas informativa, que seja fácil de uma pessoa ler. Temos que concordar que Cep@163b91 não é muito informativo, no entanto, se tivéssemos como retorno 92110300 seria muito mais informativo. Outro detalhe importante é que o contrato de toString recomenda que as subclasses devem ser sobrepostas.

Fornecer uma boa implementação de toString tornará a classe mais agradável de usar. O método toString sempre é chamado de forma automática quando um objeto é passado para println, printf, para o operador de concatenação de Strings ou para assert, ou exibido por um depurador.

Benefícios de Sobrepor toString

Os benefícios do fornecimento de um bom método toString que obedece bem ao seu contrato vão além das instancias da classe e se estendem a objetos contendo referências a essas instâncias, principalmente coleções. Por exemplo, o que é melhor de ver ao exibir um mapeamento: “Jenny=Cep@163b91” ou “Jenny=92110300”?

Para ser considerado como um método prático, o método toString deve retornar todas as informações interessantes contidas no objeto, como no exemplo acima do número do CEP. Quando temos um objeto grande demais o ideal é criar um resumo como “O funcionário Higor mora no CEP 92110300, Rua Garibalde Correa no Estado do Rio Grande do Sul em Porto Alegre”. O ideal é que a String sempre seja auto-explicativa.

Como um exemplo introdutório ao uso do método toString criaremos uma classe Endereco conforme definida abaixo:

Listagem 1: Exemplo da classe Endereco sem sobrepor o método toString.

public class Endereco {
	
	private Integer cep;
	private String cidade;
	private String estado;
	private String rua;
	private Integer numero;
	
	public Integer getCep() {
		return cep;
	}
	public void setCep(Integer cep) {
		this.cep = cep;
	}
	public String getCidade() {
		return cidade;
	}
	public void setCidade(String cidade) {
		this.cidade = cidade;
	}
	public String getEstado() {
		return estado;
	}
	public void setEstado(String estado) {
		this.estado = estado;
	}
	public String getRua() {
		return rua;
	}
	public void setRua(String rua) {
		this.rua = rua;
	}
	public Integer getNumero() {
		return numero;
	}
	public void setNumero(Integer numero) {
		this.numero = numero;
	}
	
	
}

Agora vamos criar outra classe ExibeDados que é responsável por exibir os dados do endereço do usuário definido anteriormente.

Listagem 2: Exemplo de exibição da classe Endereco.

public class ExibeDados {

	public static void main(String args []) {
		Endereco endereco = new Endereco();
		endereco.setCEP(92110300);
		endereco.setCidade("Porto Alegre");
		endereco.setEstado("Rio Grande do Sul");
		endereco.setNumero(700);
		endereco.setRua("Chacara Barreto");
		
		System.out.println(endereco);
	}
	
}

A saída da execução da classe acima é o texto abaixo:
Endereco@addbf1

Podemos notar que ele não informa absolutamente nada para quem esta acessando o método toString dessa classe. Agora vamos imaginar o cenário abaixo onde temos uma coleção de objetos Endereço.

Listagem 3: Exemplo de chamada do método toString da classe Endereco numa coleção.

import java.util.ArrayList;
import java.util.List;


public class ExibeDados {

	public static void main(String args []) {
		List<Endereco> enderecos = new ArrayList<Endereco>();
		
		//Adiciona Primeiro Endereço
		Endereco endereco = new Endereco();
		endereco.setCep(92110300);
		endereco.setCidade("Porto Alegre");
		endereco.setEstado("Rio Grande do Sul");
		endereco.setNumero(700);
		endereco.setRua("Chacara Barreto");
		
		enderecos.add(endereco);
		
		//Adiciona Segundo Endereço
		endereco = new Endereco();
		endereco.setCep(92987970);
		endereco.setCidade("Porto Alegre");
		endereco.setEstado("Rio Grande do Sul");
		endereco.setNumero(1389);
		endereco.setRua("Borges de Medeiros");

		enderecos.add(endereco);

		//Adiciona Terceiro Endereço
		endereco = new Endereco();
		endereco.setCep(85456000);
		endereco.setCidade("Porto Alegre");
		endereco.setEstado("Rio Grande do Sul");
		endereco.setNumero(1389);
		endereco.setRua("Avenida Mauá");

		enderecos.add(endereco);
		
		
		System.out.println(enderecos);
	}
	
}

Temos como resultado da impressão dessa lista o texto abaixo:
[Endereco@addbf1, Endereco@42e816, Endereco@9304b1]

No entanto, agora vamos sobrepor o método toString para melhorar o entendimento da nossa classe. Altere a classe Endereco para ficar conforme está especificado abaixo:

Listagem 4: Exemplo da classe Endereco sobrepondo o método toString.

public class Endereco {
	
	private Integer cep;
	private String cidade;
	private String estado;
	private String rua;
	private Integer numero;
	
	public Integer getCep() {
		return cep;
	}
	public void setCep(Integer cep) {
		this.cep = cep;
	}
	public String getCidade() {
		return cidade;
	}
	public void setCidade(String cidade) {
		this.cidade = cidade;
	}
	public String getEstado() {
		return estado;
	}
	public void setEstado(String estado) {
		this.estado = estado;
	}
	public String getRua() {
		return rua;
	}
	public void setRua(String rua) {
		this.rua = rua;
	}
	public Integer getNumero() {
		return numero;
	}
	public void setNumero(Integer numero) {
		this.numero = numero;
	}
	
	@Override
	public String toString() {
		return "Rua: " + this.rua + ", Número: " + this.numero +
			   ", Cidade: " + this.cidade + ", Estado: " + this.estado +
			   ", CEP: " + this.cep;
	}
	
}

Agora podemos executar novamente a listagem anterior e temos como saída o resultado abaixo:
[Rua: Chacara Barreto, Número: 700, Cidade: Porto Alegre, Estado: Rio Grande do Sul, CEP: 92110300, Rua: Borges de Medeiros, Número: 1389, Cidade: Porto Alegre, Estado: Rio Grande do Sul, CEP: 92987970, Rua: Avenida Mauá, Número: 1389, Cidade: Porto Alegre, Estado: Rio Grande do Sul, CEP: 85456000]

Temos um resultado muito mais claro e informativo do que o resultado anterior, sem precisar nem mesmo iterar por toda lista para saber o que há dentro dela, caso queiramos uma listagem mais simples.

Como uma observação note que também poderíamos usar um StringBuffer para concatenar as nossas Strings, porém o retorno deverá ser stringbuffer.toString(). O exemplo abaixo demonstra o uso do StringBuffer:

Listagem 5: Exemplo da classe Endereco sobrepondo o método toString utilizando StringBuffer.

public class Endereco {
	
	private Integer cep;
	private String cidade;
	private String estado;
	private String rua;
	private Integer numero;
	
	public Integer getCep() {
		return cep;
	}
	public void setCep(Integer cep) {
		this.cep = cep;
	}
	public String getCidade() {
		return cidade;
	}
	public void setCidade(String cidade) {
		this.cidade = cidade;
	}
	public String getEstado() {
		return estado;
	}
	public void setEstado(String estado) {
		this.estado = estado;
	}
	public String getRua() {
		return rua;
	}
	public void setRua(String rua) {
		this.rua = rua;
	}
	public Integer getNumero() {
		return numero;
	}
	public void setNumero(Integer numero) {
		this.numero = numero;
	}
	
	@Override
	public String toString() {
		StringBuffer stringBuffer = new StringBuffer();
		stringBuffer.append("Rua: ");
		stringBuffer.append(this.rua);
		stringBuffer.append(", Número: ");
		stringBuffer.append(this.numero);
		stringBuffer.append(", Cidade: ");
		stringBuffer.append(this.cidade);
		stringBuffer.append(", Estado: ");
		stringBuffer.append(this.estado);
		stringBuffer.append(", CEP: ");
		stringBuffer.append(this.cep);
		
		return stringBuffer.toString();
	}
	
}

Outras informações

Outra situação importante é que devemos sempre fornecer acesso programático a todas as informações contidas no valor retornado por toString. Por exemplo, imaginamos uma classe Telefone, nesse caso a classe deve conter acessadores (getters) para o código da área, o prefixo e o número da linha. Se isso não for feito pelo programador estaríamos forçando outros programadores que precisarem dessas informações a analisar a string.

Entre os malefícios que isso teria tem-se uma piora no desempenho e a geração de trabalho desnecessário para os programadores, além de propiciar erros e sistemas frágeis que perderão a validade se o formato for posteriormente alterado. Ao não oferecermos acessadores, estaríamos transformando o formato de string em uma API, mesmo se tiver especificado que ele está sujeito a alterações.

Conclusão

Neste artigo estudamos o que é o método toString, como podemos sobrepor este método, quais as suas vantagens em defini-lo e quais são as melhores práticas para sobrepor o método. Além disso, estudamos alguns exemplos que demonstram o uso do método toString e como podemos se beneficiar ao sobrepor este método.

Bibliografia

  1. Java Efetivo 2º edição. Joshua Bloch. Alta Books, 2010.
  2. Representação textual de objetos
  3. Java toString Method
  4. Implementing toString