[Pesquisar este blog]

segunda-feira, 12 de fevereiro de 2018

POO::Fundamentos-11-Autorreferência this

POO-F-10-Pacotes e Namespaces POO-F-12-Tratamento de Exceções
Todo objeto, em Java, C# ou C++, dispõe de uma referência para si próprio que é representada por meio da palavra reservada this, conhecida também como referência this ou autorreferência.

A autorreferência this tem duas aplicações bastante típicas: a primeira é possibilitar a diferenciação de campos da classe de variáveis locais ou parâmetros formais; a segunda é permitir que um método retorne uma referência para o próprio objeto[1][4][5][6][7][9].


Diferenciação de elementos

Quando um método de uma classe utiliza métodos ou campos não estáticos da própria classe, implicitamente está sendo usada a referência this [4][7]. Observe os métodos e os construtores da classe Horario que segue.

public class Horario {
// atributos int de acesso restrito (privado)
   private int hor, min, seg;

// métodos de acesso (leitura) dos campos restritos
   public int getHoras () { return hor; }
   public int getMinutos () { return min; }
   public int getSegundos () { return seg; }

// métodos de alteração (escrita) dos campos restritos
   public void setHoras (int hor) {
      if(hor<0 || hor>23) {
         throw new RuntimeException("Hora invalida: " + hor);
      }
      this.hor = hor;
   }
   public void setMinutos (int min) {
      if(min<0 || min>59) {
         throw new RuntimeException("Minutos invalido: " + min);
      }
      this.min = min;
   }
   public void setSegundos (int seg) {
      if(seg<0 || seg>59) {
         throw new RuntimeException("Segundos invalido: " + seg);
      }
      this.seg = seg;
   }
   public void setHorario (int hor, int min, int seg) {
      setHoras(hor); setMinutos(min); setSegundos(seg);
   }

// construtor parametrizado
   public Horario (int hor, int min, int seg) {
      setHorario(hor, min, seg);
   }
// construtor default
   public Horario () {
      this(0, 0, 0);
   }

// representação textual dos objetos Hora
   public String toString () {
      return String.format("%02d:%02d:%02d",
         hor, min, seg);
   }
}

Observe os métodos setHoras(int), setMinutos(int) e setSegundos(int): nos três o parâmetro formal declarado tem o mesmo nome do respectivo campo da classe. O uso explícito de this permite diferenciar variáveis locais dos campos (variáveis-membro) da classe com mesma denominação.

Quando um método declara uma variável local (ou um parâmetro) com o mesmo nome de um campo da classe, a variável local oculta o campo, ou seja, quando tal nome é usado, é acessada a variável local e não o campo da classe. Analise o fragmento de setHoras(int):
if(hor<0 || hor>23) {
   throw new RuntimeException("Hora invalida: " + hor);
}

A variável hor assim indicada faz referência ao parâmetro formal do método e não ao campo da classe. Ou seja, este código testa o valor recebido pelo método. Se o valor é adequado, é atribuído ao campo da classe, distinguido da variável local por this:
// campo da classe recebe valor da variável local
this.hor = hor;

Quando não existe conflito entre os nomes presentes no escopo de um método, os campos da classe são usados automaticamente [4][7]. Na verdade, nesta situação ocorre o uso implícito da autorreferencia this, como no método toString() da classe Horario.

A figura que segue ilustra o uso de this numa outra classe.

A referência this também permite distinguir construtores sobrecarregados da classe [7], como feito na classe Horario.

O construtor Horario(int, int, int) aciona o método público setHorário(int, int, int), que por sua vez aciona os métodos setHoras(int), setMinutos(int) e setSegundos(int), os quais validam os componentes do horário.

Já o construtor Horario() aciona o construtor Horario(int, int, int) por meio do uso de this, acompanhado dos argumentos desejados:
this(0, 0, 0);

Deve ser destacado que esse uso de this só é possível em construtores e quando feito na primeira linha de código do construtor. Ao mesmo tempo, é evitada a repetição de código em construtores, prática benéfica para facilitar a manutenção da classe.

Retorno da autorreferência

Outra aplicação da autorreferência this é como retorno de métodos da classe [7].

