2015-08-10 2 views
6

У меня есть класс корпуса Foo, определенный ниже. Я хочу переопределить поведение == тем, что последний элемент (optBar) игнорируется при сравнении. Вот что я пробовал и, похоже, работает.Как определить пользовательское равенство в классах классов

case class Bar(i:Int) 
case class Foo(i:Int, s:String, optBar:Option[Bar]) { 
    override def equals(o:Any) = o match { 
     case Foo(`i`, `s`, _) => true 
     case _ => false 
    } 
    override def hashCode = i.hashCode*997^s.hashCode * 991 
} 
val b = Bar(1) 
val f1 = Foo(1, "hi", Some(b)) 
val f2 = Foo(1, "hi", None) 
f1 == f2 // true 

Что я хочу знать, это правильный способ создания hashCode. Я получил его от this link.

ответ

9

Ваше определение хэш-кода правильное, так как оно соответствует контракту equals/hashCode. Но я думаю,

override def hashCode = (i, s).## 

приятнее читать.

Чтобы прояснить, что это делает: ## - это просто convenience method on scala.Any, который вызывает hashCode, но правильно обрабатывает нулевые и некоторые угловые случаи, связанные с примитивами.

val x: String = null 
x.## // works fine. returns 0 
x.hashCode // throws NullPointerException 

Так (я, с). ## создает кортеж из I и S (который имеет четко определенный метод HashCode), а затем возвращает его хэш-код. Таким образом, вам не нужно вручную писать метод хеш-кода с участием MurmurHash и т. Д. Кстати, это также будет корректно работать, если один из элементов кортежа равен нулю, тогда как рукописный хэш-метод, подобный тому, который задан в вопросе может бросить NPE.

Однако, по моему опыту, если вы хотите изменить любую из вещей, которые предоставляет класс case, вам действительно не нужен класс case. Кроме того, переопределение равенства, чтобы не принимать во внимание некоторые данные, может показаться умной идеей в какой-то момент, но это может привести к некоторому очень запутанному поведению.

+0

Я согласен с тем, что «приводит к запутанному поведению», поэтому я пересматриваю, если действительно может обойтись без него. Я могу, но мне нужно добавить дополнительную логику, чтобы позаботиться о последнем поле в 'Foo'. Мне нужно взвесить варианты. – Jus12

+3

@ Jus12 Вы можете написать 'case class Foo (i: Int, s: String) (optBar: Option [Bar])'. Второй список параметров не считается частью генерируемых методов класса case. – sschaef

+0

Принимается как правильный ответ, потому что 'override def hashCode = (i, s). ##' намного приятнее. Можете ли вы рассказать о том, что он делает? :) – Jus12

1

Как насчет использования другого оператора для вашей собственной версии равенства. Я думаю, что это лучше, чем переопределение поведения по умолчанию ==, например. ~= как «примерно равны»

case class Bar(i:Int) 
case class Foo(i:Int, s:String, optBar:Option[Bar]) { 

    def ~= (that:Foo): Boolean = (this.i, this.s) == (that.i, that.s) 

} 

val foo1 = Foo(1, "a", None) 

val foo2 = Foo(1, "a", Some(Bar(4))) 

foo1 == foo2 //false 

foo1 ~= foo2 //true 

Edit:

Если вы хотите, чтобы иметь возможность использовать это в качестве ключей карты, то я хотел бы попробовать:

case class Bar(i: Int) 

trait FooLike { 
    def s: String 
    def i: Int 

    def ~=(that: FooLike) = (s, i) == (that.s, that.i) 
} 

case class SubFoo(s: String, i: Int) extends FooLike 

case class Foo(sub: SubFoo, barOpt: Option[Bar]) extends FooLike { 
    def s = sub.s 
    def i = sub.i 
} 

val map = scala.collection.mutable.Map.empty[SubFoo, String] 

val sub = SubFoo("a", 1) 

val foo = Foo(sub, None) 

foo ~= sub //true 

val foo2 = Foo(sub, Some(Bar(1))) 

foo ~= foo2 ///true 

map += sub -> "abc" 

map.get(foo.sub) //Some("abc") 
+0

Мне нужно использовать это в коллекциях. Например, 'listOfFoos.contains (foo1)'. Есть ли способ сделать эту работу для моего прецедента? – Jus12

+0

Вы можете использовать 'listOfFoos.exists (_ = ~ foo1)', но я понимаю, что 'contains' является просто примером, и могут быть и другие методы, которые полагаются на' == 'под капотом.Как сделать 'i' и' s' в другой класс case, который затем является членом 'Foo', поэтому вы можете сделать что-то вроде' listOfFoos.map (_. SubFoo) .contains (foo1.subFoo) '? – mattinbits

+0

Моя ошибка для неправильного использования. Как вы правильно сказали, мы * можем * использовать это в 'contains' и т. Д., Но можем ли мы использовать это на карте, как в' mapWithKeyFee.get (foo1) '? – Jus12

1

Вы можете также удалить optBar из определения класса case и создайте конструктор с тремя параметрами. Чтобы избежать использования ключевого слова new, когда вы хотите использовать этот конструктор, вы можете создать объект-компаньон.

case class Bar(i:Int) 
case class Foo(i:Int, s:String) { 
    var optBar: Option[Bar] = None 

    def this(i:Int, s:String, optBar:Option[Bar]) { 
    this(i, s) 
    this.optBar = optBar 
    } 
} 
object Foo { 
    def apply(i:Int, s:String, optBar:Option[Bar]) = 
    new Foo(i, s, optBar) 
} 

val b = Bar(1) 
val f1 = Foo(1, "hi", Some(b)) 
val f2 = Foo(1, "hi", None) 
f1 == f2 // true 
+0

Мне тоже нравится, но, похоже, немного хаки. +1 от меня. :) – Jus12

Смежные вопросы