web-dev-qa-db-fra.com

Quelle est la raison derrière Enum.hashCode ()?

La méthode hashCode () de la classe Enum est finale et définie en tant que super.hashCode (), ce qui signifie qu'elle renvoie un nombre basé sur l'adresse de l'instance, qui est un nombre aléatoire fourni par les programmeurs POV.

La définir par exemple ordinal() ^ getClass().getName().hashCode() serait déterministe pour différentes machines virtuelles. Cela fonctionnerait même un peu mieux, puisque les bits les moins significatifs "changeraient autant que possible", par exemple, pour une énumération contenant jusqu'à 16 éléments et un HashMap de taille 16, il n'y aurait à coup sûr aucune collision (bien sûr, L'utilisation d'une EnumMap est préférable, mais parfois impossible, par exemple, il n'y a pas de ConcurrentEnumMap). Avec la définition actuelle, vous n'avez aucune telle garantie, n'est-ce pas?

Résumé des réponses

L'utilisation de Object.hashCode() se compare à un hashCode plus agréable, comme celui ci-dessus, comme suit:

  • AVANTAGES
    • simplicité
  • CONTRAS
    • la vitesse
    • plus de collisions (pour n'importe quelle taille d'une HashMap)
    • non-déterminisme, qui se propage à d'autres objets les rendant inutilisables pour
      • simulations déterministes
      • Calcul ETag
      • traquer les bugs en fonction, par exemple sur un ordre d'itération HashSet

Personnellement, je préférerais le hashCode plus agréable, mais à mon humble avis, aucune raison ne pèse beaucoup, peut-être sauf pour la vitesse.

METTRE À JOUR

J'étais curieux de la vitesse et j'ai écrit un repère avec des résultats surprenants . Pour un prix d'un seul champ par classe, vous pouvez utiliser un code de hachage déterministe qui est presque quatre fois plus rapide. Stocker le code de hachage dans chaque champ serait encore plus rapide, bien que négligeable.

La raison pour laquelle le code de hachage standard n’est pas beaucoup plus rapide est qu’il ne peut pas s'agir de l'adresse de l'objet car les objets sont déplacés par le GC.

MISE À JOUR 2

Il y a des choses étranges en cours avec la performance hashCode en général. Quand je les comprends, il reste la question ouverte, pourquoi System.identityHashCode (lire dans l'en-tête de l'objet) est bien plus lent que d'accéder à un champ d'objet normal.

44
maaartinus

Je pense que la raison pour laquelle ils l'ont rendu final est d'éviter que les développeurs se lancent dans le pied en réécrivant un hashCode sous-optimal (ou même incorrect).

En ce qui concerne l’implémentation choisie: elle n’est pas stable entre les machines virtuelles, mais elle est très rapide, évite les collisions et n’a pas besoin d’un champ supplémentaire dans l’énumération. Étant donné le nombre généralement faible d'instances d'une classe enum et la rapidité de la méthode equals, je ne serais pas surpris que le temps de recherche HashMap soit plus long avec votre algorithme qu'avec l'actuel, en raison de sa complexité supplémentaire.

11
JB Nizet

La seule raison d'utiliser hashCode () de Object et de le rendre final, je pense, est de me faire poser cette question.

Tout d'abord, vous ne devez pas compter sur de tels mécanismes pour partager des objets entre machines virtuelles. Ce n'est tout simplement pas un cas d'utilisation pris en charge. Lorsque vous sérialisez/désérialisez, vous devez vous appuyer sur vos propres mécanismes de comparaison ou seulement "comparer" les résultats avec des objets de votre propre JVM.

Le fait de laisser enum hashCode être implémenté en tant que Objects code de hachage (basé sur l'identité) est dû au fait que, dans une machine virtuelle, il y aura une seule instance de chaque objet enum. Cela suffit pour garantir que cette mise en œuvre est logique et correcte.

Vous pourriez argumenter comme "Hey, String et les wrappers des primitives (Long, Integer, ...) ont tous des spécifications bien définies et déterministes de hashCode! Pourquoi les enums ne l'ont-ils pas?", Eh bien, pour commencer, vous pouvez avoir plusieurs références de chaîne distinctes représentant la même chaîne, ce qui signifie que l'utilisation de super.hashCode serait une erreur. Ces classes ont donc nécessairement besoin de leur propre implémentation de hashCode. Pour ces classes de base, il était logique de leur laisser des hashCodes déterministes bien définis.

Pourquoi ont-ils choisi de le résoudre comme ça?

Eh bien, regardons les exigences de la mise en œuvre hashCode . La principale préoccupation est de s'assurer que chaque objet doit renvoyer un code de hachage distinct (sauf s'il est égal à un autre objet). L'approche basée sur l'identité est extrêmement efficace et garantit cela, contrairement à votre suggestion. Cette exigence est apparemment plus forte que n'importe quel "bonus de commodité" pour faciliter la sérialisation, etc.

24
aioobe

J'ai posé la même question, car je n'ai pas vu celui-ci. Pourquoi, dans Enum, hashCode () fait référence à la mise en oeuvre Object hashCode (), au lieu de la fonction ordinal ()?

Je l'ai rencontré comme une sorte de problème, lors de la définition de ma propre fonction de hachage, pour un objet s'appuyant sur enum hashCode comme l'un des composites. Lors de la vérification d'une valeur dans un ensemble d'objets renvoyé par la fonction, je les ai vérifiées dans un ordre, ce qui, je suppose, devrait être identique, puisque le hashCode que je me suis défini et que, par conséquent, les éléments tombent aux mêmes nœuds sur l'arborescence, mais puisque hashCode renvoyé par enum change du début à la fin, cette hypothèse était fausse et le test pourrait échouer de temps en temps.

Alors, quand j'ai compris le problème, j'ai commencé à utiliser ordinal. Je ne suis pas sûr que tout le monde écrivant hashCode pour leur objet le réalise.

Donc, en gros, vous ne pouvez pas définir votre propre hashCode déterministe, en vous basant sur enum hashCode, vous devez utiliser ordinal à la place}

