Codementor Events

Effective Java in Kotlin, item 1: Consider static factory methods instead of constructors

Published Apr 02, 2018Last updated Sep 29, 2018
Effective Java in Kotlin, item 1: Consider static factory methods instead of constructors

Article originally published on blog.kotlin-academy.com

Effective Java book by Joshua Bloch is one of the most important books about Java development. I reference it often and probably this is why I am often asked to say or write more about it. I also noticed a lot of interest about how they relate to Kotlin. This is why I decided to describe them one by one in terms of Kotlin, here on Kotlin Academy blog. I will continue as long as I see an interest from readers 😉

This is the first rule from Effective Java:

Consider static factory methods instead of constructors.

Let’s explore it.

Book reminder

The first rule of Effective Java claimed that developers should more often consider usage of static factory method instead of constructors. Static factory method is a static method used to produce an instance of an object. Here are some Java examples of static factory method usage:

Boolean trueBoolean = Boolean.valueOf(true);
String number = String.valueOf(12);
List<Integer> list = Arrays.asList(1, 2, 4);

Static factory methods are a really powerful alternative to constructors. Here are some of their advantages:

  • Unlike constructors, they have names. Names explain how an object is created and what are the arguments. For example, let’s say that you see the following code: new ArrayList(3). Can you guess what does 3 mean? Is it suppose to be the first element of array or size of a list? It is definitely not self-explanatory. This is a situation where a name, like ArrayList.withSize(3), would clear all confusion. This is one case where a name is really useful: it is explaining arguments or characteristic way of object creation. Another reason to have a name is that it solves a conflict between constructors with the same parameters types.
  • Unlike constructors, they do not require to create a new object each time they’re invoked. When we are using static factory method, we can include caching mechanism to optimize object creation. This way we can improve a performance of object creation. We can also define static factory method that is returning null if object cannot be created, like Connectrions.createOrNull() which is returning null when Connection cannot be created for some reason.
  • Unlike constructors, they can return an object of any subtype of their return type. This can be used to provide a better object for different cases. This is especially important when we want to hide an actual object behind an interface. For example, in Kotlin all collections are hidden behind interfaces. It is important because there are different classes under the hood for different platforms. When we use listOf(1,2,3), then we have ArrayList when we run on Kotlin/JVM. The same call will return JavaScript array on Kotlin/JS. This is an optimization, but it is not a problem because both types are implementing Kotlin List interface. listOf return type is List, and this is an interface on which we are operating. It normally doesn’t matter for us what is actual type under the hood. Similarly, in any static factory method we can return different types or even change actual implementation of types and everything is OK as long as they are hidden behind some super-type or interface which is specified as a static factory method return type.
  • They reduce verbosity of creating parameterized type instances. This is rather Java problem than Kotlin since Kotlin has better type inference. The point is that when we invoke a constructor, we must specify the parameter types, even when they are quite obvious. When invoking a static factory method, one may avoid the use of parameter types.

These are really strong arguments that stand behind static factory method use, but Joshua Bloch pointed out also some disadvantages:

  • They cannot be used in subclasses construction. In subclass construction, we need to use superclass constructor. We cannot use static factory method instead.
  • They are not readily distinguishable from other static methods. The exceptions are: valueOf, of, getInstance, newInstance, getType and newType. These are common names for different types of static factory methods.

Intuitive conclusion, after this arguments, is that functions used to construct an object which are strongly connected to object structure or construction should be specified as an constructor. On the other hand, when construction is not so directly connected with object structure, then it most likely should be defined using static method.

Let’s get to Kotlin. When I was learning Kotlin, I had a feeling that somebody was designing it while having Effective Java book in front of his eyes. It answers most of Java problems described in the book. Kotlin also changed how factory methods are implemented. Let’s analyze it.

Companion factory method

Kotlin does not allow static methods. Analogue of Java static factory method is commonly called companion factory method. It is factory method placed in companion object:

