2013-01-20 3 views
1

J'ai une classe et des règles d'équivalence différentes (implémentation différente de equals et hashCode). Les données sont générées dans un processus en premier, où une règle d'équivalence est appliquée, puis envoyée au deuxième processus, où l'autre règle d'équivalence est appliquée. En particulier, je fais beaucoup de map opérations et equals et hashCode sont appelées implicitement par la bibliothèque standard (sur laquelle je n'ai pas de contrôle). Que pensez-vous être le meilleur moyen d'y parvenir? J'ai deux solutions maintenant:Basculer entre les règles d'égalité

  1. définir deux sous-classes avec différentes equals et hashCode. Après le processus 1, effectuez la conversion en initiant des objets de l'autre sous-classe.
  2. Introduire des états mutables dans la classe pour indiquer quelle règle d'équivalence appliquer.

Alors, selon vous, est-ce mieux ou existe-t-il d'autres bonnes solutions?

+0

Est-ce que vous définissez 'hashCode()' et 'equals()' uniquement pour les besoins de la compatibilité 'Map'? – cheeken

+0

@cheeken 'HashSet' est également utilisé, mais fondamentalement pour ce genre de collections. – Kane

Répondre

0

Enfin, je trouve que l'écriture de mon propre personnalisé Map est le chemin à parcourir (au moins dans mon problème). Après avoir creusé un moment dans la bibliothèque standard de scala, je me rends compte que c'est extrêmement facile. Qu'elles soient mutables ou non, les méthodes d'égalité d'élément et hashCode de HashMap sont héritées de HashTable et de HashTable.Utils et sont protégées, ce qui signifie que n'importe quelle sous-classe peut facilement la remplacer. Donc ce qui suit est ce que je me retrouve avec:

trait Equility[T] { 
    def equal(t1: T, t2: T): Boolean 
    def hash(t: T): Int 
} 

class MapWithEquility[K, V](e: Equility[K]) extends scala.collection.mutable.HashMap[K, V] { 
    override def elemHashCode(key: K) = e.hash(key) 
    override def elemEquals(key1: K, key2: K) = e.equal(key1, key2) 
} 

J'ai fait un test simple et cela a bien fonctionné.

4

Une solution peut-être plus élégante serait une classe personnalisée Map qui permet la personnalisation du hachage et de l'évaluation de l'égalité.

trait MappingScheme[KEY_CLASS,VALUE_CLASS] implements Comparable[VALUE_CLASS] { 
    def generateHash(key: KEY_CLASS): Int 
    // Also imposes compare() definition from Comparator 
} 

class CustomSchemeMap[K,V](mappingScheme: MappingScheme[K,V]) implements Map[K,V] { 
    // Implement Map methods; use mappingScheme to generate hashes and 
    // perform equality checks 
} 

Dans votre scénario, vous devez créer deux personnalisés MappingScheme s et les utiliser comme appropriés dans vos CustomSchemeMap s. Cette approche est plus performante que les solutions que vous suggérez (pas de création d'instance supplémentaire et vous n'avez pas à muter vos objets), mais elle a aussi un sens plus logique et est plus facile à suivre.


Toutefois, la mise en œuvre d'un Map peut être une commande importante. Si cela semble hors de portée, je créerais des classes adaptatrices simples pour entourer vos objets et les introduire dans les cartes.

class KeyableAdapter1(o: OriginalClass) { 
    override def hashCode() = o.hashCode + 10 // e.g. 
    override def equals(that: Object) = o.stuff == that.stuff // e.g., after cast 
    def get(): OriginalClass = o // To get it back out, if you need to 
} 

class KeyableAdapter2(o: OriginalClass) { 
    override def hashCode() = o.hashCode^10 
    override def equals(that: Object) = o.otherStuff = that.otherStuff 
    def get(): OriginalClass = o 
} 

// Later 
myMap.put(new KeyableAdapter1(o1), stuff) 
myOtherMap.put(new KeyableAdapter2(o1), moreStuff) 

Ceci est similaire à l'approche de sous-classement, sauf que vous pouvez récupérer l'objet original via get(), et il est plus facile à suivre (au moins à mon avis).

1

Définir deux sous-classes avec des équations différentes et hashCode. Après le processus 1, effectuez la conversion en initiant des objets de l'autre sous-classe.

Ceci est correct, mais ces deux classes ne sémantiquement pas, je pense. Ils seraient juste utilisés dans un autre cas, bien qu'ils représentent la même chose.

Introduire des états mutables dans la classe pour indiquer quelle règle d'équivalence appliquer.

Ne jamais le faire, cela est cassé:

  • Si vous changez globalement l'état, il est une grande magie, qui pourrait causer beaucoup de problèmes, surtout si vous utilisez ces classes dans plusieurs threads. Vous pouvez casser certaines cartes existantes et ainsi de suite. Si vous le changez localement, c'est moins magique, mais vous violez presque sûrement la symétrie dans les contrats d'égalité et de hachage, c'est-à-dire pour tous les objets o1 et 2o, o1.equals(o2) implique o2.equals(o1). Vous pouvez comparer des comparateurs (par ex.[1]), il gardera au moins le contrat. Bien qu'il garde le comtract, c'est moche.

