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.