20 março, 2013

Resumo Effective Java - Parte 2 (Métodos comuns a todos os objetos)



Capítulo 3 – Métodos comuns a todos os objetos

Item 8 – Obedecendo o contrato do equals

·         Sempre que instâncias de uma classe precisarem ser comparadas entre si e houver a noção de identidade que difere da mera identidade entre objetos (ou seja, aquela que retorna verdadeiro meramente se dois objetos forem iguais na memória), o método equals deve ser implementado. 
·         Deve-se obedecer as seguintes regras (contrato) ao sobreescrever o equals:

o   Reflexividade: Dado x, x.equals(x) deve retornar true.
o   Simetria: Dado x e y, x.equals(y) deve retornar true se e somente se y.equals(x) retornar true.
o   Transitividade: Dado x,y e z. Se x.equals(y) é true e y.equals(z) é true, então x.equals(z) deve ser true.

·         Transitividade: Essa característica merece uma atenção especial. A discussão que segue é longa e complicada, mas achei importante acrescentar aqui.

Numa relação de herança, nada impede que uma subclasse acrescente atributos que devem ser considerados na execução do equals. Isso gera inúmeros problemas! Vejamos o código abaixo:






public class Point {

  private final int x;
  private final int y;
  public Point(int x, int y) {
this.x = x;
this.y = y;
}

@Override public boolean equals(Object o) {

  if (!(o instanceof Point))
    return false;

  Point p = (Point)o;
  return p.x == x && p.y == y;
}

... // Remainder omitted
}

 


public class ColorPoint extends Point {

private final Color color;

public ColorPoint(int x, int y, Color color) {

super(x, y);
this.color = color;

}
... // Remainder omitted
}

 

 
O funcionamento que esperamos é que consigamos comparar intercaladamente pontos e pontos coloridos através do método equals. Afinal, são todos pontos.  Mas como ColorPoint acrescentou uma informação a mais à sua identidade, esta deve ser considerada. Como implementar o equals para a classe ColorPoint? Vejamos algumas soluções.

1 - Comparar apenas objetos da mesma classe:

// Broken - violates symmetry!
@Override public boolean equals(Object o) {

if (!(o instanceof ColorPoint))
return false;

return super.equals(o) && ((ColorPoint) o).color == color;

}

Esse código viola a simetria. Point.equals(ColorPoint) retornará true se as coordenadas forem as mesmas mas ColorPoint.equals(Point) sempre retornará false.

2 – Não considerar informação de cor em comparações misturadas

Resolve o problema da simetria mas ao custo da transitividade

// Broken - violates transitivity!
@Override public boolean equals(Object o) {

if (!(o instanceof Point))
return false;
    
if (!(o instanceof ColorPoint))
return o.equals(this); //nao considera cor se Object for Point
     
return super.equals(o) && ((ColorPoint) o).color == color;

}

ColorPoint p1 = new ColorPoint(1, 2, Color.RED);
Point p2 = new Point(1, 2);
ColorPoint p3 = new ColorPoint(1, 2, Color.BLUE);

P1.equals(P2) retorna true = > chama o equals do ColorPoint na linha “return o.equals(this)”

P2.equals(P3) retorna true => chama o equals do Point

P1.equals(P3) retorna false => retorna falso pois são ColorPoint de cores diferentes

3 – Usando o getClass  ao invés do instanceOf

// Broken - violates Liskov substitution principl
@Override public boolean equals(Object o) {

  if (o == null || o.getClass() != getClass())
    return false;
  Point p = (Point) o;
  return p.x == x && p.y == y;

}

O código acima compara apenas objetos da mesma classe.  O princípio da substituição de Liskov diz que qualquer propriedade importante para um tipo deve valer também para o subtipo. Assim, qualquer método escrito para um tipo, deve funcionar corretamente nos seus subtipos. A implementação do equals, na classe Point, na forma acima, viola esta propriedade. Qualquer subclasse criada a partir de Point e que precisar (e puder) usar o equals da classe Point, não vai funcionar. Sempre que esta nova classe for comparada com a classe Point irá retornar false, independentemente se a comparação dos atributos estiver válida. 

