Recipes for equals and hashCode

Một phần của tài liệu Artima programming in scala 2nd (Trang 703 - 709)

Step 12. Read lines from a file

30.4 Recipes for equals and hashCode

In this section, we’ll provide step-by-step recipes for creating equalsand

hashCodemethods that should suffice for most situations. As an illustration, we’ll use the methods of class Rational, shown inListing 30.5. To create this class, we removed the mathematical operator methods from the version of classRationalshown inListing 6.5onpage 155. We also made a minor enhancement totoStringand modified the initializers ofnumeranddenom

to normalize all fractions to have a positive denominator (i.e., to transform

1

−2to −12 ). Here’s the recipe for overridingequals:

1. If you’re going to override equals in a non-final class, you should create a canEqualmethod. If the inherited definition of equals is fromAnyRef(that is,equalswas not redefined higher up in the class

Section 30.4 Chapter 30 ã Object Equality 704

class Rational(n: Int, d: Int) { require(d != 0)

private val g = gcd(n.abs, d.abs) val numer = (if (d < 0) -n else n) / g val denom = d.abs / g

private def gcd(a: Int, b: Int): Int = if (b == 0) a else gcd(b, a % b)

override def equals(other: Any): Boolean = other match {

case that: Rational =>

(that canEqual this) &&

numer == that.numer &&

denom == that.denom case _ => false }

def canEqual(other: Any): Boolean = other.isInstanceOf[Rational]

override def hashCode: Int =

41 * (

41 + numer ) + denom

override def toString =

if (denom == 1) numer.toString else numer +"/"+ denom }

Listing 30.5ãClassRationalwithequalsandhashCode.

Section 30.4 Chapter 30 ã Object Equality 705 hierarchy), the definition ofcanEqualwill be new, otherwise it will override a previous definition of a method with the same name. The only exception to this requirement is for final classes that redefine the

equalsmethod inherited fromAnyRef. For them the subclass anoma- lies described inSection 30.2cannot arise; consequently they need not definecanEqual. The type of the object passed tocanEqualshould beAny:

def canEqual(other: Any): Boolean =

2. The canEqual method should yield true if the argument object is an instance of the current class (i.e., the class in which canEqualis defined),falseotherwise:

other.isInstanceOf[Rational]

3. In the equalsmethod, make sure you declare the type of the object passed as anAny:

override def equals(other: Any): Boolean =

4. Write the body of the equalsmethod as a singlematchexpression.

The selector of thematchshould be the object passed toequals:

other match { // ...

}

5. Thematchexpression should have two cases. The first case should de- clare a typed pattern for the type of the class on which you’re defining theequalsmethod:

case that: Rational =>

Section 30.4 Chapter 30 ã Object Equality 706 6. In the body of this case, write an expression that logical-ands to-

gether the individual expressions that must be true for the objects to be equal. If theequalsmethod you are overriding is not that ofAnyRef, you will most likely want to include an invocation of the superclass’s

equalsmethod:

super.equals(that) &&

If you are definingequalsfor a class that first introducedcanEqual, you should invokecanEqualon the argument to the equality method, passingthisas the argument:

(that canEqual this) &&

Overriding redefinitions ofequalsshould also include thecanEqual invocation, unless they contain a call tosuper.equals. In the latter case, the canEqual test will already be done by the superclass call.

Lastly, for each field relevant to equality, verify that the field in this object is equal to the corresponding field in the passed object:

numer == that.numer &&

denom == that.denom

7. For the second case, use a wildcard pattern that yields false:

case _ => false

If you adhere to the preceding recipe, equality is guaranteed to be an equiv- alence relation, as is required by theequalscontract.

ForhashCode, you can usually achieve satisfactory results if you use the following recipe, which is similar to a recipe recommended for Java classes inEffective Java.8 Include in the calculation each field in your object that is used to determine equality in theequals method (the “relevant” fields).

For each relevant field, no matter its type, you can calculate a hash code by invokinghashCodeon it. To calculate a hash code for the entire object, add

8Bloch,Effective Java Second Edition. [Blo08]

Section 30.4 Chapter 30 ã Object Equality 707 41 to the first field’s hash code, multiply that by 41, add the second field’s hash code, multiply that by 41, add the third field’s hash code, multiply that by 41, and so on, until you’ve done this for all relevant fields.

For example, to implement the hash code for an object that has five rele- vant fields nameda,b,c,d, ande, you would write:

override def hashCode: Int =

41 * (

41 * (

41 * (

41 * (

41 + a.hashCode ) + b.hashCode ) + c.hashCode ) + d.hashCode ) + e.hashCode

If you wish, you can leave off the hashCode invocation on fields of type

Int, Short, Byte, andChar. The hash code for anIntis the value of the

Int, as are the hash codes ofShorts,Bytes, andChars when automatically widened toInt. GivennumerordenomareInts, therefore, we implemented

Rational’shashCodemethod like this:

override def hashCode: Int =

41 * (

41 + numer ) + denom

The number 41 was selected for the multiplications because it is an odd prime. You could use another number, but it should be an odd prime to mini- mize the potential for information loss on overflow. The reason we added 41 to the innermost value is to reduce the likelihood that the first multiplication will result in zero, under the assumption that it is more likely the first field used will be zero than−41. The number 41 was chosen for the addition only for looks. You could use any non-zero integer.

If theequalsmethod invokessuper.equals(that)as part of its cal- culation, you should start your hashCodecalculation with an invocation of

super.hashCode. For example, had Rational’sequals method invoked

super.equals(that), itshashCodewould have been:

Section 30.4 Chapter 30 ã Object Equality 708

override def hashCode: Int =

41 * (

41 * (

super.hashCode ) + numer

) + denom

One thing to keep in mind as you write hashCodemethods using this approach is that your hash code will only be as good as the hash codes you build it out of, namely the hash codes you obtain by calling hashCodeon the relevant fields of your object. Sometimes you may need to do something extra besides just callinghashCodeon the field to get a useful hash code for that field. For example, if one of your fields is a collection, you probably want a hash code for that field that is based on all the elements contained in the collection. If the field is a List,Set, Map, or tuple, you can simply call hashCodeon the field, becauseequals andhashCodeare overridden in those classes to take into account the contained elements. However the same is not true forArrays, which do not take elements into account when calculating a hash code. Thus for an array, you should treat each element of the array like an individual field of your object, callinghashCodeon each element explicitly, or passing the array to one of thehashCodemethods in singleton objectjava.util.Arrays.

Lastly, if you find that a particular hash code calculation is harming the performance of your program, you can consider caching the hash code. If the object is immutable, you can calculate the hash code when the object is created and store it in a field. You can do this by simply overridinghashCode with avalinstead of adef, like this:

override val hashCode: Int =

41 * (

41 + numer ) + denom

This approach trades off memory for computation time, because each in- stance of the immutable class will have one more field to hold the cached hash code value.

Section 30.5 Chapter 30 ã Object Equality 709

Một phần của tài liệu Artima programming in scala 2nd (Trang 703 - 709)

Tải bản đầy đủ (PDF)

(883 trang)