Effective Kotlin: Creating and Destroying Objects

Let’s see how we can apply Joshua Bloch’s Effective Java in the Kotlin world, starting with Creating and Destroying Objects.

Item 1: Consider static factory methods instead of constructors

In Kotlin, we can e.g. use the companion object to achieve this:

class MyFragment {
  companion object {
    // add @JvmStatic annotation to turn them into static methods if needed
    fun newInstance(arg1: Int, arg2: String) {
      ...
    }
  }
}

Or we can simply use a function, like those in the Kotlin standard libraries, e.g.:

fun <T> listOf(vararg elements: T): List<T> {
   ...
}

Item 2: Consider a builder when faced with many constructor parameters

For constructors or functions written in Kotlin, we can simply use named parameters. For example, if your Widget class has a constructor that accepts four integer arguments:

class Widget(x: Int, y: Int, width: Int, height: Int) {
  ...
}

You can call it like below:

val widget = Widget(
  x = 12,
  y = 34,
  width = 56,
  height = 78
)

Or we can create a simple DSL like below:

data class Name(var firstName: String, var lastName: String = "")

data class UserInfo(val name: Name = Name(""), var password: String = "") {
  fun name(block: Name.() -> Unit) = name.block()
}

fun userInfo(block: UserInfo.() -> Unit): UserInfo = UserInfo().apply(block)

fun main() {
  val user = userInfo {
    name {
      firstName = "Xizhi"
      lastName = "Zhu"
    }
    password = "super secure password"
  }
}

Item 3: Enforce the singleton property with a private constructor or an enum type

Kotlin provides a built-in way to create a singleton by using the object keyword:

object Singleton {
  fun myFunction() {
    ...
  }
}

Singleton.myFunction()

Item 4: Enforce noninstantiability with a private constructor

In Kotlin, you can just use top-level functions to achieve this. For example, the standard library has a bunch of utility functions to create lists:

// creates an empty read-only list
public fun <T> emptyList(): List<T> = EmptyList

// creates a read-only list containing the given elements
public fun <T> listOf(vararg elements: T): List<T> =
  if (elements.size > 0) elements.asList() else emptyList()

// creates an empty mutable list
public inline fun <T> mutableListOf(): MutableList<T> = ArrayList()

Item 5: Prefer dependency injection to hardwiring resources

There isn’t anything special for Kotlin for this item, keep using Dagger or any other dependency injection tools you prefer.

Item 6: Avoid creating unnecessary objects

Here’re just some of the scenarios, far from an exhaustive list.

Primitive types

When using “primitive types”, try to avoid nullable types, because non-nullable types like Int will be translated to primitive types like int, but nullable types like Int? will be translated to boxed types like Integer.

Similarly, choose primitive type arrays over the generic one, because IntArray will be translated to int[], but Array<Int> will be translated to Integer[].

Iterations

The basic for-loops are optimized with zero overhead. For example:

for (i in 0..10) {
  println(i)
}

// The above Kotlin code will be translated to:
int i = 0;

for(byte var1 = 10; i <= var1; ++i) {
  boolean var2 = false;
  System.out.println(i);
}

It’s the same for downTo and until. However, it gets ugly when you apply step there:

for (i in 0..10 step 2) {
  println(i)
}

// The above Kotlin code will be translated to:
byte var3 = 0;
IntProgression var10000 = RangesKt.step((IntProgression)(new IntRange(var3, 10)), 2);
int i = var10000.getFirst();
int var1 = var10000.getLast();
int var2 = var10000.getStep();
if (var2 >= 0) {
  if (i > var1) {
    return;
  }
} else if (i < var1) {
  return;
}

while(true) {
  boolean var4 = false;
  System.out.println(i);
  if (i == var1) {
    return;
  }

  i += var2;
}

On the other hand, if you use forEach, you’ll see it’s will be translated into more complicated code than the basic for-loops:

(0..10).forEach {
  println(it)
}

// The above Kotlin code will be translated to:
byte var0 = 0;
Iterable $this$forEach$iv = (Iterable)(new IntRange(var0, 10));
int $i$f$forEach = false;
Iterator var2 = $this$forEach$iv.iterator();

while(var2.hasNext()) {
  int element$iv = ((IntIterator)var2).nextInt();
  int var5 = false;
  boolean var6 = false;
  System.out.println(element$iv);
}

Lambda

As expected, lambda comes with a cost, for example:

fun higherOrderFunction(f: () -> Unit) {
    f()
}

// And you use it like this:
val a = 0
higherOrderFunction {
  println(a)
}

The code that calls the higher order function will be translated to something below:

final int a = 0;
higherOrderFunction((Function0)(new Function0() {
  // $FF: synthetic method
  // $FF: bridge method
  public Object invoke() {
    this.invoke();
    return Unit.INSTANCE;
  }

  public final void invoke() {
    int var1 = a;
    boolean var2 = false;
    System.out.println(var1);
  }
}));

To optimize this, you can inline the function, then the calling code will become this:

int a = 0;
int $i$f$higherOrderFunction = false;
int var2 = false;
boolean var4 = false;
System.out.println(a);

Item 7: Eliminate obsolete object references

There isn’t anything special for Kotlin for this item. Always remember to nullify references which are no longer needed.

Item 8: Avoid finalizers and cleaners

Actually, Kotlin’s Any class does NOT provide a finalize() method (though you can still override it). Please refer to Item 9 on how to release non-memory resources in Kotlin.

Item 9: Prefer try-with-resources to try-finally

There is no try-with-resources in Kotlin, but we can use the use() extension function for this purpose.

To release non-memory resources in Kotlin, just have your class implement AutoCloseable, and requires its client to use the use() extension function:

class MyClass: AutoCloseable {
  override fun close() {
    ...
  }
}

// You can use it like this to ensure close() is called:
MyClass().use {
  ...
}


See also

comments powered by Disqus