Não há um meio satisfatório de estender uma classe, adicionar um atributo e ainda a relação de identidade continuar funcionando.
A melhor solução para isso é: ao invés de fazer herança, optar por composição. Concluímos que, toda vez que uma classe é estendida e um novo atributo adicionado, isso vai quebrar o método equals implementado na superclasse. Quando uma comparação for feita entre um objeto da sub.equals(super), o método da subclasse vai querer utilizar para comparação um atributo que só ela tem.

Evitamos isso utilizando composição ao invés de herança:

// Adds a value component without violating the equals contract
public class ColorPoint {

 private final Point point;
 private final Color color;

 public ColorPoint(int x, int y, Color color) {

  if (color == null)
   throw new NullPointerException();
  point = new Point(x, y);
  this.color = color;
 }


@Override public boolean equals(Object o) {

 if (!(o instanceof ColorPoint))//Como herança não está envolvida, não é
  return false;                 //preciso permitir a comparação de Point com
                                //ColorPoint                               
 ColorPoint cp = (ColorPoint) o;
 return cp.point.equals(point) && cp.color.equals(color);
}
... // Remainder omitted

}

OBS.: Sempre comparar double com Double.compare e float com Float.compare

Item 9 – Hashcode

·         Algoritmo para gerar hashcode:

1. Store some constant nonzero value, say, 17, in an int variable called result.
2. For each significant field f in your object (each field taken into account by the equals method, that is), do the following:

a. Compute an int hash code c for the field:
i. If the field is a boolean, compute (f ? 1 : 0).
ii. If the field is a byte, char, short, or int, compute (int) f.
iii. If the field is a long, compute (int) (f ^ (f >>> 32)).
iv. If the field is a float, compute Float.floatToIntBits(f).
v. If the field is a double, compute Double.doubleToLongBits(f), and
then hash the resulting long as in step 2.a.iii.
vi. If the field is an object reference and this class’s equals method compares the field by recursively invoking equals, recursively invoke hashCode on the field. If a more complex comparison is required, compute a “canonical representation” for this field and invoke hashCode on the canonical representation. If the value of the field is null, return 0 (or some other constant, but 0 is traditional).
vii. If the field is an array, treat it as if each element were a separate field. That is, compute a hash code for each significant element by applying these rules recursively, and combine these values per step 2.b. If every element in an array field is significant, you can use one of the Arrays.hashCode methods added in release 1.5.

b. Combine the hash code c computed in step 2.a into result as follows: result = 31 * result + c;

3. Return result.

4. When you are finished writing the hashCode method, ask yourself whether equal instances have equal hash codes. Write unit tests to verify your intuition!

Ex:
// Lazily initialized, cached hashCode
private volatile int hashCode;  // (See Item 71)

@Override public int hashCode() {
  int result = hashCode;
  if (result == 0) {
    result = 17;
    result = 31 * result + areaCode;
    result = 31 * result + prefix;
    result = 31 * result + lineNumber;
    hashCode = result;
  }
 return result;
}

Item 13 – Classes e Interfaces

·         Atributos devem ser PRIVADOS.
·         Uma exceção pode ser as constantes. Campos public static final SEMPRE devem apontar para primitivos ou objetos imutáveis. Se não apontarem para objetos imutáveis, apesar da referencia não poder ser alterada, o objeto poderá! Um array é sempre mutável e assim nunca deve ser criado como constante.
·         Exemplo de como disponibilizar um array privado de forma que continue imutável:

private static final Thing[] PRIVATE_VALUES = { ... };
public static final List VALUES = Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES));

ou

private static final Thing[] PRIVATE_VALUES = { ... };
public static final Thing[] values() {  return PRIVATE_VALUES.clone();  }

Item 10 – Sempre sobreescreva toString
Item 11 – Sobreescreva o método clone com cuidado
Item 12 – Considere implementar a interface Comparable

Resumo Effective Java - Parte 1 (Criando e destruindo objetos)




Vou começar aqui uma série de postagens com o resumo recente que fiz do livro Effective Java, 2nd Edition. Para quem não conhece, é um livro bem recomendado na comunidade Java e alguns dizem que de leitura obrigatória. A Caleum faz comentários no seu blog http://blog.caelum.com.br/effective-java-segunda-edicao/.

O livro tem em torno de 350 páginas e o resumo ficou em 33. Eu presumo um certo conhecimento da linguagem e por isso não abordo explicações que achei muito básicas. Apesar disso, em alguns momentos, para assuntos mais difíceis, faço uma fundamentação teórica  antes de falar das dicas do Joshua Bloch.

