ScalaEquals
ScalaEquals copied to clipboard
ScalaEquals. Never look up an equals/hashCode recipe again!
ScalaEquals
ScalaEquals provides easy to use macros for generating correct equals
/hashCode
/canEqual
implementations,
never look up an equals
/hashCode
recipe again! The methods generated from ScalaEquals are taken directly
from Programming in Scala and strictly obey the contract of equals
and hashCode
, and unlike
case classes, the generated methods work as expected with sub-classing. As a bonus, the macros also check that
equals
/hashCode
/canEqual
/toString
are defined correctly; they will catch misspellings, incorrect
types, etc.
In the documentation and in this README, anywhere ScalaEquals.equal
is seen it is assumed that
ScalaEquals.equalAllVals
also applies, unless otherwise stated. Additionally, in the documentation the
argument names are assumed to be other
, but this is not a requirement, you may name the parameters
however you would like, the macros will find the name of the parameter and use it in the expansion.
Downloading
You can download ScalaEquals directly from Sonatype, or to use with sbt, add the following to your project file:
libraryDependencies += "org.scalaequals" %% "scalaequals-core" % "1.2.0"
How To Use
Equals Using All val
s In Constructor
class Point(val x: Int, val y: Int) {
override def equals(other: Any): Boolean = ScalaEquals.equal
override def hashCode(): Int = ScalaEquals.hash
def canEqual(other: Any): Boolean = ScalaEquals.canEquals
override def toString: String = ScalaEquals.genString // returns "Point(x, y)"
}
Equals Using All val
s In Constructor And Body
// TIP: Statically importing ScalaEquals will make the methods look even cleaner!
import org.scalaequals.ScalaEquals._
class Point(_x: Int, val y: Int) {
val x: Int = _x
override def equals(other: Any): Boolean = equalAllVals
override def hashCode(): Int = hash
def canEqual(other: Any): Boolean = canEquals
override def toString: String = genString // returns "Point(_x, y)"
}
Equals Using User-Defined Fields
import org.scalaequals.ScalaEquals._
import org.scalaequals.ScalaEqualsExtend
class Point(_x: Int, var y: Int) {
def x: Int = _x
override def equals(other: Any): Boolean = ScalaEqualsExtend.equal(x, y)
override def hashCode(): Int = hash
def canEqual(other: Any): Boolean = canEquals
override def toString: String = genString(x, y) // returns "Point(x, y)"
}
Details
-
Every
equal
method will usecanEqual
if it is defined. -
Every
equal
method will usesuper.equals(that)
if a super class that is notAnyRef
orObject
overridesequals
. -
ScalaEquals.equal
will use allval
s in constructor that are not inherited from a parent class, i.e.val
,protected val
,private val
, but not anything qualified withoverride
. -
ScalaEquals.equalAllVals
will use allval
s in constructor AND body of class, subject to the same constraints as above. -
ScalaEqualsExtend.equal(params)
will use only the fields specified inparams
(as well assuper.equals
if applicable). Valid arguments includeval
,var
,lazy val
, anddef
that take no arguments. Any access modifier is allowed, and unlikeequal
andequalAllVals
, arguments inherited from a super class and/or qualified withoverride
may also be used. Use at your own risk -
ScalaEquals.hash
will use exactly the values checked inequals
-
ScalaEquals.hash
will usesuper.hashCode()
if and only ifsuper.equals(that)
is called inequals
. -
ScalaEquals.hash
will work with lazy hashCode, i.e. -override lazy val hashCode: Int = ScalaEquals.hash
-
ScalaEquals.canEquals
is a simple macro that converts toother.isInstanceOf[Class]
-
ScalaEquals.genString
uses all constructor parameters in the generated string.ScalaEquals.genString(params)
works identically toScalaEquals.equal(params)
. -
Works with classes, traits, abstract classes and generic variants (parameterized and with abstract type members). As always, be careful about initialization order when using traits and abstract classes.
-
ScalaEqualsExtend
contains various additional flavors of theequals
/hashCode
macros that if used incorrectly will not produce implementations that obey the contract. Use at your own risk. -
Use
-Xmacro-settings:scala-equals-no-warn
to silence warnings from macros.
Example Macro Expansion
import org.scalaequals.ScalaEquals._
class Point(val x: Int, val y: Int) {
override def equals(other: Any): Boolean = equal
override def hashCode(): Int = hash
def canEqual(other: Any): Boolean = canEquals
override def toString: String = genString
}
becomes
class Point(val x: Int, val y: Int) {
override def equals(other: Any): Boolean = other match {
case that: Point => (that canEqual this) && that.x == this.x && that.y == this.y
case _ => false
}
override def hashCode(): Int = MurmurHash3.seqHash(List(x, y))
def canEqual(other: Any): Boolean = other.isInstanceOf[Point]
override def toString: String = "Point(" + x + ", " + y + ")"
}
Additional Features
Equals Without Using compareTo
for Double
/Float
values
ScalaEqualsExtend.equalNoCompareTo
ScalaEqualsExtend.equalAllValsNoCompareTo
ScalaEqualsExtend.equalNoCompareTo(param, params...)
Hash using a custom hash function of type Array[Any] => Int
import org.scalaequals.ScalaEquals
import org.scalaequals.ScalaEqualsExtend
class Point(val x: Int, val y: Int) {
override def equals(other: Any): Boolean = ScalaEquals.equal
override def hashCode(): Int = ScalaEqualsExtend.hash(myCustomHashFunction)
def canEqual(other: Any): Boolean = ScalaEquals.canEquals
override def toString: String = ScalaEquals.genString
}
Feedback
Questions or Comments?
Want to tell me how awesome (or horrible) ScalaEquals is? Send me an email!
Found a Bug? Want a New Feature?
Add issues or feature requests here on github at the issue tracker, alternatively fork the project and submit a pull request.
Release Notes
View complete release notes.
Equals Contract
• It is reflexive: for any non-null value x, the expression x.equals(x) should return true.
• It is symmetric: for any non-null values x and y, x.equals(y) should return true if and only if y.equals(x) returns true.
• It is transitive: for any non-null values x, y, and z, if x.equals(y) re-turns true and y.equals(z) returns true, then x.equals(z) should return true.
• It is consistent: for any non-null values x and y, multiple invocations of x.equals(y) should consistently return true or consistently return false, provided no information used in equals comparisons on the objects is modified.
• For any non-null value x, x.equals(null) should return false.
• For any non-null values x and y, if x.equals(y) returns true then x.hashCode() == y.hashCode() should return true.
Testing
All implementations have been thoroughly tested using ScalaCheck
. Check out
the core-test
project for details, specifically check out the documentation for
EqualsFixture
for exact testing methodology. If you find a problem, please
submit an issue! As always, even when the implementation is perfect, it is good to
sanity check your own code to ensure that the logic of equals
/hashCode
is defined
how you want it to be.