[1]

def equals(o: Object) = o match { 
    case that: MyClass => 
     (that.comparator == this.comparator) && comparator.compare(this, that) 
    case _ => false // for null values and other classes 
} 
0

Ceci est une amélioration de la première solution proposée par @cheeken. Je suggère chaudement de ne pas adopter la seconde, sauf si vous travaillez sur un projet trivial. Avec la deuxième approche, vous ne pouvez pas faire en sorte que tous les éléments que vous avez placés dans la carte aient leur hachage calculé avec le même hachage, ce qui peut conduire à un comportement incorrect et inattendu difficile à expliquer à l'exécution.

La bonne façon de faire est de prendre ispiration de la HashMap dans la bibliothèque Scala:

@SerialVersionUID(2L) 
class HashMap[A, +B] extends Map[A,B] with MapLike[A, B, HashMap[A, B]] with Serializable with CustomParallelizable[(A, B), ParHashMap[A, B]] { 

    override def size: Int = 0 

    override def empty = HashMap.empty[A, B] 

    def iterator: Iterator[(A,B)] = Iterator.empty 

    override def foreach[U](f: ((A, B)) => U): Unit = { } 

    def get(key: A): Option[B] = 
    get0(key, computeHash(key), 0) 

    override def updated [B1 >: B] (key: A, value: B1): HashMap[A, B1] = 
    updated0(key, computeHash(key), 0, value, null, null) 

    override def + [B1 >: B] (kv: (A, B1)): HashMap[A, B1] = 
    updated0(kv._1, computeHash(kv._1), 0, kv._2, kv, null) 

    override def + [B1 >: B] (elem1: (A, B1), elem2: (A, B1), elems: (A, B1) *): HashMap[A, B1] = 
    this + elem1 + elem2 ++ elems 
    // TODO: optimize (might be able to use mutable updates) 

    def - (key: A): HashMap[A, B] = 
    removed0(key, computeHash(key), 0) 

    protected def elemHashCode(key: A) = key.## 

    protected final def improve(hcode: Int) = { 
    var h: Int = hcode + ~(hcode << 9) 
    h = h^(h >>> 14) 
    h = h + (h << 4) 
    h^(h >>> 10) 
    } 

    private[collection] def computeHash(key: A) = improve(elemHashCode(key)) 

    protected type Merger[B1] = ((A, B1), (A, B1)) => (A, B1) 

    private[collection] def get0(key: A, hash: Int, level: Int): Option[B] = None 

    private[collection] def updated0[B1 >: B](key: A, hash: Int, level: Int, value: B1, kv: (A, B1), merger: Merger[B1]): HashMap[A, B1] = 
    new HashMap.HashMap1(key, hash, value, kv) 

    protected def removed0(key: A, hash: Int, level: Int): HashMap[A, B] = this 

    protected def writeReplace(): AnyRef = new HashMap.SerializationProxy(this) 

    def split: Seq[HashMap[A, B]] = Seq(this) 

    def merge[B1 >: B](that: HashMap[A, B1], merger: Merger[B1] = null): HashMap[A, B1] = merge0(that, 0, merger) 

    protected def merge0[B1 >: B](that: HashMap[A, B1], level: Int, merger: Merger[B1]): HashMap[A, B1] = that 

    override def par = ParHashMap.fromTrie(this) 

} 

Si vous regardez, vous pouvez simplement écrire la classe suivante:

class CustomHashMap[A,+B](val hashCalculator:HashCalculator[A]) extends HashMap[A,B] { 
    //protected def elemHashCode(key: A) = key.## 
    override def elemHashCode(key: A) = hashCalculator(key) 
} 

Vous devez être Assurez-vous que toutes les méthodes publiques se comportent correctement, y compris par (vous devez implémenter une carte de hachage parallèle qui utilise votre hachage spécial) et fusionner, ainsi que le vide, qui ne doit pas renvoyer HashMap.empty[A,B] mais CustomHashMap.empty[A,B]

+0

Vous abordez seulement la partie du code de hachage, qu'en est-il de 'equals'? Aussi, vous dites "Avec la deuxième approche, vous ne pouvez pas faire en sorte que tous les objets que vous mettez dans la carte aient leur hash calculé avec le même hasher" et je ne peux pas vous suivre tout à fait. Sûrement dans un 'HashMap [KeyableAdapter1, T]' vous êtes à peu près sûr que le code de hachage utilisé provient de 'KeyableAdapter1', n'est-ce pas? –

+0

HashMap [A, B] ne modifie pas ses paramètres de type, mais obtient un hashCalculator personnalisé en tant que propriété immutable. Vous ne voulez pas changer le HashMap [A, B] dans un HashMap [KeyableAdapter1, T] ou l'itération, le mapping et le mapping changeront aussi la sémantique – Edmondo1984

+0

Et le HashCalculator peut être HashAndEqualsCalculator si vous préférez – Edmondo1984