在平等规则之间切换

时间:2013-01-20 00:46:08

标签: java scala equality

我有一个类和不同的等价规则(equalshashCode的不同实现)。首先在一个进程中生成数据,其中应用一个等价规则,然后将其提供给第二个进程,其中应用另一个等价规则。特别是,我正在进行大量的map操作,equalshashCode由标准库隐式调用(我无法控制)。您认为实现这一目标的最佳方式是什么?我现在有两个解决方案:

  1. 使用不同的equalshashCode定义两个子类。在进程1之后,通过启动另一个子类的对象来进行转换。
  2. 在类中引入可变状态以指示要应用的等效规则。
  3. 那么您认为哪一个更好还是有其他好的解决方案?

4 个答案:

答案 0 :(得分:4)

可能更优雅的解决方案是自定义Map类,它允许自定义散列和相等评估。

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
}

在您的方案中,您将创建两个自定义MappingScheme并在CustomSchemeMap中使用它们。这种方法比你建议的解决方案更高效(没有额外的实例创建,你不必改变对象),但它也更符合逻辑,更容易理解。


但是,实施Map可能是一项艰巨的任务。如果这似乎遥不可及,我会创建简单的适配器类来包装你的对象并将它们提供给地图。

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)

这与子类化方法类似,不同之处在于您可以通过get()返回原始对象,并且更容易理解(至少在我看来)。

答案 1 :(得分:1)

  

使用不同的equals和hashCode定义两个子类。在进程1之后,通过启动另一个子类的对象来进行转换。

这是正确的,但我认为这两个类在语义上没有区别。它们只会在另一种情况下使用,尽管它们代表相同。

  

在类中引入可变状态以指示要应用的等效规则。

永远不要这样做,这是破碎的:

  • 如果你在全局范围内改变状态,这可能会带来很多麻烦,特别是如果你在更多的线程中使用这些类。你可以打破一些现有的地图等等。
  • 如果你在本地更改它,它就不那么神奇了,但你几乎肯定违反了equals和hashCode契约中的symetry,即对于所有对象o1和2o,o1.equals(o2)暗示o2.equals(o1)。您可以比较比较器(例如[1]),它至少会保留合同。虽然它保留了合同,但它很难看。

[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
}

答案 2 :(得分:0)

这是@cheeken提出的第一个解决方案的改进。我热烈建议不要采用第二种,除非你正在进行一项琐碎的项目。使用第二种方法,您无法强制执行放在地图中的所有项目都使用相同的哈希计算其哈希值,这可能会导致错误和意外的行为,这在运行时很难解释。

正确的方法是从Scala库中的HashMap中获取灵感:

@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)

}

如果你看,你可以写下面的课:

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)
}

你必须确保你所有的公共方法都正常运行,包括par(你需要实现一个使用你的特殊hasher的并行哈希映射)和merge,以及empty,它不应该返回 HashMap.empty[A,B]CustomHashMap.empty[A,B]

答案 3 :(得分:0)

最后我发现编写自己的自定义Map是可行的方法(至少在我的问题中)。在我深入scala标准库一段时间后,我发现它非常容易。无论是否可变,HashMap中的元素相等和hashCode方法都继承自HashTableHashTable.Utils并受到保护,这意味着任何子类都可以轻松覆盖它。以下是我最终的结果:

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)
}

我做了一个简单的测试,效果很好。