É comum que métodos retornem novos objetos, como por exemplo, o método substring (int ini, int fim) da classe java.lang.String, que retorna um novo objeto do tipo String contendo o fragmento delimitado pelas posições ini (incluída) e fim (não incluída) do texto da String. O método toUpperCase() retorna um novo objeto String apenas com maiúsculas. Por exemplo:
// Posição             6    11
//                     v    v
String texto1 = "Peter Jandl Junior";
// texto2 recebe "Jandl"
String texto2 = texto1.substring(6, 11);
// texto3 recebe JANDL
String texto3 = texto2.toUpperCase();

Quando um método retorna um objeto, é possível, no Java e C#, encadear a chamada de métodos ou concatenar suas chamadas, assim pode ser feito:
String texto1 = "Peter Jandl Junior";
String texto3 = texto1.substring(6, 11).toUpperCase();

Aqui é acionado o método substring(int, int) do objeto texto1 (do tipo String), e o método toUpperCase(), no resultado produzido por substring, que também é do tipo String. Cada chamada de método da classe String produz um resultado String (ou de outro tipo), de modo que o objeto original não é alterado, pois, por definição de projeto, os objetos da class String são imutáveis.

Mas existem situações onde os objetos são mutáveis, isto é, podem ser alterados, como os objetos do tipo Horário, dado anteriormente neste post. Os métodos setHoras(int), setMinutos(int) e setSegundos(int) têm retorno do tipo void, portanto não permitem qualquer espécie de encadeamento.

Considere agora a classe Somador abaixo.

public class Somador {
// campo privado com total do somador
   private double total;

// construtores parametrizado e default
   public Somador(double valorInicial) {
      setTotal(valorInicial);
   }
   public Somador() {
      this(0);
   }

// método de acesso
   public double getTotal() { return total; }

// métodos de alteração
   public Somador setTotal(double valor) {
      total = valor;
      return this;
   }
   public Somador add(double valor) {
      total = total + valor;
      return this;
   }

// método de produção   
   public Somador addToNew(double valor) {
      Somador novo = new Somador(total);
      novo.add(valor);
      return novo;
   }
}

A classe Somador tem como propósito representar um somador (ou um totalizador) de valores reais. Os construtores sobrecarregados permitem tanto criar um somador com um valor inicial predeterminado, como um somador default com valor inicial zero, sem repetição de código.
// somador com valor inicial = 15.4
Somador s1 = new Somador(15.4);
// somador com valor inicial = zero
Somador s2 = new Somador();

Como o campo total é privado e, portanto, inacessível, o método de acesso getTotal() permite consultar o valor total presente do objeto.
System.out.println("Somador s1 = " + s1.getTotal());
double aux = s2.getTotal();

Existem também dois métodos de alteração. O método add(double) permite adicionar o valor do argumento ao total do objeto, mas ao invés de retornar void (típico de métodos setter), retorna a referência do próprio objeto com this (por isso o tipo de retorno de add(double) é a própria classe Somador). Isto permite o encadeamento de métodos:
// com encademento
Somador s3 = new Somador();   // total = 0
s3.add(1.5).add(3.6).add(-1); // total = 4.1

// sem encademento
Somador s4 = new Somador(); // total = 0
s4.add(1.5); // total = 1.5
s4.add(3.6); // total = 5.1
s4.add(-1);  // total = 4.1

É fácil notar que o uso do encadeamento permite obter construções mais simples. Também deve ser notado que o retorno de this permite que um mesmo objeto seja afetado (alterado) por uma série de operações encadeadas.

Finalmente, a classe Somador possui o método addToNew(double) que é considerado de produção porque retorna um novo objeto, sem alterar o objeto inicial. No código deste método, observe a instanciação de um novo somador com o valor total do objeto existente, depois a adição do valor dado como argumento e o retorno da referência deste novo objeto.
Somador s5 = s4.addToNew(10.9);
// s5 contém 15.0
// s4 continua com 4.1

Esta estratégia permite criar operações que tanto retornam novos objetos, como mantém inalterados os objetos iniciais, além de possibilitar o encadeamento de operações.

Considerações finais

O uso da autorreferência this é bastante conveniente, pois além de permitir a diferenciação de campos da classe de variáveis locais, possibilita que a sobrecarga de construtores, evitando a repetição de código.

Outro aspecto do uso de this é permitir o retorno da referência do próprio objeto, possibilitando o encadeamento de operações sobre o mesmo objeto.

POO-F-10-Pacotes e Namespaces POO-F-12-Tratamento 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.

Nenhum comentário: