Você está na página 1de 31

Effective Java – Resumo.

Criando e Destruindo Objetos

Este artigo trata sobre criação e destruição de objetos: quando e como criá-los ou evitar criá-los, como garantir que são
destruídos no tempo correto e como gerenciar qualquer ação de cleanup que precisa preceder a destruição de um objeto.

Item 1: Considere a utilização de static factory methods ao invés de construtores


A forma mais comum de permitir que clientes obtenham uma instância de sua classe é através de construtores públicos.
Uma outra maneira possível é através de um static factory method público, que nada mais é do que um método estático
que retorna uma instância da classe. Ex.:

    public static Boolean valueOf(boolean b) {


        return (b ? Boolean.TRUE : Boolean.FALSE);
    }
Uma das vantagens de um static factory method é que eles possuem nomes. Em casos em que temos construtores para
diversas categorias diferentes de objetos, às vezes recebendo até os mesmos tipos em seus parâmetros (diferenciando
apenas pela ordem já que a assinatura do construtor deve ser diferente), é aconselhável substituir um ou mais desses
construtores por métodos factory com nomes mais sugestivos para indicar as diferenças.

Uma segunda vantagem dos static factory methods sobre os construtores é eles não precisarem criar um novo objeto
cada vez que são chamados, permitindo um maior controle das instâncias criadas ou até mesmo fazer um cache de

objetos. Assim é possível, por exemplo, garantir que duas instâncias são iguais - a.equals(b) - se e somente se a ==
b, e então os clientes podem utilizar esta segunda forma para testar igualdade, ganhando performance.
Uma terceira vantagem dos static factory methods é eles poderem retornar um objeto de qualquer subtipo do seu tipo de
retorno, aumentando a flexibilidade. Desta forma, estas subclasses podem não constar na API, tornando-a mais
compacta e simples de entender. Esta técnica é utilizada em frameworks baseadas em interfaces, que são tipos de
retorno naturais para estes métodos, e ainda garante que o cliente também estará utilizando as interfaces, o que é outra
boa prática. É até possível criar soluções mais elaboradas, como a encontrada na JCE (Java Cryptography Extension),
em que as classes são instanciadas de acordo com o parâmetro passado para o método.

A principal desvantagem do uso de static factory methods substituindo os construtores é que não é possível herdar uma
classe sem que ela tenha um construtor visível externamente. Porém isto pode ser visto como uma certa vantagem, uma
vez que favorece composição ao invés de herança.
Uma segunda desvantagem é que eles não são facilmente distinguidos de outros métodos estáticos, ficando mais difícil

entender na documentação da API como instanciar uma classe que utiliza static factory methods ao invés de
construtores. Algumas convenções estão sendo criadas para endereçar esse problema, sendo que dois nomes são comuns

para esses métodos: valueOf (utilizado normalmente para conversões de tipo) e getInstance.


Portanto, o importante é analisar o código para verificar se seria apropriado o uso de static factory methods. Caso seja
indiferente, ainda é melhor utilizar construtores, por eles serem o padrão.
Item 2: Garanta uma propriedade singleton com um construtor privado
Um singleton é uma classe que pode ser instanciada uma única vez, como explicado no livro de Design Patterns da GoF.
Normalmente representa um componente que é intrinsicamente único, como uma placa de vídeo ou o sistema de
arquivos.
A forma de implementar um singleton é mantendo o construtor privado e provendo um membro estático público para
retornar a instância, que pode ser diretamente um campo final da classe ou então um método que retorne essa instância.
Ex.:

    public class Elvis {


        private static final Elvis INSTANCE = new Elvis();

        private Elvis() {


            // construtor da classe
        }

        public static Elvis getInstance() {


            return INSTANCE;
        }
    }
Para que o singleton seja serializável, também é necessário incluir um método readResolve que descarte a nova
instância criada na desserialização, retornando aquela já existente.

Item 3: Garanta a não-instancialização com um construtor privado


Às vezes é prático criar uma classe apenas com métodos estáticos que podem ser utilizados para executar tarefas que

ajudam outras classes, tal como acontece na  java.lang.Math. Essas classes não foram criadas para serem
instanciadas, mas se não for colocado nenhum construtor o compilador automaticamente criará um, permitindo que
sejam criados os objetos. Para evitar isso, inclua um construtor privado. Note que simplesmente declarar a classe
abstrata não funcionaria, pois daria a impressão de que ela foi criada para ser estendida e as subclasses seriam
instanciáveis. Como dito anteriormente, se a classe possuir apenas um construtor privado, não será possível estendê-la.

Item 4: Evite criar objetos duplicados


Reutilizar um objeto é mais rápido e elegante do que criar um novo objeto toda vez. Um objeto poderá sempre ser
reutilizado caso seja imutável. Um exemplo do que evitar:

    String s = new String("evite isso");


O código acima cria uma nova instância a cada vez que é executado, sendo que seu argumento já é em si mesmo uma

instância de String funcionalmente idêntica a qualquer objeto criado pelo construtor. Portanto, o correto seria
fazer simplesmente o seguinte:

    String s = "faça assim";


Desta forma, só é criado um novo objeto se ainda não existir um equivalente.

Para outros casos, podem ser utilizados static factory methods ao invés de construtores para atingir este objetivo, como
no exemplo da classe Boolean acima, cujo método valueOf retorna um objeto já existente.
Outra situação seria quando dentro de um método que é chamado várias vezes você cria objetos como o Calendar ou
TimeZone, mas na verdade seus valores são fixos durante o ciclo de vida do programa e bastaria que fossem criados
quando é inicializada a classe (em um bloco static) ou quando são usados pela primeira vez.
Porém, essa dica não deve ser levada ao extremo. Objetos pequenos cujo construtor possui pouco processamento não
representam muito overhead de performance, então não há problema em criá-los se isso irá melhorar a clareza ou
simplicidade do código. Já para casos como a conexão em um banco de dados, cuja criação tem um custo elevado,
compensa até utilizar mecanismos como um pool de objetos que são reutilizados.
É importante ressaltar também que é melhor criar um novo objeto, mesmo sem necessidade, do que reutilizar um objeto
quando deveria ser criado um novo, pois isso poderá levar a sérios bugs no programa.

Item 5: Elimine referências a objetos obsoletas


Apesar do Java cuidar da maior parte do gerenciamento de memória através do garbage collector, não podemos
esquecer totalmente do assunto enquanto programamos.
É importante estar sempre atento para verificar se uma referência a um objeto que não é mais utilizado está sendo retida,
impedindo que ele (e aqueles referenciados por ele) sejam recolhidos pelo garbage collector. Isso é mais freqüente em
casos em que a classe propõe-se a gerenciar sua própria memória, como no exemplo abaixo:

    public class Stack {


        private Object[] elements;
        private int size = 0;

        public Stack(int initialCapacity) {


            this.elements = new Object[initialCapacity];
        }

        public void push(Object e) {


            elements[size++] = e;
        }

        public Object pop() {


            return elements[--size];
        }
    }
No código acima, são inseridos e retornados elementos de uma array. Pela implementação é possível observar que uma
vez que um elemento é retornado da array, ele não é mais utilizado, porém sua referência continua lá e o Java não sabe

que ele não será mais utilizado. Uma maneira melhor de implementar o método pop seria:

    public Object pop() {


       Object result = elements[--size];
       elements[size] = null;
       return result;
    }
Essa técnica também ajuda na detecção de erros, uma vez que se tentarem reutilizar a referência por engano acontecerá

logo uma NullPointerException ao invés de um comportamento imprevisível.


Porém, novamente, não é necessário radicalizar e colocar todas as referências como null assim que terminar de utilizar o
objeto. O melhor é reutilizar a variável ou deixá-la sair de escopo. Isso costuma ocorrer naturalmente quando você a
declara no menor escopo possível. Note que não basta sair do bloco em que a variável está definida, e sim do método
que o contém.
São em casos como o acima que é mais comum precisar estar atento em relação à necessidade de colocar o null
explicitamente. Outro caso comum de vazamento de memória é o de caches. Em algumas situações é possível controlar
o ciclo de vida do cache de acordo com o escopo de variáveis que referenciam a chave, e então o problema é

solucionável com um WeakHashMap. Mas na maioria dos casos é necessário recorrer a uma thread que faça a
limpeza dos objetos já não utilizados regularmente ou então remover os registros mais antigos conforme são

adicionados novos, por exemplo utilizando o método removeEldestEntry da classe java.util.LinkedHashMap.


Os vazamentos de memória não costumam ser facilmente identificados, sendo necessário verificar detalhadamente o
código ou então utilizar ferramentas conhecidas como heap profilers. Por isso é preferível um bom planejamento
antecipadamente da classe do que deixar para diagnosticar os problemas depois.

Item 6: Evite finalizadores

Considerando que a especificação da linguagem Java não garante a execução do método finalize, e muito menos o
tempo em que ela irá ocorrer, é importante não depender da execução deste método. É melhor criar um método que

deva ser explicitamente chamado pelo cliente quando o objeto não estiver mais sendo usado, como o close() da

classe InputStream. O finalize deve ser implementado apenas como uma garantia a mais para a liberação de
recursos, não como a principal solução.
Outro motivo para evitar este método é que se ocorrer qualquer exceção dentro do mesmo ela é simplesmente ignorada,
podendo deixar outros objetos em estado corrompido sem nem ao menos avisar.
Um uso legítimo de finalizadores é no caso de métodos nativos delegando tarefas a objetos nativos, pois estes estão fora
do escopo do garbage collector e por isso não são recolhidos automaticamente por ele. Porém, se o objeto retém
recursos críticos, é necessário ter um método explícito para liberá-los.
Outro ponto importante é que, diferentemente dos construtores, não existe uma chamada automática do

método finalize da superclasse de uma classe. Portanto, ao fazer override deste método, o correto é finalizar a

subclasse em um bloco try e chamar o super.finalize() no bloco finally, para garantindo que ambos serão


chamados.
Uma classe pode evitar que seu finalizador não seja chamado em decorrência de uma incorreta implementação do
finalizador da subclasse. Isso é conseguido ao custo de criar um objeto a mais para cada instância. Ao invés de colocar o
código no finalizador da própria classe, é criada uma classe anônima com um finalizador contendo o código necessário.
Ex.:

   
public class Foo {
        private final Object finalizerGuardian = new Object() {
            protected void finalize() throws Throwable {
                // Finaliza o objeto Foo
            }
        };
    ... // restante da classe Foo
    }

Métodos Comuns a Todos Objetos

Este artigo trata sobre os métodos não finais da classe Object. É responsabilidade do programador sobrescrevê-los em
suas classes de forma adequada, garantindo o bom funcionamento de outras classes que os utilizam.

Item 7: Obedeça ao contrato geral quando sobrescrever o equals


As conseqüências por sobrescrever o equals de forma errada podem ser desastrosas. O jeito mais fácil de evitar
problemas é simplesmente não sobrescrevê-lo, de forma que cada instância é igual apenas a ela mesma. Esta é a
abordagem correta nas seguintes situações:
- Cada instância da classe é intrinsecamente única. Isto aplica-se a classes que representam entidades ao invés de
valores, como por exemplo a Thread.
- Não é importante que a classe provenha um teste lógico de igualdade, pois os clientes não precisarão desta
funcionalidade.
- Uma superclasse já sobrescreveu o equals e o comportamento herdado é adequado para a classe.
- A classe é privada ou com acesso somente dentro do pacote e você tem certeza de que o método equals nunca será
chamado. Nesse caso é mais apropriado que o método seja sobrescrito lançando
uma UnsupportedOperationException, de forma a garantir que não seja utilizado futuramente.
De forma análoga, é adequado sobrescrever o equals em classes de valores, como a Integer e a Date, em que
existe a noção de igualdade lógica. Desta forma acontece o comportamento esperado quando utilizadas como chaves em
Maps ou como elementos de Sets. Mas se uma superclasse já sobrescreveu o método adequadamente, ou na classe é
garantida a existência de apenas um objeto para cada valor, não é necessária uma nova implementação.
Ao sobrescrever o método equals, é necessário obedecer a seu contrato, implementando uma relação de equivalência
com as seguintes características:
- Reflexiva: x.equals(x) == true

- Simétrica: x.equals(y) == true se e somente se y.equals(x) == true. Para evitar problemas neste


caso, deve-se comparar somente objetos da mesma classe, pois não é possível garantir como está sendo realizada a
implementação do equals em outra classe.

- Transitiva: Se x.equals(y) == true e y.equals(z) == true então x.equals(z) == true.  Este


item é particularmente importante no caso de estender uma classe não abstrata, pois não é possível incluir um novo

aspecto sem violar esta regra ou a de simetria no equals. Na API do Java, por exemplo, a classe Timestamp viola
a regra de simetria ao herdar a Date, conforme explicado na sua documentação. Quando acontecer um caso
semelhante, é melhor utilizar composição ao invés de herança.

- Consistente: chamadas múltiplas de x.equals(y) sempre retornam true ou sempre retornam false se


nenhuma informação usada no equals é modificada nos objetos. (esta regra é mais significativa nos casos de objetos
imutáveis).
- Para qualquer referência não nula de x, x.equals(null) == false. É importante que o código deste método
verifique esta possibilidade e retorne false, ao invés de permitir que seja lançada
uma NullPointerException durante sua execução. Utilizando o operador instanceof antes de fazer o cast
do objeto esse problema já é resolvido.
Desta forma, os passos para sobrescrever o equals adequadamente são:
1. Usar o operador == para checar se o argumento é uma referência ao mesmo objeto, o que poupa processamento caso
a comparação dos objetos tenha problemas de performance.
2. Usar o instanceof para checar se o argumento é do tipo correto. Ele pode ser comparado com a própria classe ou
com uma interface implementada por esta classe (desde que sejam obedecidas as regras na implementação das outras
classes).
3. Realizar o cast do argumento para o tipo correto.
4. Para cada campo significativo da classe, verificar o valor correspondente do campo do objeto passado como
argumento. Para objetos, deve ser aplicado o equals recursivamente, testando se ele é nulo antes de aplicar o
método. No caso de tipos primitivos, exceto float e double, utiliza-se o operador ==. O float deve ser convertido para int

através do método Float.floatToIntBits e o double para long através de Double.doubleToLongBits.


Para arrays, deve ser comparado cada elemento.
A performance do método pode ser afetada pela ordem em que são comparados os campos. Para otimizar o processo, é
melhor comparar primeiro os campos que têm maior probabilidade de estarem diferentes ou cuja comparação seja mais
rápida. Normalmente não é necessário comparar campos redundantes, calculados a partir de outros campos, mas isso
pode ser feito caso o campo sozinho já sumarize todo o objeto, poupando tempo caso esta comparação já retorne falso.
Observações finais:
- Sempre sobrescreva o hashCode quando sobrescrever o equals.
- Utilize métodos simples de comparação, não tentando inventar mecanismos mais complexos que podem causar
problemas.
- Não escreva um método equals que dependa de recursos incertos, tais como uma conexão de rede. É melhor que
dependa apenas de objetos residentes em memória, garantindo a consistência.
- Não substitua o Object por outro tipo na assinatura do método, pois nesse caso você está fazendo overload do
método e não sobrescrevendo-o.

Item 8: Sempre sobrescreva o hashCode quando sobrescrever o equals


Não sobrescrever o hashCode e alterar o equals viola o seu contrato, o que causará problemas ao usar a classe
juntamente com collections que dependem deste método, tais como HashMap, HashSet e Hashtable. O
método hashCode deve obedecer às seguintes regras:
- Sempre que for chamado no mesmo objeto mais de uma vez durante a execução de uma aplicação ele deve retornar o
mesmo número inteiro, desde que nenhuma informação utilizada no equals tenha sido modificada.
- Se dois objetos são iguais de acordo com o método equals, então chamando o hashCode nos dois objetos deve
retornar o mesmo resultado. Daí a importância de sobrescrever um método quando o outro for sobrescrito.
- Não é necessário que objetos diferentes produzam hash codes distintos, mas isso tem influência direta na performance
de hash tables.
Portanto, a forma mais simples possível de obedecer ao contrato é simplesmente retornar um valor constante. Porém,
esta técnica não deve ser utilizada, já que não estaria ajudando em nada. Idealmente, os objetos deveriam ser
distribuídos uniformemente dentro de uma collection, o que é extremamente difícil de ser atingido. Mas uma
aproximação pode ser obtida pelos seguintes passos:
1. Guarde um valor diferente de zero (ex. 17) em uma variável int chamada result
2. Para cada campo considerado no equals, faça o seguinte:

   a. calcule um hashCode para o campo (f) e jogue em uma variável c:


      i. Se o campo for boleano, calcule (f ? 0 : 1)
      ii. Se o campo for byte, char, short ou int, calcule (int) f
      iii. Se o campo for long, calcule (int) (f ^ ( f >>> 32))
      iv. Se o campo for float, calcule Float.floatToIntBits
      v. Se o campo for double, calcule Double.doubleToLongBits e então faça o hash do resultado pelo passo
2.a.iii

      vi. Se o campo for uma referência a objeto e a classe, realize a comparação do campo chamando
o equals recursivamente e chame o hashCode recursivamente. Se for necessária uma comparação mais complexa,
utilize uma "representação canônica" do campo e chame o método de hashCode nela. Se o valor do campo for nulo,
retorne 0

      vi. Se o campo for uma array, trate cada elemento como um campo separado e combine os valores como descrito no
método 2.b

   b. Combine o hash code c, obtido no passo a, na variável result, da seguinte forma: result = 37 * result
+ c;
3. Retorne a variável result
4. Revise o método hashCode e equals verificando se instâncias iguais estão retornando o mesmo hash code, de
forma a obedecer ao contrato. É imprescindível que qualquer campo não derivado que não é utilizado no equals não
seja utilizado também no hashCode, para não violar a segunda regra do contrato.
A multiplicação no passo 2.b faz com que a ordem dos campos seja considerada, e o 37 foi escolhido por ser um
número ímpar primo. Números pares não devem ser utilizados porque caso a multiplicação fique muito grande seria
perdida informação, pois multiplicação por 2 é equivalente a uma operação de shift.
Para melhorar a performance, se a classe for imutável pode ser considerada a possibilidade de guardar o valor do hash
code no objeto ao invés de recalculá-lo toda vez. Uma má idéia é excluir campos significativos do cálculo, pois isso
poderá impactar a performance no uso de hash tables posteriormente.
Item 9: Sempre sobrescreva o toString
O método original toString, herdado da classe Object, consiste no nome da classe seguido por um sinal de arroba
e a representação hexadecimal do hash code. O contrato para esse método diz que a string retornada deve ser uma
representação informativa e concisa e deve ser fácil para uma pessoa ler. Uma boa implementação deste método torna a
classe mais agradável de ser usada, pois ele é chamado automaticamente quando o objeto é passado em um println,
ao usar o operador de concatenação (+) ou juntamente com o assert.
Quando viável, o método toString deve retornar todas as informações relevantes contidas no objeto. Mas pode ser
criado um tipo de resumo que identifique o objeto quando ele for muito grande ou tiver alguma informação que não seja
possível representar por uma string. Idealmente, o valor retornado é auto-explicativo.
No caso de classes de valores, uma boa opção é documentar o formato do retorno deste método e prover um construtor
que receba uma string neste formato para criar o objeto, tornando possível que outros programadores traduzam
facilmente de objeto para string e vice-versa. Porém, ao fazer isso torna-se perigoso mudar este formato, pois outras
classes provavelmente estarão dependendo dele. Qualquer que seja a decisão, é importante deixar claro na
documentação o formato ou então a possibilidade de mudança desta representação.
Independentemente de especificar ou não o formato, é sempre uma boa idéia ter métodos que acessem diretamente toda
a informação retornada pelo método toString, evitando que os outros programadores sejam forçados a obtê-la
através de parse da string, o que reduz a performance e está mais sujeito a erros.

Item 10: Sobrescreva o clone cuidadosamente


A interface Clonable foi criada para indicar que o objeto permite clonagem. Porém, ela falha nesse aspecto, pois não

inclui o método clone e o método da classe Object tem acesso protected , não sendo possível chamar o método
pelo simples fato de ter a interface implementada. O que a Cloneable faz na verdade é determinar o comportamento
da implementação original do método clone: se ela tiver sido implementada, será feita uma cópia campo a campo do
objeto, caso contrário, será lançada uma CloneNotSupportedException.
O contrato para a implementação deste método é bem vago, dando apenas algumas sugestões. De forma geral, a cópia
de um objeto envolve a criação de uma nova instância da classe (sem utilizar o construtor) que pode ser seguida de uma
cópia dos dados internos.
Se você sobrescrever o método clone de uma classe que não seja final, você deve retornar o objeto criado através
de super.clone, pois desta forma as subclasses podem fazer o mesmo e obter um objeto de sua própria classe, que

será retornado pelo método da classe Object em um mecanismo similar ao de encadeamento de construtores.


Utilizando apenas o super.clone, você já obtém um objeto da mesma classe e com todos os campos com valores
iguais aos do original. Mas observe que se algum desses campos fizer referência a um objeto mutável, o clone e a
instância original estarão apontando para o mesmo objeto, que pode ser alterado por qualquer uma delas, ficando sujeito
a resultados inesperados. Para evitar isso, deve ser chamado também o método clone destes objetos. Porém, se
existirem campos finais referindo-se a objetos mutáveis, não será possível copiá-los dessa forma, sendo necessário
remover o atributo final ou aceitar as conseqüências de compartilhar o objeto. Em alguns casos, quando as classes
dos objetos utilizados internamente estão fora do padrão, não adianta apenas chamar o método clone recursivamente,
sendo necessário criar um mecanismo mais elaborado para copiar os objetos internos. Também é necessário tratar os
campos internamente quando eles representam atributos únicos, como um ID ou data de criação. A última abordagem
que pode ser utilizada é criar um novo objeto através da chamada ao super.clone, reinicializar todos os campos e
chamar outros métodos da classe específicos para gerar novamente o estado do objeto.
Outro ponto a ser considerado é que o método clone, da mesma forma que construtores, não deve chamar métodos
não finais, pois caso o método tenha sido sobrescrito ele pode ser chamado antes da subclasse ter tipo a oportunidade de
arrumar seu estado interno no objeto clonado, podendo gerar um estado corrompido.
Em relação a CloneNotSupportedException, classes finais podem omití-la da declaração do método, tornando
mais prático o seu uso. Porém, se a classe for desenhada para ser herdada, é melhor manter a exceção na declaração,
pois assim as subclasses podem optar por não suportar a clonagem de forma mais elegante.
De forma geral, você só é obrigado a prover um método clone próprio se está herdando de uma classe que
implemente a interface Cloneable. Para outros casos, costuma ser mais prático simplesmente criar outros
mecanismos para copiar um objeto, como por exemplo um construtor ou método static factory com esta finalidade, que
apresentam diversas vantagens em relação ao método clone.

Item 11: Considere a implementação de Comparable


Ao contrário dos outros métodos discutidos neste artigo, o método compareTo não é declarado na classe Object, e
sim na interface java.lang.Comparable. Implementando esta interface a classe indica que possiu uma ordem
natural, sendo possível ordenar e comparar objetos.
O contrato do método compareTo manda que ele retorne um inteiro negativo, zero ou um inteiro positivo, se o objeto
for menor, igual ou maior que o objeto especificado, respectivamente. Ele define também que será lançada
uma ClassCastException se  o tipo do objeto for incompatível com o que está sendo comparado. Há ainda
algumas regras relacionadas à função matemática signum, que retorna -1, 0 ou 1 de acordo com o sinal da expressão
dada:

- sgn(x.compareTo(y)) == -sgn(y.compareTo(x)) - isto implica ainda que só poderá ser lançada uma
exceção em um dos lados da igualdade se também for lançada no outro

- x.compareTo(y) > 0 && y.compareTo(z) > 0 implica em x.compareTo(z) > 0

- x.compareTo(y) > 0 implica em sgn(x.compareTo(z)) == sgn(y.compareTo(z) para qualquer z


- É recomendável, mas não necessário, que (x.compareTo(y)==0) == (x.equals(y)). Quando isso não for
verdadeiro deve estar indicado na documentação.
As três primeiras regras implicam nas mesmas propriedades discutidas no método equals: reflexividade, simetria,
transitividade e não-nulidade. Conseqüentemente, as mesmas observações e limitações discutidas no item 7 são
aplicáveis aqui. A implementação também segue uma metodologia semelhante, mas no caso do compareTo é
esperado que sejam lançadas exceções caso ocorram problemas de cast ou argumentos nulos, não sendo necessárias as
verificações destes casos. A comparação de campos referentes a objetos é feita chamando o
método compareTo recursivamente e de campos primitivos utilizando os operadores < e >. Se a classe tem vários
campos significativos, é necessário começar pelo mais significativo e ir prosseguindo até que a comparação resulte em
algo diferente de zero, retornando este resultado. Ao invés de comparar tipos primitivos com os operadores < e > e
então retornar -1 ou +1, você pode calcular a diferença entre os valores e retorná-la diretamente. Porém, esta técnica
deve ser utilizada apenas em casos específicos, observando que o número não seja negativo e que a diferença entre eles
seja menor que (2³¹ - 1), pois estes casos causariam inconsistência no resultado.
Uma classe que viole o contrato poderá causar erros ao ser utilizada em outras classes que dependam de comparação,
tais como TreeSet, TreeMap, Collections e Arrays.
Classes e Interfaces

Este artigo contém orientações sobre o uso de classes e interfaces, tornando-as mais usáveis, robustas e flexíveis.

Item 12: Minimize a acessibilidade de classes e membros


O conceito de encapsulamento significa que um módulo deve ocultar seus dados internos e implementação, separando a
API, que será usada pelos outros módulos, da implementação. Isso permite um maior desacoplamento entre módulos, o
que possibilita que sejam desenvolvidos, trabalhados e usados individualmente. Outras vantagens são o aumento de sua
reusabilidade e a redução dos riscos no desenvolvimento de sistemas maiores.
No Java, a acessibilidade de uma entidade é determinada pelo local em que ela está declarada e os modificadores de
acesso que são utilizados.
A regra geral é que você deve deixar cada classe ou membro o mais inacessível possível.
No caso de classes não-internas, as opções são públicas ou com permissão dentro do pacote (default). Este segundo tipo
de acesso é melhor pois torna a classe parte da implementação do pacote ao invés de parte da API, sendo possível
modificá-la, trocá-la ou eliminá-la em uma próxima versão sem quebrar o código dos clientes da classe. Deixando-a
pública, você fica obrigado a mantê-la e suportá-la para sempre. Se a classe for usada somente por uma única outra
classe, você pode considerar a possibilidade de torná-la uma inner class, reduzindo ainda mais sua acessibilidade.
Porém, apenas reduzir o acesso para o pacote já costuma ser suficiente, pois fica claro que a classe não faz parte da API
exposta para o cliente.
Para membros (campos, métodos, classes aninhadas ou interfaces aninhadas) existem 4 níveis de acesso, nesta ordem:
private (somente dentro da classe), default (qualquer classe dentro do mesmo pacote), protected (subclasses e classes
dentro do mesmo pacote) e public (qualquer lugar).
Depois de definida a API pública, todos os outros membros deveriam ser declarados private. O surgimento de muitos
campos utilizados por outras classes que são parte da implementação do módulo (portanto usando o acesso default) é
uma indicação para considerar a decomposição da classe para obter partes mais desacopladas.
No caso de membros de classes públicas, a alteração de acessibilidade de default para protected é enorme, pois torna o
membro parte da API pública, representando um comprometimento com os usuários da classe.
A única restrição que existe sobre reduzir o acesso é no caso de herança, em que não é permitido que o método
sobrescrito da subclasse tenha uma acessibilidade menor que o da superclasse ou interface.
Campos nunca devem ser públicos, pois isso tira sua liberdade para limitar seus valores, tomar alguma ação quando ele
é alterado ou mudar a sua representação interna. Classes que possuem campos públicos alteráveis nunca são thread-safe.
O único uso válido de campos públicos é o caso de constantes, declaradas como public static final e que referenciam um
tipo primitivo ou objeto imutável. Note que uma array é sempre mutável, portanto é errado deixá-la em um campo deste
tipo, pois seus valores podem ser alterados. Uma solução para esse caso é o uso de uma List:
    private static final Type[] PRIVATE_VALUES;
    public static final List VALUES =
Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES));
Outra opção é ter um método que retorne uma cópia da array.

Item 13: Prefira a imutabilidade


Uma classe imutável é aquela cujas instâncias não podem ser modificadas. A informação é fornecida
quando cada instância é criada e permanece fixa pelo tempo de vida do objeto. Este tipo de classe é mais
fácil de desenhar e implementar, além de ser menos sujeita a erros e mais segura.
Para ter uma classe com essa característica devem ser seguidos os seguintes passos:
1. Não provenha nenhum método que modifique o objeto;
2. Garanta que nenhum método pode ser sobrescrito (marcando a classe como final, os métodos como final
ou deixando apenas um construtor privado);
3. Marque todos os campos como final;
4. Marque todos os campos como private (não necessário mas recomendável);
5. Garanta acesso exclusivo a todos os componentes mutáveis que são referenciados dentro da classe. Crie
cópias de segurança para quando o objeto é exposto ao cliente.
Objetos imutáveis são simples de usar, pois não é necessário nenhum esforço por parte do programador para conferir se
seu estado não foi alterado desde sua criação. Também são thread-safe, não sendo necessária sincronização e
favorecendo o reuso de instâncias. Pode-se inclusive criar métodos static factory para promover este reuso.
Eles também são muito práticos para serem usados como parte de outros objetos, já que o número de variações do
objeto fica controlado. Um caso especial deste princípio é utilizá-los como chaves em mapas e em sets.
A única desvantagem de classes imutáveis é a necessidade de um objeto diferente para cada valor distinto, o que pode
causar um overhead de performance dependendo do custo da criação do objeto. Nos casos em que não é possível um
mecanismo de otimização para trabalhar com os objetos internamente, pode ser criada uma classe auxiliar mutável
usada de forma intermediária, como o caso da StringBuffer para o String.
Algumas classes, como a BigInteger e BigDecimal, não foram protegidas de herança. Portanto, ao utilizar
objetos deste tipo, é necessário checar a classe para garantir que seja imutável ou criar um objeto do tipo correto, da
seguinte forma:
    public void foo(BigInteger b) {
       if (b.getClass() != BigInteger.class)
          b = new BigInteger(b.toByteArray());
       ...
    }
Note que as regras que garantem que um objeto seja imutável não precisam ser seguidas à risca. Um objeto pode mudar
alguns estados internos e mesmo assim ser considerado imutável do ponto de vista externo.
De forma geral, as classes devem ser imutáveis a não ser que exista um bom motivo para que não sejam. Neste último
caso, ainda deve ser limitada a mutabilidade da classe tanto quanto possível, reduzindo o número de estados possíveis e
inicializando completamente os objetos nos construtores.

Item 14: Prefira composição ao invés de herança


A herança entre classes é um meio poderoso de atingir reusabilidade do código, mas leva a um software frágil se for
utilizada inapropriadamente. É seguro utilizá-la dentro de um único pacote, em que o mesmo programador é
responsável pela superclasse e subclasse, ou quando a classe foi desenhada e documentada especificamente para ser
estendida. Já no caso se classes concretas comuns de outros pacotes há maiores riscos.
Herança quebra o encapsulamento, uma vez que a subclasse depende dos detalhes de implementação da sua superclasse
para funcionar corretamente. Se a implementação da superclasse muda de uma versão para outra, a subclasse pode
quebrar sem nem ter sido alterada. Conseqüentemente, a subclasse precisa acompanhar a evolução de sua superclasse.
Alguns exemplos de causas comuns de erro são a alteração da implementação de um método utilizado pela subclasse ou
a inclusão de um novo método que permita alterar o objeto sem passar pela verificação ou tratamento do dado que é
realizado em outro método da subclasse. Também ocorrerão problemas se a superclasse criar um novo método com a
mesma assinatura de um que já foi implementado da subclasse, podendo até impedir sua compilação.
Uma forma de evitar estes problemas é, ao invés de estender a classe, incluir na nova classe um campo privado
referenciando um objeto da classe existente. Desta forma, a classe existente torna-se um componente da classe nova.
Cada método da classe nova pode chamar os métodos da classe antiga através desta instância e retornar o resultado.
Exemplo: criar uma classe com a funcionalidade de um HashSet e que também conte os elementos que estão sendo
adicionados:
public class InstrumentedSet implements Set {
    private final Set s;
    private int addCount = 0;
   
    public InstrumentedSet(Set s) {
        this.s = s;
    }

    public boolean add(Object o) {


        addCount++;
        return s.add(o);
    }

    public boolean addAll(Collection c) {


        addCount += c.size();
        return s.addAll(c);
    }
   
    public int getAddCount() {
        return addCount;
    }

    // Forwarding methods


    public void clear()                      { s.clear();               }
    public boolean contains(Object o)        {return s.contains(o);     }
    public boolean containsAll(Collection c) { return s.containsAll(c); }
    public boolean isEmpty()                 { return s.isEmpty();      }
    public Iterator iterator()               { return s.iterator();     }
    public boolean remove(Object o)          { return s.remove(o);      }
    public boolean removeAll(Collection c)   { return s.removeAll(c);   }
    public boolean retainAll(Collection c)   { return s.retainAll(c);   }
    public int size()                        { return s.size();         }
    public Object[] toArray()                { return s.toArray();      }
    public Object[] toArray(Object[] a)      { return s.toArray(a);     }
    public boolean equals(Object o)          { return s.equals(o);      }
    public int hashCode()                    { return s.hashCode();     }
    public String toString()                 { return s.toString();     }
}
Este tipo de classe é chamado de wrapper, pois envolve outra instância de um Set adicionando funcionalidades.
Também é conhecida pelo design pattern Decorator.
Herança só é apropriada em casos em que a subclasse realmente é um subtipo da superclasse, obedecendo a relação B
"é um" A. Além disso, deve ser observada se a API da classe que estará sendo estendida é apropriada, pois usando
herança você estará propagando as falhas para a nova classe.

Item 15: Especifique e documente para herança, caso contrário, proíba-a


