web-dev-qa-db-fra.com

Quel est le hashCode pour une classe personnalisée n'ayant que deux propriétés int?

En Java, j'ai une classe qui représente un point avec des coordonnées int

public class Point {
    int x = -1;
    int y = -1;

    public Point (int xNew, int yNew) {
        x = xNew; y = yNew;
    }

    public boolean equals (Object o) {
        // no need for (o instanceof Point) by design
        return x == ((Point)o).x && y == ((Point)o).y;
    }
}

J'utilise des objets de la classe Point comme clés dans une HashMap et en tant qu'éléments dans une HashSet.

Quel serait le meilleur candidat pour la fonction hashCode? Je voudrais le faire en double pour que la partie gauche soit x et la partie droite est y, par exemple: x = 4, y = 12, puis le hashCode renvoie 4.12. Mais par la mise en œuvre, il ne peut pas être double, seulement int.

Ce n'est pas une option:

public int hashCode() {
    // no need to check for exception parseInt since x and y are valid by design
    return Integer.parseInt(Integer.toString(x) + Integer.toString(y));
}

parce que les valeurs x et y peuvent être trop longues, de sorte qu'elles ne seront pas converties ensemble.

24
Sophie Sperner

Vous ne pouvez pas changer le type de hashCode et vous ne devriez pas le vouloir non plus.

Je voudrais juste aller avec quelque chose comme:

public int hashCode() {
    return x * 31 + y;
}

Notez que cela signifie que (a, b) est différent de (b, a) dans la plupart des cas (contrairement à l'ajout ou à la XOR-ing, par exemple). Cela peut être utile si vous vous retrouvez souvent avec des clés pour les valeurs "commutées" dans la vie réelle.

C'est n'est pas unique - mais les codes de hachage ne doivent pas obligatoirement l'être. Ils just doivent être identiques pour des valeurs égales (pour l'exactitude) et (pour l'efficacité) "habituellement" différents pour des valeurs non égales, avec une distribution raisonnable.

En général, je suis généralement le même modèle que celui suggéré par Josh Bloch dans Effective Java:

public int hashCode() {
    int hash = 17;
    hash = hash * 31 + field1Hash;
    hash = hash * 31 + field2Hash;
    hash = hash * 31 + field3Hash;
    hash = hash * 31 + field4Hash;
    ...
    return hash;
}

field1Hash serait le code de hachage pour les champs de type référence (ou 0 pour une référence nulle), la variable int pour les valeurs int, une sorte de hachage de 64 bits à 32 pour long.

EDIT: Je ne me souviens pas des détails de la raison pour laquelle 31 et 17 travaillent bien ensemble. Le fait qu’ils soient tous les deux premiers peut être utile - mais de ce que je me souviens, les maths qui expliquent pourquoi des hachages de ce type sont généralement raisonnables des valeurs probables est connu à l'avance) est difficile ou mal compris. Je sais que multiplier par 31 n’est pas cher (décalage à gauche 5 et soustraire la valeur initiale) ...

37
Jon Skeet

Utilisez simplement Java.util.Objects.hash (valeurs Object ...) .

public int hashCode() {
    return Objects.hash(field1,field2);
}
7
YieldNull

Je sais qu'il est normal que les objets non égaux aient les mêmes codes de hachage. Cependant, plus le nombre de collisions est important, plus les performances seront mauvaises (par exemple, dans une table de hachage).

Autant que je sache, le meilleur mapping deZ² →Zest la "fonction de couplage élégant" (google it). Voici la mise en place

// x,y must be non-negative
int elegant(int x, int y) {
    return x < y ? y * y + x : x * x + x + y;
}


// returns a unique number for every x,y pair
int elegantSigned(int x, int y) {
    if (x < 0) {
        if (y < 0)
            return 3 + 4 * elegant(-x - 1, -y - 1);
        return 2 + 4 * elegant(-x - 1, y);
    }
    if (y < 0)
        return 1 + 4 * elegant(x, -y - 1);
    return 4 * elegant(x, y);
}

Cela commencera à se chevaucher dès que vous aurez un débordement de multiplication. Si la valeur absolue de x et y est inférieure à environ 46 000, il y aura alors des collisions zéro hash. 

7
John Henckel

Cela vaut souvent la peine de considérer Apache Commons HashCodeBuilder

Cette classe permet de construire une bonne méthode hashCode pour toutes les classes. Elle suit les règles énoncées dans le livre Effective Java de Joshua Bloch. Écrire une bonne méthode hashCode est en fait assez difficile. Ce cours vise à simplifier le processus.

et je recommanderais vivement de consulter le livre référencé Effective Java .

3
Brian Agnew

Il existe une stratégie commune pour générer une opération de hashcode. Dans votre cas, ce serait:

public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + x;
    result = prime * result + y;
    return result;

}

2
Leonkur

Vous voudrez peut-être jeter un oeil à Google GuavaObjects.hashCode (Object ...) method.

public int hashCode() {
  return Objects.hashCode(x, y);
}
1
t3chris

Cette question est assez ancienne, mais je pense que l'idée même sera réelle tant que Java existera. Laissez-nous analyser les approches ci-dessus:

  1. Objects.hashCode(...) est fluide et clair ce qui doit être fait, MAIS il utilise varargs (créant implicitement un tableau) et de plus, il implicitement encadre chaque primitive, être passé dans la méthode.
  2. x * 31 + y est performant: il n'y a pas de boxing, aucune opération de création de tableau explicite ou implicite n'est utilisée. MAIS, c’est peu clair ce qui doit être fait. Pourquoi 31, pas 42 ? Pour ceux qui sont familiarisés avec le fonctionnement du hachage, il n’ya aucune difficulté à comprendre un tel code, mais qu’en est-il des autres? Le deuxième piège est qu’il est difficile à étendre: vous pouvez facilement oublier d’ajouter de nouvelles valeurs au code de hachage si vous vouliez, par exemple, passer en 3D et ajouter la coordonnée z, parce que cela vous oblige à copier-coller un code presque identique plusieurs fois.

Je peux introduire la troisième approche, ne pas être mentionné dans les réponses ci-dessus:

@Override
public final int hashCode()
{
    final int[] numbers = {x, y};
    return Arrays.hashCode(numbers);
}

Il utilise un tableau temporaire pour contenir les entiers en cours de hachage et en appelant Arrays.hashCode () , disponible depuis Java 1.5, il existe également des versions pour d'autres types primitifs.

(Pros): C'est SEC , fluide et parfaitement clair sur ce qui doit être fait. Il ne souffre pas de la boxe implicite et n'utilise pas de vararg implicite. C'est relativement rapide et pas cher. Il peut être facilement étendu en ajoutant des nombres supplémentaires dans l'initialiseur de tableau.

Contre: Ce n'est pas aussi rapide que la méthode copier-coller. Veuillez considérer si le code de hachage est appelé fréquemment.

Meilleures salutations.

1
Netherwire

essayez d'ajouter leurs codes de hachage. ? 

retour nouvel Integer (x) .hashCode () + nouvel Integer (y) .hashCode ();

0
Markus Mikkolainen