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:

  1. Reflexive: For any non-null reference value x,

    x.equals(x)must returntrue

  2. Symmetric: For any non-null reference values x and y,

    x.equals(y)must returntrueif and only ify.equals(x)returnstrue.

  3. Transitive: For any non-null reference values x, y, z,

    if x.equals(y)returnstrueandy.equals(z)returnstrue, thenx.equals(z)must returntrue.

  4. Consistent: For any non-null reference values x and y,

    multiple invocations ofx.equals(y)consistently returntrueor consistently returnfalse, provided no information used in equals comparisons on the objects is modified.

  5. 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 qualityequalsmethod

  • Use the ===operator to check if the argument is a reference to the object
  • Check if the javaclassof 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 hashCodemethod.

In Kotlin, we get all this for free using thedata classprovided by default

Code available here


Item 9: Overriding Hashcode

There is a rule to always overridehashCodewheneverequalsis 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 thehashCodemethod 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 thedata classprovided by default

Steps to write a high qualityhashCodemethod

  • 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 theequalsmethod), do the following:

  • Compute anInt hashCode cfor the field
    • Boolean: f ?: 0
    • Byte, Char, Short, Float, Double, Int: f.toInt()
    • Long: (f ^ (f >>> 32)).toInt()
    • The class's equalsmethod compares the field by recursively invoking equals and invoke the hashCode on the field.
    • Array: Each element is treated as a separate field by applying the rules recursively.

In Kotlin, we get all this for free using thedata classprovided by default

Code available here


Item 10: Overriding toString

A goodtoStringimplementation makes the class much more pleasant to use. It clearly displays the most significant information required in a class object. If thetoStringmethod 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 thetoStringthen mention them in the documentation. If not then make a specific comment about thetoStringmethod.

Also, provide programmatic access to all the information contained in the value returned bytoString.

In Kotlin, we get all this for free using thedata classprovided by default

Code available here


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 clonemethod

Code available here


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. ThecompareTomethod provided by the comparable interface is used to sort the collection in the way specified. The general contract of thecompareTomethod is similar to that of theequalsmethod.

  • 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))

compareByusing Comparator

ThecompareByfunction 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))

Code available here


results matching ""

    No results matching ""