[Pesquisar este blog]

domingo, 11 de março de 2018

POO::Plena-19-Membros Estáticos

POO-P-18-Especificadores e Modificadores POO-P-20-Herança Simples
Os membros estáticos são aqueles declarados com o modificador static, de maneira que tanto atributos/campos como métodos/operações possam ser definidas desta forma. Desta maneira é preciso uma conceituação mais precisa das implicações de seu uso para possibilita seu emprego adequado.

Como visto, as linguagens Java e C# possuem vários especificadores e modificadores (como no post anterior Especificadores e Modificadores), dentre os quais existe um denominado static. O uso deste modificador altera o comportamento dos membros assim declarados, tornando membros de instância em membros de classe, como veremos a seguir.

Membros de instância

Uma classe pode declarar vários membros:
  • Variáveis-membro (campos ou atributos) e
  • Funções-membro (métodos ou operações).
Estes membros, naturalmente, só podem ser utilizados pelas instâncias da classes, ou seja:
  • Cada objeto instanciado tem cópias exclusivas das variáveis-membro para seu uso individual; e
  • As funções-membro, acionadas pelos objetos instanciados, utilizam apenas as variáveis-membro de suas respectivas instâncias.
Por isso, tais membros são denominados de membros de instância.

Consideremos a classe Point dada a seguir.

public class Point {
    public double x;
    public double y;

    public void reflectX() { y = - y; }
    public void reflectY() { x = - x; }
}

Objetos do tipo Point podem ser instanciados como no fragmento abaixo, que usa o construtor default suprido pelo compilador:

Point p1 = new Point();
Point p2 = new Point();

Depois de criados, os objetos podem ser acessados por meio de suas referências, no caso p1 e p2, o que permite acessar as variáveis-membro x e y que são públicas.

p1.x =  5.5;
p1.y = 3.0;

p2.x = -1.5;
p2.y = 6.7;

Cada instância armazena seus próprios valores, retendo, portanto, apenas o seu próprio estado e, assim, o uso de métodos só afeta a instância onde são acionados.

System.out.println("P1: " + p1.x + ","+ p1.y);
System.out.println("P2: " + p2.x + ","+ p2.y);

p1.reflectY();
p2.reflectX();

System.out.println("P1: " + p1.x + ","+ p1.y);
System.out.println("P2: " + p2.x + ","+ p2.y);

Com isso fica claro que operações executadas numa instância não afetam quaisquer outras instâncias (do mesmo tipo ou de outros).

Membros de classe

Quando um membro de uma classe (variável ou método) é declarado como static, ele é modificado de maneira que deixa de pertencer às instâncias da classe, passando a pertencer a própria classe.

Assim uma variável-membro ou campo declarado como static torna-se uma variável de classe, um membro de classe ou um campo estático. Da mesma maneira, um método de instância ou operação torna-se um método de classe ou método estático.

O uso do modificador static não afeta os especificadores de acesso, de modo que static pode ser combinado com public, protected ou private, conforme desejado pelo programador.

Por outro lado, os construtores não podem, em qualquer circunstância, ser declarados como estáticos.

Analise a classe Cruise dada a seguir. Observe a existência de um campo estático denominado MAX e de um método estático chamado maxRate(double).

public class Cruise {
    public static double MAX;
    public double velocity;

    public boolean overMax () {
        return velocity > MAX;
    }

    public static double maxRate(double v) {
        return v/MAX;
    }
}

Objetos do tipo Cruise podem ser instanciados normalmente, com uso do construtor default.

Cruise c1 = new Cruise();
Cruise c2 = new Cruise();

O uso dos membros não estáticos não é afetado pela presença dos elementos estáticos, ou seja, é possível fazer:

c1.velocity =  81.5;
c2.velocity = 105.7;

if (c1.overMax()) System.out.println(c1.velocity);
if (c2.overMax()) System.out.println(c2.velocity);
// se executado este trecho exibe as velocidades 81.5 e 105.7.

O campo estático MAX é um membro de classe, portanto comum a todas as instâncias. Seu uso deve ser feito por meio do nome da classe, como segue:

Cruise.MAX = 100;

