Item 8: Overriding Equals
Don't override the equals method if
- Each instance of the class is inherently unique
- Superclass has overridden equals method
- When the package is private
override fun equals(other: Any?): Boolean {
throw AssertionError() // Method is never called
}
Override the equals method if
- Logical equality is important
Equals method implements an equivalence relation:
Reflexive: For any non-null reference value x,
x.equals(x)
must returntrue
Symmetric: For any non-null reference values x and y,
x.equals(y)
must returntrue
if and only ify.equals(x)
returnstrue
.Transitive: For any non-null reference values x, y, z,
if x.equals(y)
returnstrue
andy.equals(z)
returnstrue
, thenx.equals(z)
must returntrue
.Consistent: For any non-null reference values x and y,
multiple invocations of
x.equals(y)
consistently returntrue
or consistently returnfalse
, provided no information used in equals comparisons on the objects is modified.For any non-null reference value x,
x.equals(null)
must returnfalse
.
Do not write equals method on unreliable resources.
Steps to writing a high qualityequals
method
- Use the
===
operator to check if the argument is a reference to the object - Check if the
javaclass
of both the objects are the same - Cast the argument to the correct type
- Every significant field in the class needs to be checked for logical equality to the corresponding field of the object
- Check if the method obeys symmetry, transitivity and if it is consistent.
- Also, make sure to override the
hashCode
method.
In Kotlin, we get all this for free using the
data class
provided by default
Item 9: Overriding Hashcode
There is a rule to always overridehashCode
wheneverequals
is overridden. If this is not obeyed, then this would result in a violation of the contract of the Object.hashCode method and will prevent the class from functioning properly in conjunction with all the has-based collections.
The vital part of writing thehashCode
method relies on the fact thatequal objects must have equal hash codes. A good hashCode should always produce different hashcodes for unequal objects.
In Kotlin, we get all this for free using the
data class
provided by default
Steps to write a high qualityhashCode
method
- Store a constant non-zero value calculated from any attribute of the class using its superclass method
var result = fName.hashCode()
For each significant field f (All field defined in theequals
method), do the following:
- Compute an
Int hashCode c
for the field- Boolean:
f ?: 0
- Byte, Char, Short, Float, Double, Int:
f.toInt()
- Long:
(f ^ (f >>> 32)).toInt()
- The class's
equals
method compares the field by recursively invoking equals and invoke thehashCode
on the field. - Array: Each element is treated as a separate field by applying the rules recursively.
- Boolean:
In Kotlin, we get all this for free using the
data class
provided by default
Item 10: Overriding toString
A goodtoString
implementation makes the class much more pleasant to use. It clearly displays the most significant information required in a class object. If thetoString
method is not overridden, then printing the object would return the class name followed by the unsigned hexadecimal representation of the hashcode.
If there is a specific format of thetoString
then mention them in the documentation. If not then make a specific comment about thetoString
method.
Also, provide programmatic access to all the information contained in the value returned bytoString
.
In Kotlin, we get all this for free using the
data class
provided by default
Item 11: Cloning Objects
Cloning in kotlin is as easy as calling the copy method from the data class. The method provided by the data class offers the following:
- x.copy() !== x
- x.javaClass == x.copy().javaClass
- x.copy().equals(x)
This method provided by Kotlin itself satisfies all the requirements that are requested by the contract of implementing the Cloneable interface in Java to expose the protected clone() method in the object class.
And in this method, we could also provide named arguments as to what should be different from the data class it is cloned from. The data class in kotlin uses the copy constructor approach from Java to implement it's cloning facility.
It uses a copy constructor and a static factory which provides a lot more robustness over implementing the Cloneable interface. Such as
- Doesn't rely on a risk-prone extralinguistic object creation mechanism
- Doesn't demand unenforceable adherence to not-so documented conventions
- Doesn't conflict with vals
- Doesn't throw checked exceptions
- Doesn't require casts
- Add interface like functionality since Cloneable doesn't have a public
clone
method
Item 12: Implementing Comparable and Using Comparators
Implementing the comparable interface in the class provides a "natural" way of ordering the objects created by the class. ThecompareTo
method provided by the comparable interface is used to sort the collection in the way specified. The general contract of thecompareTo
method is similar to that of theequals
method.
- Compare
this
object with the other object provided - Returns a negative integer if the object is less than the object it is compared to.
- Returns zero if they are equal
- Returns a positive integer if the object is greater than the object it is compared to.
- Throws
ClassCastException
if the specified object's type prevents it from being compared to this Object.
The contract specification goes as follows
- x.compareTo(y) == -y.compareTo(x) for all x and y
- Transitivity: (x.compareTo(y) > 0 && y.compareTo(z) > 0)
- if x.compareTo(y) == 0, then x.compareTo(z) == y.compareTo(z)
- Recommendation: (x.compareTo(y) == 0) == (x.equals(y))
compareBy
using Comparator
ThecompareBy
function available in Kotlin is used to sort an object with multiple fields with the use of comparators and method references. This creates a comparator using the sequence of functions to calculate a result of the comparison. This is called in the sequence that we need the list to be ordered.
// Using the it operator
val sortedListOfMovies: List<Movies> = list.sortedWith(compareBy({ it.rating }, { it.year }))
// Using method references
val sortedListOfMovies: List<Movie> = moviesList.sortedWith(compareBy(Movie::rating, Movie::year))