O item anterior avisou sobre os perigos de herdar uma classe que não foi criada com esse propósito. Então quando a
classe for feita para ser herdada ela deve documentar precisamente os efeitos de sobrescrever qualquer método. Devem
ser indicados quais métodos "sobrescrevíveis" (public ou protected e não marcado como final) são chamados, em qual
seqüência e como os resultados de cada chamada afetam o processamento posterior. Exemplo: "This implementation
iterates over the collection looking for the specified element. If it finds the element, it removes the element from the
collection using the iterator's remove method." (retirado da API da AbstractCollection). Nesta documentação do
método remove fica claro que sobrescrever o iterator irá afetar seu comportamento. Pode ser observado aqui que
herança quebra o encapsulamento, uma vez que é necessário expor detalhes da implementação.
Também podem ser criados métodos e campos "protected" para expor parte da implementação que pode ser sobrescrita
nas subclasses, como por exemplo o método removeRange da AbstractList. Neste caso há uma implementação
padrão que é chamada pelo método clear, podendo ser alterada para melhorar a performance do clear em casos
que ela não é apropriada, poupando o programador de ter que reescrever todo o mecanismo.
Outra regra que deve ser seguida é que construtores não devem chamar métodos sobrescrevíveis, diretamente ou
indiretamente. Isso porque o construtor da superclasse é executado antes do da subclasse, e assim o método da subclasse
seria chamado antes de seu construtor ter sido executado, com o objeto em um estado incompleto.
As interfaces Cloneable e Serializable também devem ser evitadas, pois representam uma tarefa extra para o
programador que herdar a classe. No caso delas serem implementadas, a mesma regra dos construtores deve ser
aplicada nos métodos clone e readObject, uma vez que passam por processos similares. No caso
do readResolve e writeReplace, é necessário que os métodos sejam colocados com acesso protected para
ficarem visíveis para as subclasses.
Portanto, no caso de classes que não estão preparadas para herança, o melhor é impedí-la. Isso pode ser atingido
marcando a classe como final ou tornando todos os construtores privados (e substituindo-os por métodos static factory).
Se não for conveniente proibir a herança, o mais prático é garantir que a classe nunca chame um de seus métodos
sobrescrevíveis, pois assim sobrescrever um método não afetará o comportamento de nenhum outro. Para isso, pode-se
mover o conteúdo desses métodos para um método auxiliar privado, deixando o método original apenas com uma
chamada para o método auxiliar. Dentro dos auxiliares, as chamadas a métodos que seriam sobrescrevíveis são
substituídas pela chamada direta ao respectivo auxiliar.

Item 16: Prefira interfaces a classes abstratas


Interfaces e classes abstratas definem um tipo que pode ter múltiplas implementações.
Implementar uma interface nova em uma classe já existente é muito fácil, pois basta declará-la e incluir seus métodos.
Já no caso de classes abstratas, seria necessário colocá-la em algum ponto da hierarquia que fizesse com que as classes
necessárias herdassem dela, forçando todos os descendentes a estendê-la.
Por isso interfaces são ideais para definir tipos secundários, ou seja, um comportamento adicional ao seu tipo primário,
como no caso da interface Comparable. Classes abstratas não são adequadas para essa tarefa pois Java não permite
herança múltipla, e pelo mesmo motivo citado anteriormente não existe um local razoável para colocar este tipo dentro
da hierarquia de classes. Desta forma, as interfaces são mais apropriadas para construir e agregar tipos que não fazem
sentido em uma estrutura hierárquica.
Interfaces também são úteis para permitir a inclusão de funcionalidades através de wrappers, como demonstrado no
exemplo do item 14. Usando apenas classes abstratas, o programador seria obrigado a usar herança, perdendo
flexibilidade.
É possível combinar as qualidades de interfaces e classes provendo um esqueleto de implementação da interface em
uma classe abstrata, pois assim o tipo ainda está definido na interface e o programador que vai utilizá-la pode
opcionalmente utilizar a classe abstrata para auxiliar na implementação. No caso do programador não poder estender a
classe abstrata, ela ainda pode ser útil sendo utilizada internamente em uma instância de inner class anônima, uma
técnica conhecida como herança múltipla simulada. Por convenção, o nome da classe abstrata deve
ser AbstractInterface, onde Interface é o nome da interface que ela implementa. Como essas implementações foram
criadas para serem utilizadas por herança, todas as orientações descritas no item 15 devem ser consideradas aqui.
A única vantagem do uso de classes abstratas ao invés de interfaces é a possibilidade de incluir um novo método sem
quebrar as classes que estão utilizando a classe abstrata, desde que o método seja concreto. No caso de interfaces, as
classes antigas não estariam implementando o novo método e portanto não compilariam mais. Portanto, uma vez que a
interface é liberada e está sendo usada amplamente, é quase impossível alterá-la, e por isso é necessário estudar
cuidadosamente logo de início como ela deve ser.
Assim, a regra geral é que uma interface é a melhor forma de definir um tipo que permite múltiplas implementações.
Uma exceção a essa regra é o caso em que a facilidade de evolução é considerada mais importante do que a
flexibilidade.

Item 17: Utilize interfaces apenas para definir tipos


Quando uma classe implementa uma interface, a interface serve como um tipo que pode ser utilizado para referir-se à
instância da classe. É inapropriado definir uma interface de qualquer outra maneira.
Um exemplo de mau uso é a chamada interface de constantes, que não contém nenhum método, apenas campos static
final exportando uma constante. As classes usando essas constantes implementam a interface para não precisarem
utilizar um nome de classe na referência a cada constante. Porém, isso está expondo a implementação interna da classe e
criando um compromisso futuro de que a classe sempre implemente esta interface, para garantir a compatibilidade
binária de outras classes que passem a depender deste fato. Algumas classes da plataforma Java utilizam esta técnica,
porém são anomalias que devem ser evitadas. Portanto, as constantes devem ficar na própria classe que são utilizadas
ou em uma utility class (item 3).
Nota da tradução: a partir do Java 1.5 poderá ser utilizado o mecanismo de static import.
 
Item 18: Prefira classes internas static às não static
Uma classe aninhada é uma classe definida dentro de outra classe.
Uma classe interna static é a forma mais simples de classe aninhada. Ela é uma classe comum que por acaso foi
declarada dentro de outra classe, tendo acesso a todos os membros static da classe externa, incluindo os privados. Serve
como uma classe auxiliar para a classe externa.
No caso de classes não static, elas ficam associadas a uma instância da classe externa. Desta forma os métodos não
static da classe interna conseguem acessar, através da palavra this, membros de instância da classe externa. A associação
entra a instância da classe externa e interna surge na criação da segunda, através do construtor ou da
expressão enclosingInstance.newMemberClass(args), e não pode ser alterada posteriormente. Um uso
comum para esse tipo de classe é para definir um Adapter, que permite que a instância da classe externa seja vista como
instância de uma classe não relacionada. Exemplo:
public class MySet extends AbstractSet {
    ...
    public Iterator iterator() {
       return new MyIterator();
    }
    private class MyIterator implements Iterator {
       ...
    }
}
Se você declarar uma classe que não requer acesso à instância da classe externa, marque a classe interna como static,
pois assim as instâncias não precisarão manter a referência para a classe externa, o que consome tempo e espaço. Além
disso, só no caso de classes static é possível obter uma instância da classe interna sem possuir uma instância da classe
externa.
Uma classe anônima não possui nome e é instanciada juntamente com sua declaração. Seu comportamento é static
quando declarada em um contexto static e não static quando declarada em um contexto de instância. As classes
anônimas normalmente apenas implementam os métodos já declarados em sua interface ou superclasse e são utilizadas
apenas no ponto em que são instanciadas. Usos comuns são objetos de função, como por exemplo uma instância
de Comparator ou objetos de processos, como Thread, Runnable ou TimerTask. Outros usos são em métodos
static factory ou inicializadores de campos static final.
Classes locais são o tipo menos utilizado. Elas obedecem às mesmas regras de escopo de variáveis locais e só possuem
uma instância exterior se declaradas em um contexto não static. Seu uso é semelhante ao de classes anônimas, mas
podem ter o código reaproveitado em mais de um ponto.

Métodos

Este artigo discute vários aspectos da criação de métodos, visando usabilidade, robustez e flexibilidade.

Item 23: Verifique a validade dos parâmetros


Aplicando o princípio de que um erro deve ser detectado o mais cedo possível depois de ocorrer, valores válidos para os
parâmetros devem ser verificados logo no início do método, evitando erros posteriores mais difíceis de serem
identificados.
Em métodos públicos, documente a restrição e utilize a tag @throws do Javadoc para indicar a exceção que será
lançada se ela for violada, que normalmente
será IllegalArgumentException, IndexOutOfBoundsException ou NullPointerException.
Já no caso de métodos que não são exportados, o autor do pacote deve assegurar que só serão enviados valores corretos
para o método, portanto a verificação deve ser realizada através do mecanismo de assertions ao invés das checagens
normais.
Se a checagem da validade do parâmetro já estiver implícita no processamento do método e for muito custoso realizar a
verificação no início, é aceitável que ela não ocorra. No caso da exceção lançada naturalmente pelo método ser
diferente da que seria utilizada na validação do parâmetro, deve ser feita sua tradução, como será descrito no item 43.
É importante ressaltar que o melhor é escrever seus métodos de forma que consigam lidar de forma geral com os
parâmetros, colocando apenas o mínimo de restrições necessárias para seu funcionamento correto.

Item 24: Crie cópias defensivas quando necessário


Apesar do Java ser uma linguagem segura, não permitindo o acesso direto à memória, é necessário programar de
maneira defensiva, assumindo que os clientes de sua classe tentarão destruir sua  estabilidade, o que pode ocorrer
propositalmente, se alguém tentar quebrar a segurança, ou por um simples engano de um programador.
No caso de construtores que recebem objetos mutáveis como parâmetro, é essencial criar uma cópia do mesmo e utilizá-
la como componente da classe, evitando as conseqüências do objeto original ser alterado. Note que a validação dos
parâmetros deve ser feita já na cópia, não criando uma janela de vulnerabilidade entre o tempo que são feitas a
validação e a cópia. Além disso, não utilize o método clone para fazer a cópia caso o parâmetro seja de uma classe não
final, e sim crie um novo objeto que contenha os mesmos valores. Outra precaução que deve ser tomada é não retornar
os objetos internos mutáveis em métodos accessors, e sim uma cópia dos mesmos.
Claro que se for previsto que a classe controle o objeto fornecido por um cliente, não é feita a cópia defensiva, e sim
utilizado o original. Mas isso deve estar explícito na documentação e deve haver confiança entre as classes que estão
compartilhando o objeto.

Item 25: Planeje a assinatura dos métodos cuidadosamente


Alguns itens que devem ser considerados na criação de métodos:
- Escolha os nomes cuidadosamente. Obedeça às convenções, e deixe os nomes compreensíveis e consistentes com os
outros nomes no mesmo pacote (e da API do Java);
- Não inclua métodos desnecessários, pois seu excesso torna a classe mais difícil de aprender, usar, documentar, testar e
manter. Para cada ação suportada pelo seu tipo (incluindo interfaces), inclua um método funcional;
- Evite listas de parâmetros longas. Muitos parâmetros confundem o programador, especialmente se alguns forem do
mesmo tipo. Soluções para esse caso são dividir o método ou utilizar uma classe auxiliar para armazenar os parâmetros;
- Para os tipos dos parâmetros, prefira interfaces a classes, dando mais flexibilidade ao método;
- Utilize objetos de função apenas quando houver uma razão legítima, como no caso da implementação de um Strategy
ou Visitor;

Item 26: Utilize overloading (sobrecarga) de forma adequada


A escolha de qual método overloaded será utilizado é feita em tempo de compilação. Por exemplo, tendo a seguinte
array:
Collection[] tests = new Collection[] {new HashSet(), new ArrayList(), new
HashMap().values()};
E fazendo um loop em que é chamado um método classify que está sobrecarregado para
receber Set, List ou Collection, para as três instâncias ele irá chamar o de Collection, ainda que em tempo
de execução o tipo seja diferente É a idéia oposta dos métodos overriden (herdados), que são aplicados de acordo com o
tipo da instância e não da variável que a declara. Portanto, evite overloads confusos, que aumentam a chance de um
programador usando a classe tomar uma decisão errada. A regra mais segura é nunca exportar dois métodos overloaded
com o mesmo número de parâmetros. Se isso não for possível, que pelo menos os tipos sejam mutuamente exclusivos
por cast. E por fim, se for possível receber o mesmo objeto em 2 métodos diferentes, eles devem ter o mesmo
comportamento para este parâmetro.

Item 27: Retorne arrays de tamanho zero, não nulls


Não há motivo para retornar null em um método que retorna um tipo de array. É melhor retornar uma array de tamanho
zero, evitando erros no método que recebe esse valor caso ele esqueça de testar e tratar o null.

Item 28: Escreva comentários de documentação (javadoc) para todos os elementos expostos da API
O javadoc provê um meio efetivo de documentar sua API e mantê-la sempre atualizada, devendo ser usado amplamente.
Todas as classes, interfaces, construtores, métodos ou declaração de campos exportados devem ser precedidos por um
comentário.
A documentação de um método deve descrever sucintamente o contrato entre o método e seu cliente. Ela deve dizer o
que o método faz ao invés de como faz. Deve enumerar as pré-condições (através da @throws, @param ou descrição),
pós-condições e efeitos colaterais. Também deve estar indicada a segurança em relação a threads. Devem ser incluídas
as tags @param para cada parâmetro, @return se o tipo de retorno não for void e @throws para as exceções que
podem ser lançadas (checked ou unchecked), seguidas pela descrição do parâmetro ou retorno ou da condição em que a
exceção é lançada.
É permitido usar HTML para uma melhor formatação do texto no javadoc.
A primeira sentença de cada comentário de javadoc é a descrição resumida do elemento. Tenha cuidado para que ela não
contenha um ponto (por exemplo em uma abreviação), pois isso quebraria a sentença antecipadamente. Se for
necessário ele deve ser substituído por "&#46;"
Há um mecanismo de reuso de documentação automática caso ela não seja fornecida para um método, utilizado no caso
de interfaces ou superclasses. Ele é prático para evitar manter várias cópias de um mesmo comentário, mas não permite
que seja alterado, sendo necessário reescrever todo comentário caso o do método da classe em questão seja mais
especializado.
Algumas vezes a API é muito complexa e a documentação gerada pelo javadoc não é suficiente, sendo necessário
incluir um link para uma documentação mais completa.

Programação em geral

Este artigo é dedicado aos detalhes práticos da linguagem Java. Ele discute o tratamento de variáveis locais, o uso de
bibliotecas, o uso dos tipos de dados, e dois recursos extralingüísticos: reflection e métodos nativos. Por fim, ele discute
otimizações e convenções de nomes.

Item 29: Minimize o escopo de variáveis locais


Minimizando o escopo de variáveis locais você aumenta a legibilidade do código e reduz a probabilidade de erros.
A programação Java, diferentemente do C, permite que você declare variáveis em qualquer lugar que é permitida uma
sentença de código. Assim, a melhor forma de minimizar o escopo de uma variável é declará-la onde ela for utilizada
pela primeira vez. Desta forma fica mais fácil para o leitor do código lembrar qual é seu tipo e valor inicial, e caso após
algum tempo a variável não seja mais utilizada é menos provável que sua declaração seja esquecida no código.
Portanto, quase todas as declarações de variáveis locais devem ser seguidas de sua inicialização. Uma exceção é o caso
de inicialização dentro de um bloco try/catch, em que você pode declarar a variável fora do bloco para que ela
possa ser acessada pelo resto do método.
No caso de loops, prefira o for ao while, já que o primeiro permite que seja declarada uma variável que é usada no
corpo do loop e também na inicialização, teste e incrementação do valor, enquanto no segundo seria necessário declarar
a variável fora do bloco e ela poderia ser reutilizada por engano posteriormente.
Exemplo:
for (int i = 0, n = list.size(); i < n; i++) {
  doSomething(list.get(i));
}
O código acima é uma boa implementação para listas de acesso aleatório, como ArrayList e Vector, pois é
executado mais rápido do que com a utilização de um Iterator. Além disso, são declaradas duas variáveis com o
escopo correto.
A última técnica para minimizar o escopo de variáveis é deixar os métodos pequenos e focados. Se você combina duas
atividades em um único método, uma variável relevante para uma atividade pode ficar no escopo da parte do código que
executa outra atividade, portanto o ideal é separar o método em dois.

Item 30: Conheça e utilize as bibliotecas


Utilizando as bibliotecas padrão, você é beneficiado pelo conhecimento dos experts que as escreveram e a experiência
daqueles que as utilizaram antes de você. Por exemplo, muitos programadores geram números aleatórios entre zero e
um determinado limite através do seguinte código:
static Random rnd = new Random();
static int random(int n) {
    return Math.abs(rnd.nextInt()) % n;
}
Este exemplo parece funcionar, porém possui três falhas graves: se n for uma potência de dois pequena, a seqüência de
números gerados será repetida em pouco tempo; se n não for uma potência de dois, alguns números serão retornados
com maior freqüência que outros; e se o número gerado for Integer.MIN_VALUE e n não for uma potência de 2 o
retorno do método será um número negativo. Para corrigir esses problemas seria necessário conhecimentos avançados
de matemática, mas este trabalho já foi realizado para você pela pessoa que criou o
método Random.nextInt(int) e testado por milhares de pessoas durante vários anos.
A segunda vantagem de utilizar as bibliotecas é não precisar perder tempo escrevendo soluções que não estão
diretamente relacionadas ao seu trabalho.
Outra vantagem é que a performance das bibliotecas padrão tendem a aumentar no decorrer do tempo, sem nenhum
esforço da sua parte. Como elas são utilizadas por muitas pessoas e em benchmarks da indústria, as organizações que as
criam são incentivadas a torná-las mais rápidas.
E a maior vantagem do uso das bibliotecas é destacar o seu código, tornando-o mais legível, fácil de manter e
reutilizável por outros desenvolvedores.
Várias funcionalidades são adicionadas à biblioteca em cada release do Java, e vale a pena estar familiarizado com elas
para evitar criar código desnecessariamente. Todos os programadores devem conhecer pelo menos o conteúdo
do java.lang, java.util e java.io. As outras podem ser estudadas conforme surgir a necessidade. Uma das
funcionalidades que merecem atenção especial é a framework de Collections.

Item 31: Evite float e double se forem necessárias respostas exatas


Os tipos float e double utilizam aritimética binária de ponto flutuante e foram criados principalmente para
cálculos científicos e de engenharia, que requerem aproximações precisas rápidas sobre determinadas magnitudes. Eles
não fornecem resultados exatos e devem ser evitados em casos como cálculos monetários.
Um exemplo é o código System.out.println(1.03 - 0.42); que gera como
resultado 0.6100000000000001.
Para cálculos monetários o correto é utilizar int, long ou BigDecimal. Utilizar os primitivos normalmente é mais
rápido e mais prático, mas aí você deve controlar as casas decimais diretamente, por exemplo, utilizando centavos ao
invés de reais. Se você preferir que o computador faça este trabalho, utilize o BigDecimal. Se as quantidades não
ultrapassarem nove dígitos, você pode usar int; até dezoito dígitos, utilize long; e acima disto será necessário
o BigDecimal.

Item 32: Evite Strings quando outros tipos forem mais apropriados


Strings foram criadas para representar texto. Mas quando um dado vem através de um arquivo, da rede ou input do
teclado normalmente também está na forma de string. Se esse dado for numérico, deve ser traduzido para o formato
numérico apropriado; se for a resposta de uma pergunta do tipo sim ou não, para boolean; e se representa um objeto,
deve ser convertido para ele, mesmo que seja necessário criar uma nova classe.
Strings também não são apropriados para tipos "enumerados", agregados (quando são concatenados valores para
representar um objeto) ou como chaves para permitir acesso a certa funcionalidade.

Item 33: Cuidado com a performance de concatenação de strings


O operador de concatenação de strings (+) é conveniente para combinar algumas strings em uma única string. Mas
usando este operador repetidamente para concatenar n strings requer um tempo quadrático em n, pois strings são
imutáveis. Para alcançar uma performance aceitável, utilize a classe StringBuffer para guardar o conteúdo em
construção. Através desta classe a performance é linear, apresentando uma diferença dramática quando o número de
concatenações é elevado. Mesmo que o tamanho do StringBuffer não esteja otimizado para receber todo o
conteúdo, a performance é bastante superior.
Portanto, não utilize o operador + para combinar mais do que algumas strings se a performance for importante. Utilize o
método append da classe StringBuffer ou trabalhe com uma array de caracteres.

Item 34: Refira-se aos objetos através de suas interfaces


O item 25 dá a dica que você deve utilizar interfaces ao invés de classes como tipos de parâmetros. De forma mais
geral, você deve preferir o uso de interfaces ao invés de classes para referir-se a objetos. Portanto, se existir uma
interface apropriada, parâmetros, valores de retorno, variáveis e campos devem ser declarados do tipo da interface. O
único momento em que você precisa referir-se à classe do objeto é durante sua criação.
Assim, seu programa fica mais flexível, bastando alterar a classe na linha em que é chamado o construtor do objeto.
Porém, se a classe original prover alguma funcionalidade especial em seus métodos que não é requerida pelo contrato
da interface, é necessário tomar cuidado com o impacto disso no seu código. Por exemplo, você pode declarar List
subscribers = new Vector() e depois mudar der Vector para ArrayList. Ainda que ambos possuam os
métodos de List, o primeiro é sincronizado, sendo incorreto fazer a substituição se o seu código depender disso.
Mesmo que hoje não exista previsão da necessidade de alterar a classe, futuramente pode surgir uma nova classe que
desempenhe a mesma tarefa melhor ou de forma mais rápida, ficando fácil aproveitá-la caso seu objeto esteja
referenciado pela interface.
Por outro lado, caso não exista uma interface apropriada, é correto referir-se ao objeto pela classe. Um exemplo disso
são as classes de valores, como String e BigInteger. Já no caso de frameworks, muitas vezes algumas classes têm
como tipo fundamental classes abstratas ao invés de interfaces, portanto o ideal é referenciar o objeto pela classe base. E
o último caso em que uma classe é usada diretamente ao invés de sua interface é quando seu código depende de
métodos que não estão declarados na interface.

Item 35: Prefira interfaces ao invés de reflection (reflexão)


O mecanismo de reflection, do pacote java.lang.reflect, oferece acesso programático a construtores, métodos e
campos de uma classe. Desta forma é possível que uma classe utilize outra, mesmo que a última ainda não existisse
quando a primeira foi compilada. É um mecanismo poderoso, mas tem desvantagens:
- você perde o benefício das verificações de tipo que acontecem em tempo de compilação, incluindo checagem de
exceções;
- o código necessário para realizar acesso reflexivo é mais confuso que o tradicional;
- há um impacto de performance (no Java 1.3 era 40 vezes mais lento chamar um método por reflexão do que
diretamente, e no Java 1.4 ainda gasta o dobro do tempo do acesso normal).
A reflection foi inserida no Java para ferramentas de construção baseadas em componentes. Portanto, deve ser utilizada
em tempo de design. Objetos não devem ser acessados por reflexão em uma aplicação normal em tempo de execução.
Apenas algumas aplicações mais sofisticadas são exceções a esta regra, como navegadores de classes, inspetores de
objetos, ferramentas de análise de código, sistemas interpretados e RPC.
Você consegue obter a maior parte do benefício de reflection incorrendo apenas um pequeno custo utilizando-a de
forma limitada. Por exemplo, se a classe não está disponível em tempo de compilação mas existe uma interface ou
superclasse dela que está disponível, a instância é criada por reflexão mas os métodos são acessados normalmente
através da superclasse ou interface. Se o construtor não tiver parâmetros não é necessário nem usar
o java.lang.reflect, pois o Class.newInstance provê a funcionalidade necessária.

Item 36: Use métodos nativos moderadamente


O JNI (Java Native Interface) permite que aplicações Java chamem métodos nativos. Eles são utilizados para obter-se
acesso a características específicas da plataforma, como registros e trava de arquivos; para ter acesso a bibliotecas de
código legado, muitas vezes responsáveis pelo acesso a dados legados; e para melhorar a performance em partes críticas
da aplicação. Muitas vezes o Java provê um mecanismo de acesso mesmo para esses casos, como o
pacote java.util.prefs para a funcionalidade de registro e a API JDBC para acesso a bancos de dados legados.
Desde a release 1.3 do Java, também é raramente aconselhado usar métodos nativos para melhorar a performance, pois
para a maioria das tarefas já é possível atingir uma performance comparável ao de métodos nativos utilizando apenas os
recursos do Java. Por exemplo, a BigInteger inicialmente era implementada utilizando uma biblioteca aritimética
de multiprecisão rápida escrita em C. A partir da versão 1.3 ela foi reescrita totalmente em Java e está mais rápida que a
antiga.
O uso de métodos nativos tem sérias desvantagens, pois não são seguros (estão sujeitos a corrupção de memória), são
dependentes de plataforma (perdendo portabilidade) e o processo de entrar e sair de métodos nativos causa impacto na
performance. Além disso, são mais difíceis de escrever e entender.

Item 37: Otimize moderadamente


Existem três aforismos sobre otimização que todos deveriam saber:
"Mais pecados computacionais são cometidos em nome da eficiência (sem necessariamente alcançá-la) do
que por qualquer outra razão - incluindo estupidez cega."
- Willian A. Wulf
"Nós deveríamos esquecer as pequenas eficiências 97% das vezes: otimização prematura é a raiz de todo
mal"
- Donald E. Knuth
"Nós seguimos duas regras a respeito de otimização:
Regra 1. Não faça
Regra 2 (apenas para experts). Não faça ainda - ou seja, não até que você tenha uma solução
perfeitamente clara e não otimizada"
- M. A. Jackson
Todos esses aforismos são 20 anos mais antigos que o Java e dizem uma grande verdade sobre otimização: é mais
provável piorar do que melhorar o código, especialmente se você otimizar de forma prematura. Durante o processo você
pode produzir software que não é rápido nem funciona corretamente e é difícil de corrigir. Esforce-se em escrever bons
programas, não programas rápidos. Se a arquitetura for boa, será fácil otimizá-lo, pois as informações estarão
encapsuladas e a alteração de um módulo individual não afetará o resto do sistema. Porém, é necessário pensar sobre a
performance durante o processo de planejamento do sistema, pois uma arquitetura que limita a performance fica difícil
de consertar depois que o sistema está pronto. Desta forma, os componentes que especificam a interação entre módulos
e com o mundo exterior devem ser planejados cuidadosamente.
Considere as conseqüências das suas decisões da API. Deixar um tipo público mutável, por exemplo, pode requerer
grande quantidade de cópia defensiva desnecessariamente. Usar herança em uma classe pública em que composição
seria apropriada prende para sempre a classe a sua superclasse, colocando um limite artificial na performance da
subclasse. E usar a classe de implementação ao invés da interface em uma API prende à implementação específica,
sendo que implementações mais rápidas podem surgir futuramente. De forma geral, um bom planejamento da API é
consistente com boa performance. Porém, deformar a API para melhorar a performance é uma má idéia.
Quando o sistema já está funcionando e você decide otimizá-lo, outra dica é medir a performance antes e depois de uma
otimização. Em média um programa gasta 80% do tempo em 20% do código, então não adianta perder tempo
otimizando uma parte do código que não é o gargalo. Ferramentas de profiling ajudam a identificar qual parte precisa de
mais otimização. No caso do Java é particularmente importante medir os efeitos de uma otimização pois ele não tem um
modelo de performance bem definido, não ficando bem claro os custos de cada operação primitiva e ocorrendo
diferenças significativas de performance em diferentes JVMs.
Outro fator importante durante a otimização é a escolha do algoritmo, pois a otimização em baixo nível não consegue
compensar um algoritmo inadequado.

Item 38: Siga as convenções de nomes


Violações de regras de nomes aumentam a dificuldade de entender a API e manter o código, confundindo e irritando
outros programadores.
Nomes de pacotes devem ser hierárquicos, com suas partes separadas por pontos. As partes devem ser letras minúsculas
e (raramente) números. O nome de qualquer pacote que vai ser utilizado fora de sua organização deve começar com o
domínio de internet dela ao contrário (ex.: com.sun). As bibliotecas padrão e pacotes opcionais, cujos nomes começam
com java e javax, são exceções a esta regra e não são permitidos esses nomes por usuários. O resto do nome do pacote
deve consistir em uma ou mais partes que descrevem o pacote.. As partes devem ser curtas, normalmente com oito ou
menos caracteres. Abreviações significativas são encorajadas, por exemplo util no lugar de utilities. Acrônimos também
são aceitos, como awt. As partes  geralmente devem ser uma palavra única ou abreviação. Muitos pacotes têm nomes
com apenas uma parte adicionalmente ao nome do domínio. Partes adicionais são apropriadas para pacotes maiores,
cujo tamanho exige que sejam divididos em uma hierarquia informal (não há suporte lingüístico para uma hierarquia
verdadeira).
Nomes de classes e interfaces devem consistir em uma ou mais palavras (substantivos ou frases substantivas), com a
primeira letra de cada palavra em letra maiúscula. Abreviações devem ser evitadas, exceto por acrônimos e algumas
abreviações comuns como max e min. Não há um consenso sobre acrônimos ficarem inteiros em letra maiúscula ou
apenas a primeira letra. Apesar da primeira abordagem ser mais comum, fica mais legível deixar apenas a primeira letra
maiúscula, especialmente quando vários acrônimos ocorrem juntos.
Nomes de métodos e campos devem seguir as mesmas convenções tipográficas de classes e interfaces, exceto que a
primeira letra deve ser minúscula, mesmo que seja um acrônimo. A única exceção é o caso de constantes, que devem ter

seus nomes todos em maiúsculas com as palavras separadas por underscore.  Os métodos normalmente utilizam verbos
ou frases verbais como nome. Métodos que retornem um boleano usam o prefixo "is" seguido de um adjetivo ou
substantivo, ex: isDigit. Já os que retornam o resultado de uma função ou um atributo do objeto são nomeados com
substantivos com ou sem o prefixo "get". O uso do get só é obrigatório se a classe for um JavaBean. Havendo um
método para atribuir o valor a um atributo do objeto utiliza-se o prefixo set. Métodos que convertem o tipo de um objeto
são chamados toTipo. Métodos que retornam uma "visualização" de um tipo diferente do objeto recebido normalmente
são chamados de asTipo. Métodos que retornam um primitivo com o mesmo valor do objeto em que foram chamados
recebem o nome de tipoValue. Nomes comuns para static factories são valueOf e getInstance.
Variáveis locais seguem as mesmas convenções de métodos e campos, mas abreviações são permitidas.
Exemplos:
Pacote: com.sun.medialib, com.sun.jdi.event
Classe ou interface: Timer, TimerTask, KeyFactorySpi, HttpServlet
Método ou campo: remove, ensureCapacity, getCrc
Valor constante: VALUES, NEGATIVE_INIFINITY
Variáveis locais: i, xref, houseNumber

Exceções

Quando bem usadas, as exceções podem melhorar a legibilidade, confiabilidade e sustentabilidade do código. Mas
quando usadas incorretamente, podem causar o efeito oposto. Este artigo fornece orientações sobre seu uso.
Item 39: Use exceções apenas para condições excepcionais
As exceções, como o próprio nome indica, devem ser usadas apenas em condições excepcionais; nunca para controle de
fluxo comum.
Seu uso costuma ter um impacto negativo na performance, uma vez que é custoso criar, lançar e pegar uma exceção,
além de atrapalhar as otimizações feitas automaticamente pela JVM.
O uso de exceções para controle de fluxo também mascara o objetivo do código e aumenta a probabilidade de um bug
passar desapercebido, ficando mais difícil encontrar a causa do erro posteriormente.
Lembre-se disso ao criar sua API, para não forçar o cliente da classe a usar exceções desnecessariamente. Uma classe
que possui um método que depende de um certo estado normalmente deve incluir também um método separado para
testar esse estado, indicando se é apropriado ou não chamar o primeiro método. Um exemplo desta técnica é a
classe Iterator, que possui os métodos next e hasNext.Outra alternativa é que o método que depende do estado
retorne um valor especial, como null, quando chamado em um objeto em estado inapropriado. Esta segunda
abordagem é a mais adequada para casos em que o estado do objeto pode mudar no intervalo entre a chamada do
método teste e o método que depende do estado, ou que a performance seja crítica. Em outras situações, é melhor o
método teste, pois é mais legível e é mais fácil detectar e corrigir um uso inapropriado.

Item 40: Use checked exceptions para condições  recuperáveis e runtime exceptions para erros de programação
Há três tipos de throwables na linguagem Java: checked exceptions, runtime exceptions e errors.
Utilize checked exceptions para condições em que se espera que a classe que chamou o método possa recuperar-se.
O lançamento da exceção é um indício ao usuário da API que aquela determinada condição é um dos retornos possíveis
da chamada do método e assim você força que ele insira código para tratar a exceção ou propagá-la.
Existem dois tipos de unchecked throwables: runtime exceptions e errors. Eles são idênticos em seus comportamentos:
ambos são throwables que não precisam ser tratados. Quando um programa lança algum deles, normalmente é um caso
impossível de recuperar; continuar executando apenas pioraria a situação. Portanto, se o programa não incluir um bloco
catch para tratar esta exceção, a thread em que ela acontece é interrompida mostrando uma mensagem de erro.
Utilize runtime exceptions para indicar erros de programação. Normalmente são usadas em violações de pré-condições,
ou seja, quando o cliente não adere ao contrato estabelecido na API.
Já os erros são utilizados pela JVM para indicar uma deficiência de recursos, falhas ou outras condições que tornam
impossível que a execução continue. Ainda que isso não seja determinado na especificação, é uma convenção bem
aceita, portanto você não deve criar novas classes de erros; utilize subclasses de RuntimeException para todos os
unchecked throwables.
É possível definir throwables que não são subclasses de Exception, RuntimeException ou Error. Seu
comportamento fica igual ao de checked exceptions, portanto é melhor herdar da classe Exception, que é o mais
comum.
Resumindo, se você acha que a situação provavelmente é recuperável, utilize checked exception, caso contrário, utilize
runtime exception.
Além disso, ao criar novas classes de exceções, lembre-se de que é possível incluir métodos para retornar informações
para o usuário, o que é uma prática muito melhor do que simplesmente obrigá-lo a obter os dados dentro da String que a
representa. Isso é especialmente importante nas checked exceptions, para que a causa do problema seja identificada
mais facilmente e contornada.

Item 41: Evite o uso desnecessário de checked exceptions


As checked exceptions tornam o código mais confiável ao forçar os programadores a lidar com condições excepcionais.
Porém, seu uso excessivo faz com que fique mais desagradável trabalhar com a API, já que é necessário incluir código
para tratar ou propagar cada exceção lançada.
Esse trabalho é justificável apenas se a condição excepcional não pode ser prevenida com um uso apropriado da API e o
programador pode tomar alguma ação útil quando confrontado com esta exceção. Se o melhor que ele pode fazer é
um e.printStackTrace(),  melhor utilizar uma runtime exception.
O fardo é ainda maior quando é o caso de uma única checked exception lançada por um método. Quando já existem
outras, a chamada ao método já estará dentro de um bloco try. Então bastaria colocar um novo bloco catch. Mas
quando uma exceção é sozinha responsável por obrigar a existência bloco try, deve ser considerado cuidadosamente
se vale mesmo a pena incluí-la na assinatura do método.
Uma técnica para transformar uma checked exception em runtime exception é quebrar o método em duas partes, a
primeira retornando um boleano que indica se a exceção seria lançada. Exemplo:
// Com checked exception
try {
    obj.actions(args);
} catch(TheCheckedException e) {
    // trata a condição
}

//Com teste de estado e runtime exception


if (obj.actionPermitted(args)) {
    obj.action(args);
} else {
    //trata a condição
}
Nem sempre esta transformação é apropriada, conforme discutido no item 39. Mas quando é, torna a API mais
agradável de ser utilizada. Ainda que a segunda seqüência para chamada do método não seja muito melhor que a
primeira, a API resultante é mais flexível. Em casos que o programador tem certeza de que a chamada será bem
sucedida ou não se importa de deixar a thread terminar caso a chamada falhe, bastaria chamar obj.action(args).

Item 42: Prefira o uso de exceções padrão


O reuso de código é uma boa prática também no caso de exceções. Torna a API mais fácil de aprender e usar porque
segue convenções que os programadores já estão familiarizados. Um menor número de classes de exceção também
incorre em menos memória necessária e menos tempo gasto carregando classes.
A exceção mais comumente reutilizada é a IllegalArgumentException. Ela deve ser lançada quando  o método
é chamado com um argumento cujo valor seja inapropriado. Outra exceção é a IllegalStateException, usada
quando a chamada ao método é ilegal naquela situação, devido ao estado do objeto em que é chamado. De forma geral,
qualquer chamada errada a um método cai em algum desses casos, mas outras exceções são utilizadas em certos tipos
de estados ou argumentos ilegais. Por exemplo, se é passado um null onde ele não é aceito, é lançada
a NullPointerException. Já no caso de um índice de uma seqüência que está fora dos limites, usa-se
a IndexOutOfBoundsException. Outra exceção que vale a pena conhecer é
a ConcurrentModificationexception, lançada quando há modificação simultânea em um objeto que deveria
ser acessado apenas por uma thread. E a última que iremos mencionar é a UnsupportedOperationException,
usada raramente, em casos que a classe não implementa alguns métodos opcionais definidos na interface.
Existem ainda muitas outras exceções utilizadas em casos mais específicos. É importante lembrar que o reuso precisa
estar baseado na semântica, ou seja, compatível com a documentação da exceção, não apenas em seu nome. Além disso,
você pode fazer uma subclasse de qualquer exceção caso queira adicionar um pouco mais de informação sobre o
problema.

Item 43: Lance exceções apropriadas à abstração


Camadas mais altas devem tratar as exceções recebidas e, caso apropriado, em seu lugar lançar exceções mais
adequadas em termos da abstração de alto nível. Exemplo:
try {
    //código
} catch(LowerLevelException e) {
    throw new HigherLevelException(...)
}
Uma forma especial de tradução de exceções, chamada encadeamento de exceções, é apropriada em casos que a
exceção de baixo nível pode ser útil para debugar a situação que causou a exceção. Neste caso a exceção de baixo nível
é armazenada como um objeto dentro da nova exceção lançada. A partir do Java 1.4 esse mecanismo é suportado
diretamente pelo Throwable, sendo possível passar a exceção pelo construtor e dispensando a criação do campo e de
um método getCause para obter o objeto.
Apesar da tradução de exceções ser superior à simples propagação das mesmas, este mecanismo não deve ser usado em
excesso. O melhor é que a classe tente tomar as ações necessárias relativas àquela exceção, dispensando as camadas
superiores de terem que tratá-la de alguma forma.

Item 44: Documente todas as exceções lançadas por cada método


A descrição das exceções lançadas é uma parte importante da documentação necessária para utilizar um método
corretamente. Sempre declare as checked exceptions individualmente, e documente as condições exatas em que cada
uma é lançada usando a tag @throws do Javadoc. Não utilize simplesmente uma superclasse comum às exceções
lançadas, pois isto torna obscuro o seu uso. As runtime exceptions, apesar de não precisarem ser declaradas, também
devem estar documentadas para que o programador esteja familiarizado com os erros mais prováveis e possa evitá-los.
Isto serve para a documentação das pré-condições de execução de um método. No caso de interfaces isto é
particularmente importante, pois serve como o contrato geral e permite um comportamento comum entre suas múltiplas
implementações.
Use a tag @throws do Javadoc para documentar cada runtime exception que o método pode lançar, mas não utilize a
palavra throws para incluí-las na declaração do método. Assim fica mais fácil para o programador que está usando a
API identificar visualmente quais são suas responsabilidades no uso daquele método quando lê a documentação gerada
pelo Javadoc.
Se uma exceção é lançada por diversos métodos da classe pela mesma razão, é aceitável documentá-la nos comentários
da documentação da classe ao invés de individualmente em cada método.

Item 45: Inclua informações sobre a falha em mensagens de detalhe


O stack trace de uma exceção é sua representação em String, resultado do método toString. Normalmente é
constituída pelo nome da classe seguido de uma mensagem de detalhe. Isto deve retornar toda informação possível para
ajudar a pessoa a diagnosticar a causa do problema, contendo os valores de todos os parâmetros e campos que
"contribuíram para a exceção". Porém, lembre-se de que o stack trace será analisado por um programador junto com o
código do método, portanto é desnecessário incluir informações facilmente observadas lendo o código diretamente e o
conteúdo é bem mais importante do que a forma como ele é apresentado.
Para garantir que a exceção tratará todas as informações necessárias, seus construtores devem obrigar a inclusão das
mesmas ao criar o objeto. Como sugerido no item 40, podem ser incluídos métodos accessors para o programador obter
individualmente estes valores.

Item 46: Tente garantir atomicidade das falhas


Um método cuja chamada não deu certo deve manter o objeto no estado em que ele estava antes da chamada ao método.
Uma forma de conseguir isso é através de objetos imutáveis (item 13). No caso de objetos mutáveis, o melhor é checar
os parâmetros antes de tentar realizar a operação (item 23), fazendo com que as exceções sejam lançadas antes de
iniciar qualquer modificação do objeto. No caso de não ser possível checar os parâmetros, deve-se tentar realizar as
operações que podem falhar antes daquelas que modificam o objeto. A terceira abordagem para atingir atomicidade das
falhas é incluir código que volte o objeto ao estado anterior à operação. E a última forma é efetuar as operações em uma
cópia temporária do objeto e substituir o conteúdo do objeto original com o da cópia após completar a operação.
No caso de objetos sendo acessados por várias threads ou de erros (ao invés de exceptions), dificilmente será possível
atingir a atomicidade. Há também casos em que o aumento de complexidade não compensa, e então deve estar
documentado claramente na API em que estado ficará o objeto caso a operação falhe.

Item 47: Não ignore exceções


É fácil simplesmente ignorar uma exceção incluindo um bloco catch vazio. Porém, isso vai contra os objetivos do
mecanismo de exceções do Java; é como ignorar um alarme de incêndio e ainda desligá-lo para que mais ninguém tenha
a chance de saber que algo está pegando fogo. Portanto, no mínimo deve haver um comentário dentro do bloco catch
explicando porque a exceção está sendo ignorada.

Threads

Threads permitem que várias atividades aconteçam simultaneamente em um mesmo programa. Como a


programação multitarefa é mais complicada, caso exista uma classe de biblioteca pronta que poupe o
trabalho da programação em baixo nível, utilize-a. Porém, mesmo usando essas bibliotecas onde aplicável,
ainda será necessário trabalhar com código para multitarefa de vez em quando.

Item 48: Sincronize o acesso a dados mutáveis compartilhados