O novo valor de MAX é conhecido por todas as instâncias da classe, que tem acesso ao seu novo conteúdo. Assim, se executado

if (c1.overMax()) System.out.println(c1.velocity);
if (c2.overMax()) System.out.println(c2.velocity);
// agora este trecho exibe apenas a velocidade 105.7!

O método overMax(), não estático, emprega internamente a variável de instância velocity, a qual é comparada a variável de classe MAX. Na primeira execução, velocity do objeto c1 tem valor 81.5, velocity do objeto c2 tem valor 105.7, enquanto MAX, não inicializada, tem valor 0; produzindo os resultados 81.5 e 105.7, ambos valores maiores que MAX.

Alterando o valor da variável de classe MAX para 100 e executando novamente o método overMax() para as duas instâncias, temos que velocity do objeto c1 ainda tem valor 81.5, velocity do objeto c2 ainda tem valor 105.7, mas MAX, com novo valor 100; produz o resultado 105.7, única velocidade maior que MAX.

O uso de campos e métodos estáticos, conforme a recomendação da plataforma, deve usar o nome qualificado da classe, tal como:

Cruise.MAX = 100;
Cruise.maxRate(120);

Apesar disso, o uso de elementos estáticos por meio de instâncias, como segue, produz, quando muito, avisos por parte do compilador, pois o que define o acesso é, de fato, o especificador de acesso empregado.

c1.MAX = 120;
c2.maxRate(40);

O modificador static altera o comportamento do membro, que deixa de pertencer a instância, passando para o domínio da classe, acarretando em um conjunto de implicações que determinam a forma com que pode ser explorado.

Usos comuns de membros static

O uso do modificador static possibilita usos como:
  • Compartilhamento de informação entre instâncias de uma mesma classe, diretamente por meio de variáveis estáticas; ou indiretamente por meio de métodos estáticos que podem oferecer operações baseadas em valores comuns existentes entre as instâncias de uma classe.
  • Criação simplificada de biblioteca de funções, quando não é necessária a retenção de estado de objetos (feito por variáveis de instância)
  • Definição de constantes, que podem preservar de maneira imutável valores (e até mesmo referências de instâncias) considerados importantes em um programa.

Compartilhamento de informação

Os membros estáticos pertencem à classe, ou seja, eles são únicos e não dependem da existência de instâncias da classe (i.e., objetos) para serem acessados, apenas respeitando os especificadores de acesso combinados, sem quaisquer restrições adicionais.

Externamente, o acesso dos membros estáticos passa a ser qualificado pela própria classe, conforme sua visibilidade. Internamente o acesso dos membros estáticos é como qualquer outro.

Assim, se existe a classe Estatica como dado a seguir:
public class Estatica {
    public static int sval = 10;
    private int ival = 0;

    public static void reset() {
       sval = 0;
    }

    public int getIVal() { return ival; }
    public void setIVal(int i) { ival = i; }
}

É possível usar seus membros estáticos desta maneira:

// Uso de campo:
Estatica.sval = 33;
// Uso de método:
Estatica.reset();

Mas o uso dos membros não estáticos requer instâncias válidas:

Estatica e1 = new Estatica();
e1.setIVal(10);
System.out.println(e1.getIVal());

Assim todas as instâncias tem acesso aos membros estáticos únicos associados às suas próprias classes. Desta maneira, torna-se possível compartilhar valores entre instâncias por meio de variáveis estáticas, pois é como se fossem variáveis "globais" de um tipo específico.

A classe Estatica2 utiliza um membro estático denominado N que mantém o número de objetos da própria classe, incrementando-o na instanciação e decrementando-o na destruição de objetos.

public class Estatica2 {
    private static int N = 0;

    public Estatica2 () {
        N++;
    }
    protected void finalize() {
        N--;
    }
    public static int instances() {
        return N;
    }
}

Esta classe pode ser usada como segue:

// Exibe 0
System.out.println(Estatica2.instances());

Estatica2 obj1 = new Estatica2();
// Exibe 1
System.out.println(Estatica2.instances());

Estatica2 obj2 = new Estatica2();
// Exibe 2
System.out.println(Estatica2.instances());

