Java: Elementos Estáticos
Neste novo compartilhamento de conhecimentos pretendo abordar os elementos estáticos do Java. O objetivo é apresentar todos estes elementos, explicar as suas características e suas funcionalidades. Além de, como tenho feito, citar alguns casos de uso para eles.
Métodos Estáticos
Toda solução Java possui uma classe principal, que é utilizada para correr a aplicação. Isto porque na arquitetura Java, a máquina virtual está preparada para buscar o "ponto de arranque" das aplicações. Este ponto de arranque é o método "main". Entretanto você já observou a assinatura deste método?
public static void main(String args){ ... }
Isto mesmo, este método possui a palavra "static" em sua assinatura. Esta palavra foi convencionada pela arquitetura, para definir os elementos estáticos.
Então, o método "main" é um método estático?
Sim. Este foi principal motivo para eu colocar os métodos como primeiro elemento a ser explicado. Pois este será o início do entendimento da importância desta funcionalidade no ambiente Java.
Entendido então que um método estático deve possuir em sua assinatura a palavra "static", a seguir a uma das palavras de definição de escopo: private, public ou protected; vamos entender o que significa assinar um método como "static".
Sabemos que as classes Java são uma espécie de script de construção de objetos. Ou seja, será através das classes que instânciaremos nossos objetos, através da sintaxe "new Classe()". Porém, as classes tornam-se muito mais poderosas quando ganham a possibilidade de terem elementos estáticos. Os elementos estáticos são elementos que "existem", ou seja, estão disponíveis para uso, sem a necessidade de serem instânciados. Isto quer dizer que podem ser utilizados em código sem a necessidade de existirem objetos produzidos (sem a necessidade de um comando "new Classe()").
Como estamos a falar dos métodos, é importante destacar algumas questões: os métodos estáticos não podem utilizar variáveis globais da classe que não sejam estáticas; os métodos estáticos não devem guardar estado; estes métodos devem, basicamente, possuir lógicas e funcionalidades "auto contidas".
Os métodos estáticos são utilizados conforme o exemplo a seguir:
"Classe.metodoEstatico();"
Não existem restrições quanto a parâmetros de entrada, retorno de valores etc. As únicas diferenças se limitam as questões apresentadas anteriormente.
Variáveis Estáticas
Passando às Variáveis estáticas. Estas variáveis são declaradas da mesma maneira como fazemos para outras váriavies globais das classes. A diferença em termos de código esta na adição da palavra "static".
Ex.:
public static String mensagemGlobal = "Esta é uma mensagem global";
Porém existe uma grande diferença em termos funcionais. As variáveis estáticas são globais à aplicação. Diferente das demais variáveis da classe, estas variáveis são únicas no contexto da aplicação. Ou seja, mesmo que diversos objetos sejam criados a partir de uma classe, se esta classe possuir variáveis estáticas, todos os objetos irão compartilhar estas mesmas variáveis. Todos irão "apontar" para a mesma região em memória alocada para estas variáveis.
Devido a este comportamento, devemos ter muita atenção ao lidar com estas variáveis, pois o seu estado armazenado (ou valor) pode ser modificado por qualquer objeto, ou código estático dentro da aplicação, Em outras palavras, não se pode garantir o valor nela armazenado.
Podemos nos perguntar: mas se eu controlo o código, tenho como controlar os momentos em que esta variável será modificada, certo?
Errado, em um ambiente de multi tarefa, diversos processos ou execuções do nosso código podem correr em paralelo. Neste cenário, não temos como saber o momento em que cada processo estará a correr e acessar as varáveis estáticas. Com isto, o valor armazenado pelo processo 1, pode ser modificado pelo processo 2, antes de o controlo de execução "voltar" para o processo 1. Então, quando o processo 1 for utilizar a varável, esta já não mais possuirá o valor esperado.
A única forma de garantir a integridade destas variáveis é controlar o acesso as mesmas, utilizando técnicas de controlo de acesso concorrente, como semáforos. Porém, esta abordagem implicará em redução de performance da alpicação, pois os processos ficarão a espera do "recurso" para seguirem. Com isto estaremos a construir um "gargalo" de processamento.
Então quando devemos utilizar estas variáveis? Veremos a seguir alguns exemplos onde estas varáveis são essênciais na construção de suas arquiteturas.
Blocos Estáticos
Antes de entrar neste assunto específico, acredito que seja um bom momento para apontar algumas questões:
Toda estrutura estática somente será processada quando da necessidade de utilização da(s) classe(s) onde estas estruturas existam. Digo com isto que não basta simplesmente correr a aplicação para que estas estruturas sejam processadas. Entretanto, qualquer "tentativa" de acesso fará com que toda a parte estática seja "iniciada". O que é garantido é que tudo que é estático será processado antes das "coisas" não estáticas.
Isto é importante porque, se quisermos que estas estruturas sejam carregadas no "startup" da aplicação, precisamos simular uma utilização destes elementos. Normalmente adicionamos um método estático "init" a estas classes e chamamos este método dentro do processo de inicialização da aplicação. Por exemplo, no inicio do código do método "main", ou no método "init" de uma Servlet etc.
Outro ponto importante é o escopo no qual os elementos estáticos são únicos, ou compartilhados. Dentro de uma mesma JVM, os elementos estáticos serão únicos no contexto da aplicação. Observe que aplicações que executem dentro de: container, servidores web, servidores de aplicações etc; estas compartilham um contexto "raiz" sobre o qual serão executadas. Com isto os elemetos estáticos podem ser únicos para multiplas aplicações (que estejam associadas ao mesmo classloader).
Os blocos estáticos consistem em trechos de lógica de programação a serem processados em contexto estático. Ou seja, estes códigos serão executados sempre que a classe for carregada para o contexto da aplicação (carregada pelo Classloader). Aqui também se aplicam as observações e restrições apresentadas para os métodos estáticos em termos de variáveis utilizadas etc.
Ex,:
public class MinhaClasse {
static {
System.out.println("Este é um bloco estático");
}
}
No exemplo acima o texto "Este é um bloco estático" será exibido na primeira vez que a classe "MinhaClasse" for utilizada. Sempre antes do processamento de qualquer código não estático.
Containers
Como dito que exemplos seriam descritos, este será um exemplo de utilização destas variáveis dentro de ferramentas do dia-a-dia. Vamos tomar como exemplo o Web Container, utilizado com infra estrutura para todas as aplicações web.
Existe no container um conceito de escopo de sessão para as aplicações. Sem entrar em muito detalhe do propósito deste escopo, vou resumir a dizer que é um repositório global e persistente para toda a aplicação. Local onde podemos guardar informações que devem estar disponível sempre que estamos a processar pedidos web.
Como estamos a falar de elementos estáticos já te deve ter ocorrido que este tipo de persistencia poderia ser "desenhado" a utilizar variáveis estáticas. Mas vou descrever uma "ideia" de como fazermos isto.
Ao desenharmos um container web, teremos uma classe que será a principal dentro da implementação desta funcionalidade.
Vamos supor que ela se chame "MainContainer". Esta classe será a responsável por guardar as informações de escopo de sessão, bem como interceptar os Requests e produzir os Responses. Observe que esta classe não é uma Sevlet, esta classe será a responsável também por instanciar e gerenciar todas as Servlets que forem publicadas dentro do container. Porém, ao avaliarmos somente o contexto do fluxo pedido-reposta, o processo se assemelha bastante com o que se passa com o método "service" de uma Servlet.
Voltando ao ponto, esta classe deverá possuir uma variável estática com uma tabela onde teremos uma relação Usuário->"Dados Armazenados". Ou seja, a partir de um identificador deve ser possível obter os dados associado. Estes dados, por vez, também estarão estruturados em uma tabela, onde as informações pertinentes a cada Usuário ficarão armazenadas. A esta variável estática daremos o nome de "globalSession".
Ex.:
public class MainContainer{
private static HashMap globalSession
= new HashMap();
private static void addNewSession (String sessionKey, HashMap map){ ... }
private static void getSession(String sessionKey) { ... }
}
Então, sempre que um pedido for interceptado pela classe "MainContainer", faremos a verificação se existe algum identificador de sessão dentro do pedido. Caso não exista, geramos um novo identificador e o adicionamos a nossa variável "globalSession", associado a uma nova tabela vazia.
Ex.:
public class MainContainer{
...
if (request.getHeader().getCookies().get("sessionKey")==null) {
String sessionKey = gerarNovoIdentificador()
;
addNewSession (sessionKey
, new HashMap() )
;
response.getHeader().getCookies().add("sessionKey", sessionKey
);
}
...
}
Observe que no exemplo estamos a guardar e obter o identificador de sessão através de Cookies. Entretanto, podemos utilizar outras maneiras para gerir isto. Os Cookies são sempre enviados junto as requisições, associadas ao domínio, ou contexto ao qual estes estiverem agregados. Não pretendo aqui detalhar esta funcionalidade.
Ainda no fluxo de processamento do Request, caso exista um identificador, devemos buscar a tabela de dados associada a este identificador e adiciona-la ao elemento Request, Permitindo assim que esta tabela seja utilizada por quem irá implementar uma Servlet a ser publicada em nosso container.
Ex.:
public class MainContainer{
...
if (request.getHeader().getCookies().get("sessionKey")==null) {
String sessionKey = gerarNovoIdentificador()
;
addNewSession (sessionKey
, new HashMap() )
;
response.getHeader().getCookies().add("sessionKey", sessionKey
);
} else {
HashTable session =
getSession(request.getHeader().getCookies().get("sessionKey")
)
;
request.putSession(session);
}
...
}
Ou seja, podemos obesrvar no exemplo que a referência para a tabela específica, associada a "sessionKey" obtida, foi agregada ao elemento Request (na parte do código "putSession(session)
").
Pensando a frente, onde alguém que está a desenvolver uma Servlet. Quando este fizer "request.getSession()" dentro do método "service", por exemplo. O resultado obtido será esta referencia para a tabela de sessão específica da sua própria Servlet.
Acho que me alonguei em demasiado... voltando ao assunto deste post. Neste exemplo podemos verificar a importância e a forma como uma variável estática pode ser utilizada.
Gestão de Memória
Após a vasta descrição anterior sobre um modelo de estrutura para armazenamento de sessão, em web containers. Imagino que possa ser mais suscinto aqui.
Através de variáveis estáticas e um conceito bastante semelhante ao apresentado, podemos colocar em memória informações que sejam utilizadas com muita frequência por nossas aplicações. Sejam estas: informações, configurações vindas de ficheiros ou dados vindo de tabelas em uma base de dados.
Um exemplo disto são os frameworks de gestão de Cache. Neste caso, os elementos contidos em tabelas de uma base dados são carregados em variáveis estáticas e disponibilizados. Quando ocorre uma "tentativa" de consulta a estes elementos, os mesmos já estsrão disponíveis em memória, acelerando assim todo o processo, pois evita-se que novas consultas a tabelas externas sejam efetuadas.
Este processo é tão complexo como o caso dos containers. Nestes frameworks de gestão de Cache, todos os acessos aos dados são interceptados e diversas regras e processamentos são executados de forma a garantir a integridade dos dados existentes; fazer a replicação destes em outros Caches; garantir a persistência de modificações; gerenciar timeouts de valores etc. Diversos são os benefícios desta abordagem, porém está fora do escopo deste post abordar este assunto. Venho aqui apenas destacar que no cerne desta arquitetura estão as variáveis e elementos estáticos.
O Factoring Pattern
Segue aqui mais um exemplo de utilização de elementos estáticos. Mais uma vez o objetivo não é descrever o Pattern, mas destacar que em sua estrutura também utilizam-se as vantagens disponibilizadas pelos recursos estáticos.
Neste pattern possuímos o método estatico "getInstance()" que é responsável por construir objetos e retorna-los para serem utilizados no código.
Ainda dentro deste pattern possuímos também o conceito de "singleton" que corresponde a garantir que somente haverá uma instância de determinado elemento dentro do contexto de execução das aplicações.
Para concluir, existem inúmeras aplicações para estes recursos estáticos. O que quis demonstrar neste post foi o conceito em si. Creio que os exemplos descritos permitem abrir vossa visão sobre o potencial disponibilizado pelo Java. Espero ter contribuído e ampliado o seu conhecimento.