P.S. C'était trop gros pour un commentaire :)

2
mavarazy

La JVM applique que pour une constante d'énumération, un seul objet existera en mémoire. Il n’ya aucun moyen de vous retrouver avec deux objets d’instance différents de la même constante d’énumération au sein d’une même machine virtuelle, sans réflexion, ni sur le réseau via la sérialisation/désérialisation.

Cela étant dit, puisqu'il s'agit du seul objet à représenter cette constante, il importe peu que son hascode soit son adresse car aucun autre objet ne peut occuper le même espace d'adressage en même temps. Il est garanti qu’il est unique et "déterministe" (en ce sens que dans la même machine virtuelle, en mémoire, tous les objets auront la même référence, quelle qu’elle soit).

1
pnt

Une autre raison pour laquelle il est implémenté de la manière que j’imagine, c’est parce que hashCode () et equals () doivent être cohérents, et que l’objectif de conception d’Enums est qu’ils devraient être simples à utiliser et constants à la compilation (pour les utiliser est des "constantes de casse"). Cela rend également légal la comparaison d'instances enum avec "==", et vous ne voudriez tout simplement pas que "égal à" se comporte différemment de "==" pour les énumérations. Cela lie à nouveau hashCode au comportement par défaut basé sur la référence Object.hashCode () . Comme je l’ai dit plus tôt, equals () et hashCode () ne doivent pas non plus considérer que deux constantes d’énumération provenant de machines JVM différentes sont égales. Lorsque vous parlez de sérialisation: par exemple, les champs tapés comme énumération du sérialiseur binaire par défaut en Java ont un comportement spécial qui sérialise uniquement le nom de la constante, et lors de la désérialisation, la référence à la valeur enum correspondante dans la JVM de désérialisation est recréée . JAXB et d'autres mécanismes de sérialisation basés sur XML fonctionnent de manière similaire. Alors, ne vous inquiétez pas

0
Mirko Klemm

Il n’est pas nécessaire que les codes de hachage soient déterministes entre les machines virtuelles et aucun avantage n’est acquis s’ils l’étaient. Si vous vous fiez à ce fait, vous les utilisez mal.

Comme il n’existe qu’une seule instance de chaque valeur énumérée, il est garanti que Object.hashcode() ne se trouvera jamais en collision, qu’il est bien réutilisé et que le code est très rapide.

Si l'égalité est définie par identité, alors Object.hashcode() donnera toujours la meilleure performance.

Le déterminisme des autres codes de hachage n'est qu'un effet secondaire de leur mise en œuvre. Comme leur égalité est généralement définie par les valeurs de champ, le mélange de valeurs non déterministes serait une perte de temps. 

0
OrangeDog

Tant que nous ne pouvons pas envoyer un objet enum1 sur une autre machine virtuelle, je ne vois aucune raison d'imposer de telles exigences aux enums (et aux objets en général)


1 Je pensais que c'était assez clair - un objet est une instance d'une classe. Un objet sérialisé est une séquence d'octets, généralement stockée dans un tableau d'octets. Je parlais d'un objet.

0
Andreas_D