A palavra synchronized garante que apenas uma thread irá executar certa parte do código de cada vez.
Isso evita que um objeto seja observado em um estado inconsistente, enquanto ainda está sendo
modificado por outra thread. Assim, o objeto é criado em um estado consistente e travado pelos métodos
que o acessam. Esses métodos observam o estado e opcionalmente causam uma transição de estado,
transformando o objeto de um estado consistente para outro. O uso apropriado de sincronização garante
que nenhum método irá observar o objeto em estado inconsistente.
Além disso, a sincronização garante que um objeto progrida de um estado consistente para outro através de
uma seqüência ordenada de transição de estados que parecem executar seqüencialmente. Cada thread
entrando em um bloco sincronizado vê os efeitos de todas as transições de estado anteriores controladas
pela mesma trava.
A linguagem garante que ler ou escrever em uma variável (exceto long ou double) é um processo atômico.
Portanto, o valor retornado ao ler uma variável é, com certeza, o valor exato que foi gravado por alguma
thread, ainda que threads múltiplas modifiquem a variável ao mesmo tempo sem sincronização. Porém,
mesmo assim a sincronização é importante para garantir uma comunicação confiável entre as threads e
conseguir a exclusão mútua. Veja como exemplo o seguinte código:
private static int nextSerialNumber = 0;
public static int generateSerialNumber() {
    return nextSerialNumber++;
}
O método acima não é confiável sem a sincronização, pois o operador de incremento (++) lê e escreve no
campo nextSerialNumber, portanto não é atômico. As operações de ler e escrever são independentes,
executadas em seqüência. Múltiplas threads simultâneas podem observar o campo com o mesmo valor e
portanto retornar o mesmo número serial. Além disso, é possível que uma thread chame o
método generateSerialNumber repetidamente, obtendo uma seqüência de números seriais de zero até
n, e depois disso uma outra thread chame o generateSerialNumber e obtenha um número serial zero
novamente, pois sem a sincronização a segunda thread pode não ver as atualizações feitas pela anterior
devido ao modelo de memória do Java. Para consertar o código basta adicionar o
modificador synchronized na declaração do método.
Para finalizar uma thread é recomendado que ela verifique regularmente uma variável (normalmente
um booleano ou uma referência a um objeto) cujo valor indique quando ela deve parar. Apesar da leitura da
variável ser atômica, também é necessária a sincronização neste caso, pois caso contrário não é garantido
quando a thread irá ver a mudança no valor da variável causada por outra thread. Assim, é utilizado um
método sincronizado para mudar o valor da variável e outro para verificar este valor, que pode retornar
um booleano indicando se a thread deve ser finalizada. Note que neste caso a sincronização é usada
apenas por seus efeitos na comunicação, não para a exclusão mútua, já que as ações de cada método são
atômicas. O custo dessa sincronização é bem baixo, mas para um código mais limpo e uma pequena
melhora de performance a sincronização pode ser omitida declarando o campo como volatile, que
garante que qualquer thread que leia o campo verá o valor mais recente.
No caso de não sincronizar corretamente o acesso a dados mutáveis o problema é ainda maior. Considere a
técnica de double-check para lazy initialization:
private static Foo foo = null;
public static Foo getFoo() {
    if (foo == null) {
       synchronized (Foo.class) {
          if (foo == null)
             foo = new Foo();
       }
    }
    return foo;
}
A idéia desta técnica é evitar o custo da sincronização no caso mais comum, que é acessar o campo (foo)
depois dele ser inicializado. A sincronização é usada apenas para evitar que múltiplas threads inicializem o
campo. Isso garante que a inicialização ocorra no máximo uma vez e que todas as threads chamando
o getFoo irão obter o valor correto da referência ao objeto. Porém, a referência ao objeto pode não
funcionar corretamente. Se uma thread lê a referência sem sincronização e então chama um método no
objeto referenciado, o método pode observar o objeto em um estado intermediário da inicialização, o que
pode causar sérios problemas. Isso pode acontecer porque apesar da referência só ser "publicada" no
campo foo após o objeto ser inteiramente construído, sem a sincronização não é garantido que a thread
verá todos os dados que foram armazenados na memória antes da publicação da referência do objeto. Em
geral, ler a referência de um objeto não garante que a thread irá ver os valores mais recentes dos dados
que constituem internamente o objeto referenciado, e portanto a técnica de double-check não funciona.
Para corrigir este problema há três alternativas: dispensar a lazy initialization instanciando o objeto
diretamente, sincronizar o método getFoo ou utilizar a técnica initialize-on-demand holder class, que é
apropriada quando um campo estático gasta muitos recursos para ser inicializado e pode não ser
necessário, mas será usado intensivamente se for necessário. A técnica é mostrada abaixo:
private static class FooHolder {
    static final Foo foo = new Foo();
}
public static Foo getFoo() { return FooHolder.foo; }
Esta técnica explora o fato de que uma classe não é inicializada até que seja usada. Quando o
método getFoo é chamado pela primeira vez, ele lê o campo FooHolder.foo, fazendo com que a
classe FooHolder seja inicializada. A beleza desta técnica é que o método getFoo não é sincronizado e
executa apenas o acesso a um campo, não aumentando o seu custo. O único inconveniente é que esta
técnica funciona apenas para campos estáticos, não para campos de instância.

Item 49: Evite uso excessivo de sincronização


Este item trata do problema oposto ao anterior. Dependendo da situação, sincronização excessiva pode
causar redução de performance, deadlock ou até mesmo comportamento não determinístico.
Para evitar risco de deadlock, nunca ceda controle ao cliente dentro de um método ou bloco sincronizado.
Em outras palavras, dentro de uma região sincronizada, não chame métodos public ou protected que foram
feitos para serem sobrescritos, pois a classe que contém a região sincronizada não tem conhecimento de
sua implementação e esta pode causar deadlock.
Portanto, coloque a chamada a métodos externos fora da região sincronizada. Essa técnica é chamada
de open call. Além de prevenir deadlocks, open calls propiciam um aumento na simultaneidade. Assim o
método pode rodar por um período longo durante o qual outras threads não terão acesso negado a objetos
compartilhados com esse método.
Como regra geral, faça o mínimo possível dentro de regiões sincronizadas. Outro problema grave que pode
acontecer é o método externo chamar de volta a região sincronizada, na mesma thread, quando seus dados
internos estão em estado inconsistente. Como o mecanismo de travas é recursivo, isso não causa deadlock,
e como a thread já possui a trava ela vai conseguir obter a trava uma segunda vez apesar de já existir uma
operação não relacionada em progresso nos dados protegidos dentro do bloco sincronizado. Dessa forma, o
mecanismo de trava torna-se inútil.
Além dos problemas na confiabilidade do código, o uso excessivo de sincronização pode diminuir a
performance. De forma geral não é uma redução muito significativa e cai nas "pequenas otimizações" que
devem ser evitadas segundo o Knuth (item 37), mas alguns casos devem ser analisados.
Se você estiver escrevendo uma abstração de baixo nível, que normalmente será usada por uma única
thread ou como um componente em um objeto sincronizado maior, não costuma ser necessário sincronizar
a classe internamente. Qualquer que seja sua decisão, é importante que você documente suas
propriedades em relação à segurança de threads.
Mas se você estiver escrevendo uma classe que será muito usada em circunstâncias que requerem
sincronização e também em circunstâncias em que ela não é necessária, uma abordagem razoável é prover
os dois tipos, sincronizada (thread-safe) e não sincronizada (thread-compatible). Uma forma de fazer isso é
criar uma classe wrapper (item 14) que implementa uma interface que descreve a classe e realiza a
sincronização apropriada antes de realizar a chamada do método no objeto. Outra forma mais simples, que
pode ser usada em classes que não serão estendidas ou reimplementadas, é prover uma classe não
sincronizada e uma subclasse consistindo apenas em métodos sincronizados que chamam seus
equivalentes na superclasse.
Uma boa razão para sincronizar uma classe internamente é quando ela será muito usada por threads
simultâneas e é possível conseguir uma maior simultaneidade através de um ajuste fino na sincronização.
Assim, ao invés de ter que sincronizar todo o objeto, apenas algumas partes serão sincronizadas
internamente. Além disso, se uma classe ou método estático depende de um campo estático mutável é
necessário fazer o sincronismo interno, pois pode não ser possível para o cliente realizar o sincronismo
externamente porque não é garantido que os outros clientes farão o mesmo.

Item 50: Nunca chame o método wait fora de um loop


O método Object.wait é usado  para fazer a thread esperar determinada condição. Ele precisa ser
chamado dentro de uma região sincronizada que trava o objeto em que é chamado. Esta é a técnica padrão
para usar o método wait:
synchronized (obj) {
    while (<condição não atendida>)
       obj.wait();
       // executa ação apropriada à condição
}
Sempre utilize-a para chamar o método wait. O loop serve para testar a condição antes e depois do wait.
Testar a condição antes de esperar e pular a espera se a condição já for atendida é necessário para garantir
a vida da thread. Se a condição já tiver sido atendida e o método notify já tiver sido chamado antes
do wait não há garantias de que a thread volte a ser executada. Testar a condição depois de esperar e
esperar novamente se a condição não for atendida é importante para garantir a segurança, já que se a
thread continuar sem que a condição tenha sido atendida ela pode destruir o estado protegido pela trava.
Outra questão relacionada é o uso de notify ou notifyAll. De forma geral, use sempre o notifyAll.
Este é o conselho mais conservador considerando que todas as chamadas ao wait estão dentro de loops,
pois garante que as threads que precisam ser reativadas sempre serão reativadas. Dessa forma também
serão reativadas outras threads, mas isso não influi no resultado do programa, já que será testada a
condição antes de continuar qualquer execução.
Para otimizar, você pode decidir usar o notify ao invés do notifyAll se todas as threads estão
esperando a mesma condição e apenas uma thread de cada vez pode se beneficiar da condição se tornar
verdadeira. Porém, mesmo assim pode ser recomendado utilizar o notifyAll, pois isto protege o código
de uma chamada indevida ao wait por outra thread não relacionada, o que é particularmente importante
se o objeto for público.
Por outro lado, ainda que o notifyAll garanta que o código sempre executará corretamente, ele pode
prejudicar a performance. Isso ocorre especialmente em certas estruturas de dados em que algumas
threads possuem um status especial e as outras precisam esperar, o que torna quadrático o número de
threads sendo tiradas do modo de espera. Se apenas algumas threads são elegíveis ao status especial,
você deve usar o pattern Specific Notification.

Item 51: Nunca dependa do agendador de threads


Quando múltiplas threads são executáveis, o agendador de threads determina qual irá rodar e por quanto
tempo. A política que será utilizada para essa decisão varia muito de acordo com a implementação da JVM,
portanto, para que o programa seja portável, é importante que ele não dependa do agendador de threads
para executar corretamente ou ter uma boa performance. Para isso, uma aplicação multitarefa deve ter o
mínimo de threads executáveis em qualquer momento.
A principal forma de atingir esse objetivo é possuir threads que executam uma quantidade de trabalho
pequena e então esperam por alguma condição (wait) ou pela passagem de certo intervalo de tempo
(sleep). Deve ser evitado o busy-wait, em que a thread fica checando repetidamente certa estrutura de
dados enquanto espera algo acontecer, pois isso pode aumentar muito a carga no processador.
Quando encontrar um programa que funciona mal porque algumas threads não recebem tempo de CPU
suficiente em relação a outras, resista à tentação de consertar o programa incluindo chamadas
ao Thread.yield. Isso pode funcionar, mas tornará o programa não-portável do ponto de vista de
performance. Outra técnica similar e ainda menos portável é o ajuste de prioridade das threads. Uma melhor
abordagem é reestruturar o programa para reduzir o número de threads executáveis ao mesmo tempo.
O único uso válido para Thread.yield é para aumentar artificialmente a simultaneidade durante os testes,
ajudando a explorar uma maior parte do programa.

Item 52: Documente a segurança em relação a threads


O comportamento de uma classe quando seus métodos estão sujeitos a uso simultâneo é uma parte
importante do contrato que ela estabelece com seus clientes.
Para permitir seu uso seguro de forma multitarefa, a classe deve documentar claramente o nível de
segurança que ela suporta, de acordo com a lista a seguir:
- immutable: instâncias desta classe parecem constantes para os clientes. Não é necessária sincronização
externa.
- thread-safe: as instâncias são mutáveis, mas todos os métodos possuem sincronização interna suficiente.
Chamadas simultâneas parecerão ser executadas serialmente, em uma ordem consistente.
- conditionally thread-safe: similar a anterior, porém alguns métodos precisam ser chamados em
seqüência e sem interferência de outras threads. O cliente precisa obter a trava apropriada durante a
execução da seqüência.
- thread-compatible: instâncias dessa classe podem ser usadas seguramente de forma simultânea se cada
chamada de método (ou seqüência) estiver dentro de uma região sincronizada.
- thread-hostile: o uso dessa classe por múltiplas threads não é seguro, mesmo que todos os métodos
sejam sincronizados externamente. Normalmente isso ocorre quando os métodos modificam dados
estáticos que afetam outras threads.

Item 53: Evite grupos de threads


Junto com threads, travas e monitores, uma abstração básica oferecida pelo sistema de threads é a de
grupos de threads. Eles servem para aplicar primitivos de Thread para várias threads ao mesmo tempo. A
maioria desses primitivos foi depreciada e os restantes são pouco usados, portanto grupos de threads não
fornecem muita funcionalidade útil. A API ThreadGroup é fraca em termos de segurança de threads e
possui diversas falhas. Como ela está obsoleta, não há previsões de que seja melhorada: ela foi um
experimento mal sucedido que pode ser ignorado. Se você precisar escrever uma classe que lida com
grupos lógicos de threads, armazene as referências em uma array ou collection. O único caso que justifica o
uso de ThreadGroup é o método ThreadGroup.uncaughtException, que é chamado
automaticamente quando uma thread do grupo lança uma exceção que não é tratada. O comportamento
padrão mostra a stack trace no standard error stream, mas você pode sobrescrever essa implementação.

Você também pode gostar