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:
|
|||
|
|||
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
}
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
Nenhum comentário:
Postar um comentário