2015-08-10 16 views
6

Mam zdefiniowaną poniżej klasę przypadku Foo. Chcę zastąpić zachowanie == w tym, aby ostatni element (optBar) został zignorowany w porównaniu. Oto, co próbowałem i wydaje się działać.Jak zdefiniować niestandardową równość w klasach przypadków

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 

Co chcę wiedzieć, czy metoda tworzenia hashCode jest poprawna. Mam go od this link.

Odpowiedz

9

Twoja definicja hashCode jest poprawna, ponieważ jest zgodna z umową equals/hashCode. Ale myślę, że łatwiej jest przeczytać.

Aby wyjaśnić, co to robi: ## to po prostu convenience method on scala.Any, który wywołuje hashCode, ale poprawnie zajmuje się pustymi i niektórymi narożnymi przypadkami związanymi z prymitywami.

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

So (l, s). ## tworzy się krotki i i S (który ma dobrze określony sposób hashCode), a następnie powraca jego kod skrótu. Więc nie musisz ręcznie pisać metody kodu haszującego z użyciem MurmurHash itp. Przy okazji: to również będzie działało poprawnie, jeśli jeden z elementów krotki ma wartość null, podczas gdy ręcznie pisana metoda mieszania, taka jak ta w pytaniu może rzucić NPE.

Jednak z mojego doświadczenia wynika, że ​​jeśli chcesz zmodyfikować którąś z rzeczy, które oferuje ci klasa spraw, to nie chcesz klasę przypadku. Nadmierna równość, aby nie brać pod uwagę niektórych danych, może w pewnym momencie wydawać się sprytnym pomysłem, ale może prowadzić do bardzo mylących zachowań.

+0

Zgadzam się z częścią "prowadzącą do dezorientujących zachowań", więc zastanawiam się, czy naprawdę mogę się bez niej obejść. Mogę, ale muszę dodać dodatkową logikę, aby zająć się ostatnim polem w 'Foo'. Muszę zważyć opcje. – Jus12

+3

@ Jus12 Możesz napisać 'case class Foo (i: Int, s: String) (optBar: Option [Bar])'. Druga lista parametrów nie jest uważana za część generowanych metod klasy przypadków. – sschaef

+0

Zaakceptowana jako poprawna odpowiedź, ponieważ 'override def hashCode = (i, s). ##' jest znacznie ładniejsze. Czy możesz rozwinąć to, co robi? :) – Jus12

1

Jak korzystać z innego operatora dla własnej wersji równości. Myślę, że jest to ładniejsze niż nadpisywanie domyślnego zachowania == np. ~= jako „w przybliżeniu równe”

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:

Jeśli chcesz, aby móc korzystać z tego jako klucze mapie, a następnie będę próbować:

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

Muszę użyć tego w kolekcjach. Na przykład 'listOfFoos.contains (foo1)'. Czy jest jakiś sposób, żeby to działało na mój przypadek użycia? – Jus12

+0

Można użyć 'listOfFoos.exists (_ = ~ foo1)', ale zdaję sobie sprawę, że 'zawiera' jest tylko przykładem i mogą być również inne metody, które opierają się na' == 'pod maską.Co powiesz na zrobienie 'i' i' s' w innej klasie case, która jest wtedy członkiem 'Foo', więc możesz zrobić coś w stylu' listOfFoos.map (_. SubFoo) .contains (foo1.subFoo) '? – mattinbits

+0

Mój błąd za niewłaściwy przypadek użycia. Jak poprawnie powiedziałeś, możemy * możemy * użyć tego w 'zawiera', etcetera, ale czy możemy użyć tego na mapie, jak w' mapWithKeyFee.get (foo1) '? – Jus12

1

Można również usunąć optBar z definicji klasy sprawy i utwórz konstruktor z trzema parametrami. Aby uniknąć konieczności używania słowa kluczowego new, gdy chcesz użyć tego konstruktora, możesz utworzyć obiekt towarzyszący.

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

Też lubię to, ale wydaje się być trochę hackish. +1 ode mnie. :) – Jus12

Powiązane problemy