poniedziałek, 28 marca 2011

Testowanie equals i hashCode

Zgodnie z obecną modą na wszechobecne testy jednostkowe wypadałoby jakoś testować nasze implementacje equals() i hashCode(), żeby się przy tym zbytnio nie narobić proponuję następujące rozwiązanie: klasa abstrakcycja, która sprawdza podstawowe warunki konktraktu dla equals() i hashCode() na podstawie 3 dostarczonych przez klasę dziedziczącą obiektów, które powinnym być identyczne wg. equals (ale maksymalnie od siebie różne) oraz jednego, który jest różny od pozastałych wg. equals. Pełny kod źródłowy wraz z klasą abstrakcyjną można pobrać korzystając z tego linku. Zastosowanie najlepiej widać na przykładzie:

Klasa testowana:
public class Entity {
    
    private int field1;
    private int field2;
    private int notSignificantField;
    
    @Override
    public int hashCode() {
         ...
    }
    
    @Override  
    public boolean equals(Object object) {  
        ...
    }

    public Entity(int field1, int field2, int notSignificantField) {
        this.field1 = field1;
        this.field2 = field2;
        this.notSignificantField = notSignificantField;
    }
    ...
} 

Klasa testująca:
public class EntityTest extends AbstractEqualsTest<Entity> {

    @Override
    public Entity objectThatShouldBeEqualA() {
        return new Entity(1, 2, 33);
    }

    @Override
    public Entity objectThatShouldBeEqualB() {
        return new Entity(1, 2, 44);
    }

    @Override
    public Entity objectThatShouldBeEqualC() {
        return new Entity(1, 2, 55);
    }

    @Override
    public Entity objectThatShouldNotBeEqual() {
        return new Entity(1, 1, 33);
    }
}

czwartek, 3 marca 2011

Metoda hashCode w javie

Nudów ciąg dalszy, czyli kilka słów o metodzie hashCode(), która powinna być zawsze nadpisana, jeśli nadpisaliśmy metodą equals().

1. Kontrakt dla metody hashCode().

Metoda musi spełniać następujące warunki:
  • W czasie działania aplikacji metoda zawsze zwraca tą samą wartość int, dla tego samego obiektu.
  • Jeśli dwa obiekty równe wg. metody equals() to wartości zwracane przez hashCode() dla tych obiektów muszą być takie same.
  • Jeśli dwa obiekty nie są równe wg. metody equals() to wartości zwracane przez hashCode() dla tych obiektów nie muszą być takie same. Aczkolwiek zaleca się taką implementację hashCode(), żeby dla różnych obiektów zwracała różne wartości hash, co wpływa pozytywnie na szybkość operacji na hashowalnych kolekcjach.
Chyba największą trudnością przy implementacji hashCode jest wybór odpowiednich pól, na bazie których, będzie liczony hash. Jak ktoś lubi może stosować różne kombinację podczas obliczania hash'a (cała masa tego w necie), ale chyba lepiej i wygodniej skorzystać z gotowych rozwiązań (Commons, Guava):
@Override
    public int hashCode() {
        
        return new HashCodeBuilder().append(someField)
                                    .append(someField2)
                                    .toHashCode();
    }

środa, 2 marca 2011

Zagadka

Jak ten młody leszcz dałem się ostatnio nabrać na TreeSet w Javie. Oto zagadka:
chciałem sobie posortować liście na podstawie ich koloru, użyłem TreeSet'a, jaki będzie wynik funkcji shouldTestTreeSet(), jaka będzie kolejność liści?

class Leaf{
   
    int color;
    int size;
   
    public Leaf(int color, int size) {
        this.color = color;
        this.size = size;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == this) return true;
        if (!(obj instanceof Leaf)) return false;
        Leaf leaf = (Leaf) obj;
        return new EqualsBuilder().append(this.color, leaf.color)
            .append(this.size, leaf.size).isEquals();
    }
   
    @Override
    public int hashCode() {
        return new HashCodeBuilder().append(color).append(size).toHashCode();
    }
   
    @Override
    public String toString() {

        return String.valueOf(color + " " +size);
    }
}

class LeafComparator implements Comparator<Leaf>{

    @Override
    public int compare(Leaf o1, Leaf o2) {
       
        return Ints.compare(o1.color, o2.color);
    }
}

public class TreeSetTest {

    public void shouldTestTreeSet() {

        Leaf leaf = new Leaf(1,1);
        Leaf leaf2 = new Leaf(1,2);
        Leaf leaf3 = new Leaf(1,3);

        Set<Leaf> tree = new TreeSet<Leaf>(new LeafComparator());
       
        tree.add(leaf3);
        tree.add(leaf);
        tree.add(leaf2);

        System.out.println(tree);
    }
}