Effective Kotlin: Classes and Interfaces

Let’s see how we can apply Joshua Bloch’s Effective Java in the Kotlin world. Today’s topic is Classes and Interfaces.

Item 15: Minimize the accessibility of classes and members

Unlike Java, there is no “package-private” accessibility in Kotlin. However, it introduces a new visibility modifier, internal, which allows access from the same module. This is quite useful, e.g. when we need to hide specific library implementations from its consumers.

Item 16: In public classes, use accessor methods, not public fields

In Kotlin, we are encouraged to use properties over accessor methods, because we can provide custom getters and setters.

For example, the custom setter below will throw an exception if the caller tries to set property as an empty string.

class Sample {
  var property: String = "EMPTY"
    set(value) = if (value.isEmpty()) throw IllegalArgumentException() else field = value
}

Item 17: Minimize mutability

Kotlin has a very good job in minimizing mutability.

For example, classes can’t be extended by default, unless you explicitly put the open keyword there.

Kotlin also provides two different keywords to define variables, var for variables that can be reassigned, and val for constant variables that can only be assigned once (similar to the final keyword in Java).

Also, collections in Kotlin have both the immutable version and also the mutable version. Unlike Java, there’s no mutating methods in the immutable collection classes, e.g. there’s no add() nor remove() in the List class, which is immutable.

Item 18: Favor composition over inheritance

In Kotlin, we can use delegation to easily achieve composition. The example in the book can be converted to something like below:

class InstrumentedSet<E>(private val s: MutableSet<E>) : MutableSet<E> by s {
  var addCount = 0
    private set

  override fun add(element: E): Boolean {
    addCount++
    return s.add(element)
  }

  override fun addAll(elements: Collection<E>): Boolean {
    addCount += elements.size
    return s.addAll(elements)
  }
}

Item 19: Design and document for inheritance or else prohibit it

As mentioned in Item 17, all Kotlin classes are closed by default, and we have to explicitly mark them open to allow inheritance.

Item 20: Prefer interfaces to abstract classes

There isn’t anything special in Kotlin regarding this item.

Item 21: Design interfaces for posterity

There isn’t anything special in Kotlin regarding this item.

Item 22: Use interfaces only to define types

In Kotlin, we can use Object declarations to define constants, e.g.:

object PhysicalConstants {
  const val AVOGADROS_NUMBER = 6.022_140_857e23
  const val BOLTZMANN_CONST = 1.380_648_52e-23
  const val ELECTRON_MASS = 9.109_383_56e-31
}

Item 23: Prefer class hierarchies to tagged classes

There isn’t anything special in Kotlin regarding this item.

Item 24: Favor static member classes over nonstatic

In Kotlin, member classes are static by default, and we need to explicitly mark them inner to be non-static.

Item 25: Limit source files to a single top-level class

In Kotlin, it’s encouraged to have multiple top-level declarations in the same file, as long as they are closely related and the file size remains reasonable.


See also