obj1 = null; // GC pode atuar!
// Exibe 1 ou 2, conforme atuação do GC
System.out.println(Estatica2.instances());

Biblioteca de funções

Como os métodos estáticos não utilizam campos mutáveis de suas classes, podem oferecer operações independentes, cujo resultado se baseia, estritamente, nos argumentos recebidos.

Um conjunto de métodos estáticos relacionados, ou seja, cujas operações pertençam a um mesmo domínio, podem ser agrupados em uma única classe, a qual se comporta como uma biblioteca de funções

Um exemplo desta aplicação, na própria API do Java é a classe java.lang.Math, que só contém métodos estáticos que oferecem funções matemáticas independentes.

A classe Conversão, que segue, também contém apenas métodos estáticos relacionados a conversão de temperaturas entre diferentes unidades.

public class Conversao {
// conversão C --> F
   public static double C2F (double c) {
      return 9*c/5 + 32;
   }
// conversão F --> C
   public static double F2C (double f) {
      return 5*(f-32)/9;
   }
}

O uso desta classe não requer instâncias, pois os seus métodos podem ser acionados diretamente:

double x = 100;
double y= Conversao.C2F(x);
System.out.println("C = " + x + " => F = " + y);

y = 32;
x = Conversao.F2C(y);
System.out.println("F = " + y + " => C = " + x);

System.out.println("C = 24" + " => F = " + Conversao.C2F(24));


Definição de constantes

A linguagem Java não contém um modificador próprio para definição de constantes (como const em linguagem C++). Mas tal efeito pode ser obtido pela combinação de dois modificadores na declaração de variáveis:
  • static, que indica que membro pertence à classe;
  • final, que indica que membro não pode ser alterado, nem mesmo em subclasses.
Exemplos de declaração de constantes são:
public static final double PI = 3.1415926;
public final static int MAX = 123;
static public final byte MASK = 0b01001001;

Observe que a inicialização é obrigatória na declaração de constantes. Além disso, a ordem com que os modificadores e especificadores são aplicados não altera o efeito produzido.

Outro aspecto importante, é que a convenção de código Java solicita que os nomes das constantes seja grafado exclusivamente com letras maiúsculas, eventualmente com  uso de _ (underline) para separação de palavras.

Restrições dos membros static

Campos e métodos estáticos só podem utilizar:
  • Constantes literais;
  • Outros elementos estáticos;
  • Objetos e membros não estáticos de objetos diretamente instanciados em seu escopo.
De maneira geral, um membro estático não pode utilizar um elemento não estático se não for garantida sua existência (instanciação) no mesmo escopo.

Importação estática

Introduzida na versão 5 do Java, esta característica permite o uso de constantes declaradas em classes externas sem necessidade de sua qualificação (indicação do nome da classe).

Por exemplo, a classe Math contém constantes usualmente empregadas (de forma qualificada) como Math.PI ou Math.E, como no exemplo que segue.

// SEM importação estática
import java.lang.*;
public class SemImportStatic {
   public static void main(String args[]) {
      double raio = Double.parseDouble(args[0]);
      // uso da constante qualificada
      double perim = 2 * Math.PI * raio;
      System.out.print("raio = " + raio); 
      System.out.println(", perim = " + perim);
   }
}

Com uso da importação estática, torna-se possível não qualificar tais constantes, usadas diretamente como PI ou E, como na versão modificada do exemplo acima com uso desta alternativa.

// COM importação estática
import static java.lang.Math.*;
public class ComImportStatic {
   public static void main(String args[]) {
      double raio = Double.parseDouble(args[0]);
      // uso da constante qualificada
      double perim = 2 * PI * raio;
      System.out.print("raio = " + raio); 
      System.out.println(", perim = " + perim);
   }
}

A importação estática não tem impacto expressivo no código e, por isso, não é usada com muita frequência.

Concluindo, o uso de membros estáticos, como visto, é bastante conveniente para criação de um mecanismo simples de compartilhamento entre instâncias de uma classe, além de permitir a criação simplificada de bibliotecas de funções. Por estas razões, os membros estáticos são empregados frequentemente no código.


POO-P-18-Especificadores e Modificadores POO-P-20-Herança Simples

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: