- •Contents
- •List of Figures
- •List of Tables
- •List of Listings
- •Foreword
- •Foreword to the First Edition
- •Acknowledgments
- •Introduction
- •A Scalable Language
- •A language that grows on you
- •What makes Scala scalable?
- •Why Scala?
- •Conclusion
- •First Steps in Scala
- •Conclusion
- •Next Steps in Scala
- •Conclusion
- •Classes and Objects
- •Semicolon inference
- •Singleton objects
- •A Scala application
- •Conclusion
- •Basic Types and Operations
- •Some basic types
- •Literals
- •Operators are methods
- •Arithmetic operations
- •Relational and logical operations
- •Bitwise operations
- •Object equality
- •Operator precedence and associativity
- •Rich wrappers
- •Conclusion
- •Functional Objects
- •Checking preconditions
- •Self references
- •Auxiliary constructors
- •Method overloading
- •Implicit conversions
- •A word of caution
- •Conclusion
- •Built-in Control Structures
- •If expressions
- •While loops
- •For expressions
- •Match expressions
- •Variable scope
- •Conclusion
- •Functions and Closures
- •Methods
- •Local functions
- •Short forms of function literals
- •Placeholder syntax
- •Partially applied functions
- •Closures
- •Special function call forms
- •Tail recursion
- •Conclusion
- •Control Abstraction
- •Reducing code duplication
- •Simplifying client code
- •Currying
- •Writing new control structures
- •Conclusion
- •Composition and Inheritance
- •A two-dimensional layout library
- •Abstract classes
- •Extending classes
- •Invoking superclass constructors
- •Polymorphism and dynamic binding
- •Using composition and inheritance
- •Heighten and widen
- •Putting it all together
- •Conclusion
- •How primitives are implemented
- •Bottom types
- •Conclusion
- •Traits
- •How traits work
- •Thin versus rich interfaces
- •Example: Rectangular objects
- •The Ordered trait
- •Why not multiple inheritance?
- •To trait, or not to trait?
- •Conclusion
- •Packages and Imports
- •Putting code in packages
- •Concise access to related code
- •Imports
- •Implicit imports
- •Package objects
- •Conclusion
- •Assertions and Unit Testing
- •Assertions
- •Unit testing in Scala
- •Informative failure reports
- •Using JUnit and TestNG
- •Property-based testing
- •Organizing and running tests
- •Conclusion
- •Case Classes and Pattern Matching
- •A simple example
- •Kinds of patterns
- •Pattern guards
- •Pattern overlaps
- •Sealed classes
- •The Option type
- •Patterns everywhere
- •A larger example
- •Conclusion
- •Working with Lists
- •List literals
- •The List type
- •Constructing lists
- •Basic operations on lists
- •List patterns
- •First-order methods on class List
- •Methods of the List object
- •Processing multiple lists together
- •Conclusion
- •Collections
- •Sequences
- •Sets and maps
- •Selecting mutable versus immutable collections
- •Initializing collections
- •Tuples
- •Conclusion
- •Stateful Objects
- •What makes an object stateful?
- •Reassignable variables and properties
- •Case study: Discrete event simulation
- •A language for digital circuits
- •The Simulation API
- •Circuit Simulation
- •Conclusion
- •Type Parameterization
- •Functional queues
- •Information hiding
- •Variance annotations
- •Checking variance annotations
- •Lower bounds
- •Contravariance
- •Object private data
- •Upper bounds
- •Conclusion
- •Abstract Members
- •A quick tour of abstract members
- •Type members
- •Abstract vals
- •Abstract vars
- •Initializing abstract vals
- •Abstract types
- •Path-dependent types
- •Structural subtyping
- •Enumerations
- •Case study: Currencies
- •Conclusion
- •Implicit Conversions and Parameters
- •Implicit conversions
- •Rules for implicits
- •Implicit conversion to an expected type
- •Converting the receiver
- •Implicit parameters
- •View bounds
- •When multiple conversions apply
- •Debugging implicits
- •Conclusion
- •Implementing Lists
- •The List class in principle
- •The ListBuffer class
- •The List class in practice
- •Functional on the outside
- •Conclusion
- •For Expressions Revisited
- •For expressions
- •The n-queens problem
- •Querying with for expressions
- •Translation of for expressions
- •Going the other way
- •Conclusion
- •The Scala Collections API
- •Mutable and immutable collections
- •Collections consistency
- •Trait Traversable
- •Trait Iterable
- •Sets
- •Maps
- •Synchronized sets and maps
- •Concrete immutable collection classes
- •Concrete mutable collection classes
- •Arrays
- •Strings
- •Performance characteristics
- •Equality
- •Views
- •Iterators
- •Creating collections from scratch
- •Conversions between Java and Scala collections
- •Migrating from Scala 2.7
- •Conclusion
- •The Architecture of Scala Collections
- •Builders
- •Factoring out common operations
- •Integrating new collections
- •Conclusion
- •Extractors
- •An example: extracting email addresses
- •Extractors
- •Patterns with zero or one variables
- •Variable argument extractors
- •Extractors and sequence patterns
- •Extractors versus case classes
- •Regular expressions
- •Conclusion
- •Annotations
- •Why have annotations?
- •Syntax of annotations
- •Standard annotations
- •Conclusion
- •Working with XML
- •Semi-structured data
- •XML overview
- •XML literals
- •Serialization
- •Taking XML apart
- •Deserialization
- •Loading and saving
- •Pattern matching on XML
- •Conclusion
- •Modular Programming Using Objects
- •The problem
- •A recipe application
- •Abstraction
- •Splitting modules into traits
- •Runtime linking
- •Tracking module instances
- •Conclusion
- •Object Equality
- •Equality in Scala
- •Writing an equality method
- •Recipes for equals and hashCode
- •Conclusion
- •Combining Scala and Java
- •Using Scala from Java
- •Annotations
- •Existential types
- •Using synchronized
- •Compiling Scala and Java together
- •Conclusion
- •Actors and Concurrency
- •Trouble in paradise
- •Actors and message passing
- •Treating native threads as actors
- •Better performance through thread reuse
- •Good actors style
- •A longer example: Parallel discrete event simulation
- •Conclusion
- •Combinator Parsing
- •Example: Arithmetic expressions
- •Running your parser
- •Basic regular expression parsers
- •Another example: JSON
- •Parser output
- •Implementing combinator parsers
- •String literals and regular expressions
- •Lexing and parsing
- •Error reporting
- •Backtracking versus LL(1)
- •Conclusion
- •GUI Programming
- •Panels and layouts
- •Handling events
- •Example: Celsius/Fahrenheit converter
- •Conclusion
- •The SCells Spreadsheet
- •The visual framework
- •Disconnecting data entry and display
- •Formulas
- •Parsing formulas
- •Evaluation
- •Operation libraries
- •Change propagation
- •Conclusion
- •Scala Scripts on Unix and Windows
- •Glossary
- •Bibliography
- •About the Authors
- •Index
Section 30.4 |
Chapter 30 · Object Equality |
703 |
class Branch[T]( val elem: T,
val left: Tree[T], val right: Tree[T]
)extends Tree[T] {
override def equals(other: Any) = other match { case that: Branch[_] => (that canEqual this) &&
this.elem == that.elem && this.left == that.left && this.right == that.right
case _ => false
}
def canEqual(other: Any) = other.isInstanceOf[Branch[_]]
override def hashCode: Int = 41 * (
41 * (
41 + elem.hashCode ) + left.hashCode
) + right.hashCode
}
Listing 30.4 · A parameterized type with equals and hashCode.
30.4 Recipes for equals and hashCode
In this section, we’ll provide step-by-step recipes for creating equals and hashCode methods that should suffice for most situations. As an illustration, we’ll use the methods of class Rational, shown in Listing 30.5. To create this class, we removed the mathematical operator methods from the version of class Rational shown in Listing 6.5 on page 155. We also made a minor enhancement to toString and modified the initializers of numer and denom to normalize all fractions to have a positive denominator (i.e., to transform12 to 21 ). Here’s the recipe for overriding equals:
1.If you’re going to override equals in a non-final class, you should create a canEqual method. If the inherited definition of equals is from AnyRef (that is, equals was not redefined higher up in the class
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
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 · Class Rational with equals and hashCode.
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Section 30.4 |
Chapter 30 · Object Equality |
705 |
hierarchy), the definition of canEqual will 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 equals method inherited from AnyRef. For them the subclass anomalies described in Section 30.2 cannot arise; consequently they need not define canEqual. The type of the object passed to canEqual should be Any:
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 canEqual is defined), false otherwise:
other.isInstanceOf[Rational]
3.In the equals method, make sure you declare the type of the object passed as an Any:
override def equals(other: Any): Boolean =
4.Write the body of the equals method as a single match expression. The selector of the match should be the object passed to equals:
other match { // ...
}
5.The match expression should have two cases. The first case should declare a typed pattern for the type of the class on which you’re defining the equals method:
case that: Rational =>
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Section 30.4 |
Chapter 30 · Object Equality |
706 |
6.In the body of this case, write an expression that logical-ands together the individual expressions that must be true for the objects to be equal. If the equals method you are overriding is not that of AnyRef, you will most likely want to include an invocation of the superclass’s equals method:
super.equals(that) &&
If you are defining equals for a class that first introduced canEqual, you should invoke canEqual on the argument to the equality method, passing this as the argument:
(that canEqual this) &&
Overriding redefinitions of equals should also include the canEqual invocation, unless they contain a call to super.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 equivalence relation, as is required by the equals contract.
For hashCode, you can usually achieve satisfactory results if you use the following recipe, which is similar to a recipe recommended for Java classes in Effective Java.8 Include in the calculation each field in your object that is used to determine equality in the equals method (the “relevant” fields). For each relevant field, no matter its type, you can calculate a hash code by invoking hashCode on it. To calculate a hash code for the entire object, add
8Bloch, Effective Java Second Edition. [Blo08]
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
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 relevant fields named a, b, c, d, and e, 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, and Char. The hash code for an Int is the value of the Int, as are the hash codes of Shorts, Bytes, and Chars when automatically widened to Int. Given numer or denom are Ints, therefore, we implemented Rational’s hashCode method 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 minimize 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 the equals method invokes super.equals(that) as part of its calculation, you should start your hashCode calculation with an invocation of super.hashCode. For example, had Rational’s equals method invoked super.equals(that), its hashCode would have been:
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
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 hashCode methods 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 hashCode on the relevant fields of your object. Sometimes you may need to do something extra besides just calling hashCode on 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 hashCode on the field, because equals and hashCode are overridden in those classes to take into account the contained elements. However the same is not true for Arrays, 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, calling hashCode on each element explicitly, or passing the array to one of the hashCode methods in singleton object java.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 overriding hashCode with a val instead of a def, like this:
override val hashCode: Int = 41 * (
41 + numer ) + denom
This approach trades off memory for computation time, because each instance of the immutable class will have one more field to hold the cached hash code value.
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index