Os dois últimos capítulos, que tratam respectivamente sobre concorrência e serialização, ficaram sem resumo. Se alguém se habilitar em fazer e me repassar para complementar o conteúdo será muito bem vindo. Bem vindas também são as críticas e sugestões. Gostaria de saber se o texto ficou claro e didático suficiente e coisas do gênero.

Espero que gostem e boa leitura!
 

Capítulo 2 – Criando e destruindo objetos

Item 1 – Static factories ao invés de construtores

·         Um método estático que retorna uma instancia da classe
·         Semanticamente faz mais sentido do que ter que entender vários construtores com o mesmo nome que só variam os argumentos
·         Não precisam sempre instanciar um objeto novo. Facilitam o gerenciamento das instancias da classe e a criação de classes imutáveis. Uma classe imutável permite a comparação dessa forma x == y invés de usar o equal com hashcode, o que melhora performance.
·         Mantém a API mais enxuta uma vez que restringe a necessidade do usuário da API ter conhecimento de todas as classes concretas que existe. Isso ocorre, pois um método static factory method não precisa necessariamente retornar uma instancia da classe, mas pode retornar qualquer subinstancia. Por exemplo, a classe Collections retorna 32 implementações de conveniência da interface Collection. Essas implementações concretas nem são disponibilizadas em documentações como parte da API, pois todas implementam a interface e basta assim liberar documentação apenas disso (como deveria mesmo ser).
·         Conceito de service provider framework: os métodos fábrica podem, inclusive, retornar uma classe que não existe no momento. É o caso da API JDBC, onde as implementações concretas são adicionadas depois e registradas para poderem ser utilizadas.  
·         Map <String, List<String>> m = HashMap.newInstance(); -> o compilador infere e retorna uma instância correta da classe.
·         Map<String, List<String>> m = new HashMap<String, List<String>>(); - > o constructor não consegue (ainda) fazer esse tipo de inferência.

Item 2 – Builder classes

·         Classes com muitos parâmetros de inicialização devem usar Builder

public interface Builder {
T build();
}
·         O construtor do Builder tem os atributos obrigatórios.
·         Métodos setters têm os atributos opcionais e retornam o próprio Builder, o que permite interface fluente
·         O método build() retorna a classe concreta e pode fazer validações

Item 3 – Singleton

·         public static final Tipo INSTANCE = new Tipo();  + construtor privado
·         o static garante que será executado assim que a classe for carregada pelo class loader
ou
·         private static final Tipo INSTANCE = new Tipo()  + public static Tipo getInstance() + construtor privado
·         o getInstance() tem que ser static já que retorna variável estática

As duas abordagens acima não preveem contra a evocação do construtor privado através de reflexão e requerem cuidados especiais para que durante a deserialização não seja criada outra instância.


·         A terceira opção é a preferida -> Utilizar uma Enum com apenas um elemento. Isso protege contra ataque através de reflexão e serialização.

Item 4 – Garanta não instabilidade através de construtores privados
 
Item 5 – Criação desnecessária de objetos

·         Nunca fazer String x = new String(“teste”); mas sim, String x = “teste”;
·         Autoboxing sempre gera uma instância wrapper de um primitivo => prefira primitivos

Item 6 – Eliminar referências obsoletas

·         Quando usando arrays e coleções ficar atento para setar para null posições que não devem conter mais nada.
·         Sempre que o programa gerenciar sua própria memória pode haver problema nesse sentido.
·         Por exemplo, se seu programa cria um array que servirá como pilha, terá que gerenciar o crescimento e encurtamento desse array. Nesse processo, elementos serão retirados da pilha e, se essas posições do array não forem inutilizadas atribuindo-se null, estas representarão referências a objetos que, na verdade, não serão mais utilizados e que não serão detectados pelo garbage collector. 

Item 7 – Não usar finalizers

·         Ou seja, não depender do método finalize()!
·         Ao invés disso, escrever um método explícito em sua classe que deverá ser chamado quando liberação de recursos for necessária. Nesse caso, os outros métodos da classe devem verificar se esta já foi finalizada (flag private) e, se for o caso, disparar uma IllegalStateException.