class MyList {
    //...

companion object {
        fun of(vararg i: Int) { /*...*/ }
    }
}

Usage is the same as usage of static factory method:

MyList.of(1,2,3,4)

Under the hood companion object is actually singleton class. There is a big advantage that comes from this fact: Companion object can extend other classes. This way we can implement multiple general factory methods and provide them with different classes. My common example is Provider class which I am using as a lighter alternative to DI. I have the following class:

abstract class Provider<T> {

     var original: T? = null
     var mocked: T? = null

     abstract fun create(): T

     fun get(): T = mocked ?: original ?: create()
           .apply { original = this }

     fun lazyGet(): Lazy<T> = lazy { get() }
}

For different elements, I only need to specify creation function:

interface UserRepository {
      fun getUser(): User

      companion object: Provider<UserRepository> {
          override fun create() = UserRepositoryImpl()
      }
}

With such definitions, I can get this repository by UserReposiroty.get() or lazily by val user by UserRepository.lazyGet() all around the code. I can also specify different implementation for testing or mocking purposes by UserRepository.mocked = object: UserRepository { /*...*/ }.

This is a big advantage over Java, where all SFM (static factory methods) had to be implemented manually in every object. Another, still undervalued, way to reuse factory methods is by using interface delegation. We could use it in above example this way:

interface Dependency<T> {
    var mocked: T?
    fun get(): T
    fun lazyGet(): Lazy<T> = lazy { get() }
}

abstract class Provider<T>(val init: ()->T): Dependency<T> {
    var original: T? = null
    override var mocked: T? = null
     
    override fun get(): T = mocked ?: original ?: init()
          .apply { original = this }
}

interface UserRepository {
    fun getUser(): User

companion object: Dependency<UserRepository> by Provider({
        UserRepositoryImpl() 
    }) 
}

Usage is the same, but note that using interface delegation we can get factory methods from different classes in a single companion object, and we are getting only functionalities specified in the interface (what is good according to Interface segregation principle). More about interface delegation here.

Extension factory methods

Note another advantage from the fact that factory methods are placed inside companion object instead of being defined as static methods: We can define extension functions for companion objects. Therefore if we want to add companion factory method to Kotlin class defined in an external library, we can still do it (as long as it defines any companion object):

interface Tool {
   companion object { … }
}

fun Tool.Companion.createBigTool(…) : BigTool { … }

Or, when companion object is named:

interface Tool {
   companion object Factory { … }
}

fun Tool.Factory.createBigTool(…) : BigTool { … }

This is powerful possibility that let us share external libraries usage from inside our code. AFAIK Kotlin is now the only language that gives such possibility.

Top-level functions

It is common in Kotlin that instead of CFM (companion object factory method) we define a top-level function. Some common examples are listOf, setOf and mapOf. Similarly, libraries designers are specifying top-level functions used to create objects. They are used really widely. For example, in Android we were traditionally defining a function to create Activity Intent as static method:

// Java
class MainActivity extends Activity {
    static Intent getIntent(Context context) {
        return new Intent(context, MainActivity.class);
    }
}

In Kotlin Anko library, we can use top-level function intentFor with reified type instead:

intentFor<MainActivity>()

The problem with such solution is that while public top-level functions are available everywhere, it is really easy to litter user IDE tips. The even bigger problem starts when someone is creating top-level functions with names that are not directly pointing that it is not a method.

Object creation using top-level functions is a perfect choice for small and commonly created objects, like List or Map, because listOf(1,2,3) is simpler and more readable then List.of(1,2,3) (even though it looks similar). Although public top-level functions need to be used carefully and deliberately.

Fake constructors

Constructors in Kotlin work similarly as top-level functions:

class A()

val a = A()

They are also referenced the same as top-level functions:

val aReference = ::A

The only distinction in use between class constructor and function is that functions does not start from upper-case. Although technically they can. This fact is used in different places including Kotlin standard library. List and MutableList are interfaces and they cannot have constructor, but Kotlin developers wanted to allow following List construction:

List(3) { "$it" } // same as listOf("0", "1", "2")

This is why following functions are included (since Kotlin 1.1) in Collections.kt:

public inline fun <T> List(size: Int, init: (index: Int) -> T): 
List<T> = MutableList(size, init)

public inline fun <T> MutableList(size: Int, init: (index: Int) -> 
T): MutableList<T> {
    val list = ArrayList<T>(size)
    repeat(size) { index -> list.add(init(index)) } 
    return list
}

They look and acts like constructors. Lots of developers are unaware of the fact that they are top-level functions under the hood. At the same time, they have some advantages of SFM: they can return subtypes of type and they do not need to create object every time. They also don’t have requirements that constructors do. For instance, the secondary constructor needs to immediately call primary constructor or constructor of superclass. When we use a fake constructor, we can postpone constructor usage:

fun ListView(config: Config) : ListView {
    val items = … // Here we read items from config
    return ListView(items) // We call actual constructor
}

Top-level functions and scope

Another reason why we might want to create factory method outside of class is when we want to have it in some specific scope. Like when we need factory method only in some particular class or file.

Some might argue that such use might be misleading because object creation scope is normally associated with this class visibility scope. All these possibilities are powerful tools to express intention and they need to be used wisely. Although the concrete way of object creation contains an information about it and it can be very profitable to use this possibility in some contexts.

Primary constructor

Kotlin introduced the great feature called primary constructor. There can be only single primary constructor in Kotlin class, but they are much more powerful than constructors known from Java (which are in Kotlin called secondary). Primary constructor parameters can be used all around class creation:

class Student(name: String, surname: String) {
    val fullName = "$name $surname"
}

What is more, such parameters can be directly defined to serve as properties:

class Student(val name: String, val surname: String) {
    val fullName 
        get() = "$name $surname"
}

It should be clear that primary constructor is strongly connected to class creation. Note that when we use a primary constructor with default arguments, we don’t need telescoping constructors. Thanks to all of that, primary constructors are used really often (on hundreds of classes I made on my projects I found only a few without primary constructor) and secondary constructors are used rarely. This is great. I think that this is how it is supposed to be. A primary constructor is strongly connected to class structure and initialization, therefore it matches perfectly cases when we should define constructor instead of factory method. For other cases, we most likely should use companion object factory method or top-level function instead of secondary constructors.

Other ways to create an object

Kotlin awesome factory methods are not the only examples how Kotlin improved object creation. In the next article we will describe how Kotlin improved builder pattern. For instance, there are multiple optimizations included, that allows DSL usage for object creation:

val dialog = alertDialog {
    title = "Hey, you!"
    message = "You want to read more about Kotlin?"
    setPositiveButton { makeMoreArticlesForReader() }
    setNegativeButton { startBeingSad() }
}

I have it in mind. In this article, I initially describe only direct alternatives to static factory method since this is what is the first item of Effective Java about. Other awesome Kotlin features related to the book will be described in next articles. If you want to be notified, subscribe to the newsletter.

Conclusion

While Kotlin changed a lot in objects creation, Effective Java arguments for static factory method are still current. What has changed is that Kotlin excluded static member methods, instead we can use alternatives that all have SFM advantages:

  • Companion factory method
  • Top-level function
  • Fake constructor
  • Extension factory method

Each of them is used in different situations and each of them has different advantages over Java SFM.

The general rule is that in most cases all we need for object creation is a primary constructor, which is by default connected to class structure and creation. When we need other ways of class construction, we should most likely use some of the SFM alternatives.


I would like to thank Ilya Ryzhenkov for corrections and important suggestions. Some examples are also his. Ilya, you made this post better 😉

If you need help with learning Kotlin, remember that I give consultations.

To be up-to-date with great news on Kotlin Academy, subscribe to the newsletter or observe Twitter

To reference me on Twitter, use @MarcinMoskala.

Discover and read more posts from Kotlin Academy
get started
post commentsBe the first to share your opinion
Show more replies