Se você estiver usando a palavra-chave 'estática' sem a palavra-chave 'final', isso deve ser um sinal para considerar cuidadosamente seu design. Mesmo a presença de um 'final' não é um passe livre, pois um objeto final estático mutável pode ser igualmente perigoso.
Eu estimaria algo em torno de 85% das vezes que vejo uma 'estática' sem uma 'final', é ERRADO. Muitas vezes, vou encontrar soluções alternativas estranhas para mascarar ou ocultar esses problemas.
Por favor, não crie mutáveis estáticos. Especialmente coleções. Em geral, as coleções devem ser inicializadas quando o objeto que os contém é inicializado e devem ser projetadas para que sejam redefinidas ou esquecidas quando o objeto que as contém é esquecido.
O uso de estática pode criar bugs muito sutis, o que causará muitos dias de sofrimento aos engenheiros. Eu sei, porque eu criei e caço esses bugs.
Se você quiser obter mais detalhes, leia…
Por que não usar estática?
Há muitos problemas com a estática, incluindo a escrita e a execução de testes, além de erros sutis que não são imediatamente óbvios.
O código que depende de objetos estáticos não pode ser facilmente testado em unidade, e as estáticas não podem ser facilmente ridicularizadas (geralmente).
Se você usa estática, não é possível trocar a implementação da classe para testar componentes de nível superior. Por exemplo, imagine um CustomerDAO estático que retorne objetos Customer que ele carrega do banco de dados. Agora eu tenho uma classe CustomerFilter, que precisa acessar alguns objetos Customer. Se o CustomerDAO for estático, não posso escrever um teste para o CustomerFilter sem primeiro inicializar meu banco de dados e preencher informações úteis.
E a população e a inicialização do banco de dados levam muito tempo. E, na minha experiência, sua estrutura de inicialização do banco de dados mudará com o tempo, o que significa que os dados se transformarão e os testes poderão ser interrompidos. IE, imagine que o Cliente 1 costumava ser um VIP, mas a estrutura de inicialização do DB mudou e agora o Cliente 1 não é mais VIP, mas seu teste foi codificado para carregar o Cliente 1…
Uma abordagem melhor é instanciar um CustomerDAO e passá-lo para o CustomerFilter quando ele é construído. (Uma abordagem ainda melhor seria usar o Spring ou outra estrutura de Inversão de controle.
Depois de fazer isso, você pode rapidamente zombar ou deletar um DAO alternativo no CustomerFilterTest, permitindo que você tenha mais controle sobre o teste,
Sem o DAO estático, o teste será mais rápido (sem inicialização do banco de dados) e mais confiável (porque não falhará quando o código de inicialização do banco de dados for alterado). Por exemplo, nesse caso, garantir que o Cliente 1 seja e sempre será um VIP, no que diz respeito ao teste.
Executando testes
A estática causa um problema real ao executar conjuntos de testes de unidade juntos (por exemplo, com o servidor de Integração Contínua). Imagine um mapa estático de objetos Socket de rede que permaneçam abertos de um teste para outro. O primeiro teste pode abrir um soquete na porta 8080, mas você esqueceu de limpar o mapa quando o teste for interrompido. Agora, quando um segundo teste é iniciado, é provável que ocorra um erro fatal ao tentar criar um novo soquete para a porta 8080, pois a porta ainda está ocupada. Imagine também que as referências de soquete em sua coleção estática não sejam removidas e (com exceção do WeakHashMap) nunca são elegíveis para serem coletadas como lixo, causando um vazamento de memória.
Este é um exemplo super generalizado, mas em sistemas grandes, esse problema ocorre o tempo todo. As pessoas não pensam nos testes de unidade iniciando e parando seus softwares repetidamente na mesma JVM, mas é um bom teste para o design de seu software e, se você deseja obter alta disponibilidade, é algo que precisa estar ciente.
Esses problemas geralmente surgem com objetos de estrutura, por exemplo, suas camadas de acesso ao banco de dados, cache, sistema de mensagens e log. Se você estiver usando Java EE ou algumas das melhores estruturas de criação, provavelmente elas gerenciam muito disso para você, mas se, como eu, você está lidando com um sistema legado, pode ter muitas estruturas personalizadas para acessar essas camadas.
Se a configuração do sistema que se aplica a esses componentes da estrutura for alterada entre os testes de unidade, e a estrutura de teste de unidade não desmontar e reconstruir os componentes, essas alterações não terão efeito e, quando um teste depender dessas alterações, elas falharão. .
Mesmo componentes que não sejam de estrutura estão sujeitos a esse problema. Imagine um mapa estático chamado OpenOrders. Você escreve um teste que cria alguns pedidos em aberto e verifica se todos estão no estado correto e o teste termina. Outro desenvolvedor escreve um segundo teste que coloca os pedidos de que precisa no mapa OpenOrders e afirma que o número de pedidos é preciso. Executar individualmente, esses testes passariam, mas quando executados juntos em um conjunto, eles falhariam.
Pior, a falha pode ser baseada na ordem em que os testes foram executados.
Nesse caso, evitando a estática, você evita o risco de persistência de dados nas instâncias de teste, garantindo melhor confiabilidade do teste.
Erros sutis
Se você trabalha no ambiente de alta disponibilidade ou em qualquer lugar em que os encadeamentos possam ser iniciados e parados, a mesma preocupação mencionada acima com os conjuntos de testes de unidade pode ser aplicada quando o código também estiver em produção.
Ao lidar com threads, em vez de usar um objeto estático para armazenar dados, é melhor usar um objeto inicializado durante a fase de inicialização do thread. Dessa maneira, toda vez que o encadeamento é iniciado, uma nova instância do objeto (com uma configuração potencialmente nova) é criada e você evita que os dados de uma instância do encadeamento fluam para a próxima instância.
Quando um thread morre, um objeto estático não é redefinido ou coletado como lixo. Imagine que você tenha um segmento chamado "EmailCustomers" e, quando iniciado, preenche uma coleção estática de String com uma lista de endereços de email e começa a enviar cada um dos endereços. Digamos que o encadeamento seja interrompido ou cancelado de alguma forma, portanto sua estrutura de alta disponibilidade reinicia o encadeamento. Então, quando o encadeamento é iniciado, ele recarrega a lista de clientes. Mas como a coleção é estática, ela pode reter a lista de endereços de email da coleção anterior. Agora, alguns clientes podem receber e-mails duplicados.
An Aside: Final Estático
O uso de "final estático" é efetivamente o equivalente em Java de um C # definido, embora haja diferenças técnicas de implementação. AC / C ++ #define é trocado de código pelo pré-processador, antes da compilação. Um Java "estático final" acabará com a memória residente na pilha. Dessa forma, é mais semelhante a uma variável "const estática" em C ++ do que a um #define.
Sumário
Espero que isso ajude a explicar algumas razões básicas pelas quais a estática é problemática. Se você estiver usando uma estrutura Java moderna, como Java EE ou Spring, etc, poderá não encontrar muitas dessas situações, mas se estiver trabalhando com um grande corpo de código legado, elas poderão se tornar muito mais frequentes.