2

I have the following case class

case class Something(val input : Array[Int], val output : Array[Int] = null, val remainder : Array[Int] = null, val next : Something = null) {
  override def equals(thatGeneric: scala.Any): Boolean = {
    if(!thatGeneric.isInstanceOf[Something])
      return false

    val that = thatGeneric.asInstanceOf[Something]

    val thisInput = if(this.input == null) null else this.input.deep
    val thatInput = if(that.input == null) null else that.input.deep

    val thisOutput = if(this.output == null) null else this.output.deep
    val thatOutput = if(that.output == null) null else that.output.deep

    val thisRemainder = if(this.remainder == null) null else this.remainder.deep
    val thatRemainder = if(that.remainder == null) null else that.remainder.deep

    return (thisInput,thisOutput,thisRemainder,this.next) == (thatInput,thatOutput,thatRemainder,that.next)
  }

  /**
    * TODO fix hashcode in case I want to use this in collection
    * @return
    */
  override def hashCode(): Int = super.hashCode()
}

I know that case classes are supposed to have your equals and hashCode methods created for you but for Arrays since == doesn't work then I guess thats why it doesn't work for my case class.

Is there a better way to not have to hand-write my equals and hashCode methods for my case class?

Note: I also welcome advice on why/how to improve my current equals method

Edit: Do appreciate the notes about my use of case class in general, but my question was basically: Assume you have the need of an Array in your case class, how do you avoid rolling your own equals and hashCode. (If the answer is that its impossible and I need to use a list then that is the answer, just not sure that is the case.)

Carlos Bribiescas
  • 4,197
  • 9
  • 35
  • 66
  • 2
    Is there a reason you want to use an `Array` and not a `List`? A case class traditionally is meant to hold an immutable data structure. – Michael Zajac Jul 13 '16 at 00:55

2 Answers2

3

Array is indeed lighter, but, unfortunately, not by much (scala doesn't reimplement most of the methods for the array, so they end up sharing the generic (sucky) scala collection performance properties.

As long as you are set on using arrays, you'll have to implement your own equals, not much that you can do about that, but you can make it look prettier, like this:

def eq[T](a: Array[T], b: Array[T]) = Option(a.toSeq) == Option(b.toSeq)
def equals(that: Something) = 
   eq(input, that.input) &&
   eq(output, that.output) &&
   eq(remainder, that.remainder) &&
   next == that.next

You should consider making your variable optional though. Like everybody says, null isn't going to score you any points in scala.

Also, again, like everybody else said, reconsider having your case class as a class: the whole point of using case classes is getting equals and hashCode (and some other stuff) for free. If you are going to reimplement all of that anyway, declaring it as a case class if just confusing, and has no benefit.

Yarek T
  • 9,715
  • 2
  • 28
  • 38
Dima
  • 39,570
  • 6
  • 44
  • 70
1

There seems to be a couple things wrong with this class definition. The use of nulls, vars, and Arrays.

As @m-z made mention, case classes are intended to be used as "data" classes. Data which should be immutable. If you don't want to have to roll your own equals and hashCode then you should rethink the implementation.

Note: Not sure what you are trying to use this class for but there might be a design pattern that probably fits your needs and adhere to the true use of case classes.

JRomero
  • 4,878
  • 1
  • 27
  • 49
  • Actually it is immutable, I originally had it as `var`s but they can be `val`s no problem. Not sure what you mean with the problem with `nulls` and `Array`s though. Can you explain? – Carlos Bribiescas Jul 13 '16 at 01:47
  • You might use a `val`, but the array itself is still mutable. Apparently you want the **content** of the array to be taken into account when computing the hashcode, but here lies the problem: this content can change, which would change the hashcode, and break the contract of the `hashCode` method ("Whenever it is invoked on the same object more than once during an execution of a Java application, the hashCode method must consistently return the same integer", see https://docs.oracle.com/javase/7/docs/api/java/util/Objects.html#hashCode(java.lang.Object)). – Régis Jean-Gilles Jul 13 '16 at 13:37
  • @RégisJean-Gilles it's not a matter of using a mutable type as arguments to your case class. For example, you can use `mutable.Seq` and case classes with the same content would still be equal. It's a matter of how equality is implemented for `Array` and `Iterator` which is referential equality. See http://stackoverflow.com/q/3737711/552902 – JRomero Jul 13 '16 at 17:49
  • Yes, I'm aware ^^. I was only replying to OP's comment above which indicates that he believes using a val makes the data immutable, while in reality only the reference is immutable, the array it points to is still very much mutable. That makes it risky to use arrays in case classes (and should thus be done with care) as it breaks the contract. The fact that additionaly arrays have reference equality is certainly more to the point of the original question, but that's not what I was replying to (otherwise I'd have posted it as an answer). – Régis Jean-Gilles Jul 27 '16 at 07:35