Kotlin Static Factory Methods
Adam Świderski
Posted on April 30, 2021
Purpose
There is a concise error in the title, there are no static methods in Kotlin. But there are ways to achieve similar behavior to proposed by Joshua Bloch in Effective Java book - using static factory methods instead of constructors. This is also completely different from the Factory Method design pattern, don't confuse those.
Long story short, these are methods that create object instances based on supplied arguments (or even without them) and that you can call from anywhere without the need of having an instance of a class that contains them. To do so in Java, you would use the static
keyword, which means that method is part of a class (understood as a type) rather than an object, and you can call it without creating an instance. We don't have this possibility in Kotlin, but we can get a similar effect with companion object
.
Example usage
Even if the phrase "static factory method" sounds alien, I'm sure you saw it being used in code. Typical static factory methods may look like this:
val locale = Locale.forLanguageTag("PL")
val today = LocalDate.now(ZoneId.of("GMT"))
val time = LocalDateTime.of(2021, 1, 1, 12, 34, 23, 123)
val someNumber = Double.fromBits(0x10000000000000L)
val formattedPi = String.format("%.2f", 3.14159265358979323)
Also, every primitive type in Kotlin is created using one, not with a constructor.
Elements
The main rule here will be to use well-named methods instead of constructor, that should be private just like in Builder Pattern. Popular factory method names are:
-
from
orvalueOf
- type conversion method, it takes a single argument and returns another object with the same value
val instant = Instant.now() val date = Date.from(instant) val prime = BigInteger.valueOf(Long.MAX_VALUE)
-
of
- the aggregating method, that takes multiple arguments and returns instance containing all of them
val cutlery = EnumSet.of(Fork, Spoon, Knife)
-
instance
,getInstance
orinstanceOf
- a method returning instance, often seen in Singletons (so not really in Kotlin :) ) it may take arguments and return the same instance for provided values but it's not guaranteed
val luke: StackWalker = StackWalker.getInstance(options)
-
create
ornewInstance
- similar togetInstance
but this time a new instance is guaranteed each time
val array = Array.newInstance(String::class.java, 10)
-
get<<Type>>
- similar togetInstance
but returns the same instance of the object when the factory method is in a separate class.<<Type>>
is the type of returned object
val path = Path.of("path", "to/file/store") // already familiar method 'of()' val fileStore: FileStore = Files.getFileStore(path) // <<Type>> to FileStore // in this case for the same `Path` instance, new `FileStore` instance will be created each time
-
new<<Type>>
- analogically to the previous example but for getting a new instance each time
val br: BufferedReader = Files.newBufferedReader(path)
The above-mentioned names are fairly generic, but nothing should stop you from using more descriptive like forLanguageTag
or fromBits
like in previous examples. The most important thing is for the static factory method name to go along with already known name patterns for this use-case. There is no other way to distinguish it from "normal" methods (that don't return an instance) at the first sight.
Companion Object
So in Java, such methods had to be static members of a class, but in Kotlin we have companion object
. It allows us to call the method without explicitly creating an instance of a class (but in fact, object
is an instance). Programmers deeply anchored in Java often use it just as a container for keeping constants and methods that they want to be static
, but it has many interesting features:
-
companion object
is a special case of an inside-classobject
so a Kotlin Singleton - Its fields and methods behave similar to
static
ones in Java, but are still members of an instance with all its consequences, like access modifiers. - It can implement an interface or extend a class.
- Class having
companion object
has access to its fields, even private ones. - Interfaces also can have
companion object
. - It's not being inherited, subclasses don't have access to parents object.
- It's not accessible from the instance, just like the inner
object
. - It can have a name, but it's optional - by default it can be reached with the
Companion
name. - There can single
companion object
inside a class, but any number of normalobjects
. - Unlike
inner
classes it doesn't have access to fields and methods of the parent object, just like inside `object. - Parent class name already points to
companion object
, there is no need to call it like<<Class>>.Companion.<<method()>>
test
- test
kotlin val test = "test"
`kotlin
// the companion object in InterestingObject class is
// extending this abstract class
abstract class Printer {
abstract fun getName(): String
fun print() {
val name = getName()
println(name)
}
}
// private constructor so instance of this class can't be
// created in traditional way
class InterestingObject private constructor() {
object Factory : Printer() { // ordinary inside object
@JvmStatic // generating true static methods for Java interoperability
fun create() = InterestingObject()
// only companion object override methods can be @JvmStatic
override fun getName(): String {
return "Interesting Object"
}
}
companion object CompanionFactory : Printer() { // companion object with a custom name
private val secret = "No one can know this" // private companion object field, not accessable outside the class
val publicInfo = "***** ***" // publicly available field
fun create() = InterestingObject()
@JvmStatic
override fun getName(): String {
return "Interesting Object from Companion"
}
}
// instance method has access to private companion object field
fun getSecret() = secret
}
val obj0 = InterestingObject() // error, private constructor doesn't allow to create instance this way
val obj1 = InterestingObject.create() // method from CompanionFactory so from the companion object
val obj2 = InterestingObject.Factory.create() // method from Factory object
val obj3 = InterestingObject.CompanionFactory.create() // companion object method again but with unnecessary companion object name
val secret = InterestingObject.CompanionFactory.secret // error, no access to the private field in companion object
val secret2 = InterestingObject.create().getSecret() // but you can get it from the instance method
val publicInfo = InterestingObject.publicInfo // this field is accessable from companion object
// calling abstract class methods
val print1 = InterestingObject.print()
val print2 = InterestingObject.Factory.print()
val print3 = InterestingObject.CompanionFactory.print()
// error, you can't call in-class objects from the instance
InterestingObject.create().Factory.getName()
InterestingObject.create().CompanionFactory.getName()
`
The code example below shows incrementing variable counter
each time when a new CountMe
instance is created. Like I already wrote subclass ReallyCountMe
doesn't have access to its parents companion object
. Nonetheless, when a new ReallyCountMe
instance is created counter
is also being incremented.
It happens because parent constructor and init
bloc is called each time, and counter
field is part of Singleton - the companion object
, so each time instance is created (parent or subclass) the same variable counter
is incremented.
`kotlin
open class CountMe(val objectName: String) {
init {
count++ // incrementing counter in companion object
}
companion object {
var count = 0 // every CountMe instance have access to this field
fun printCount() = println("count = $count")
}
}
class ReallyCountMe(private val name: String): CountMe(name)
// usage
CountMe.printCount() // prints 0
CountMe("1")
CountMe("2")
CountMe("3")
CountMe.printCount() // prints 3
ReallyCountMe("1")
ReallyCountMe("2")
ReallyCountMe("3")
CountMe.printCount() // prints 6
`
Every CountMe
instance has a reference to the same (and the only) companion object
instance.
Another example from library Fiel
(simple HTTP client), where Result
subclasses are created with companion object
methods:
`kotlin
sealed class Result {
...
companion object {
// Factory methods
fun error(ex: E) = Failure(ex)
fun <V : Any?> success(v: V) = Success(v)
fun <V : Any?> of(value: V?, fail: (() -> Exception) = { Exception() }): Result<V, Exception> =
value?.let { success(it) } ?: error(fail())
fun <V : Any?, E: Exception> of(f: () -> V): Result<V, E> = try {
success(f())
} catch (ex: Exception) {
error(ex as E)
}
}
`
Companion Object Extensions
There are times where you don't have the chance to modify class companion object
, like when it comes from 3rd party library. But if the class has the companion object
then you can use extension functions
on it.
`kotlin
// --- external module, like 3rd party library ---
class ExternalClass {
companion object
}
class ExternalClassWithoutCompanion
interface Car{
companion object
}
// --- internal module ---
class Ferrari: Car // internal class implementing interface from the library
fun Car.Companion.getFerrari():Ferrari = Ferrari() // library class extension method creating instance of internal class
// usage
val ferrari = Car.getFerrari()
// other extensions function examples
fun ExternalClass.Companion.newMethod() = "Wow! Extending class companion"
fun Car.Companion.newMethod() = "Wow! Extending interface companion"
fun ExternalClassWithoutCompanion.Companion.newMethod() = "Wow!" // error, this class has no companion object
fun ExternalClassWithoutCompanion.newMethod() = "Wow!" // extension method for the class instance
// usage
ExternalClass.newMethod() // new extension method of ExternalClass companion object
ExternalClassWithoutCompanion.newMethod() // error, newMethod() is instance method, not companion object
ExternalClassWithoutCompanion().newMethod() // this will work, extension method is used on the instance
`
Again using Fuel
library, where Result
class has companion object
. Result
class itself is sealed
so you cant extend it outside its module.
`kotlin
fun Result.Companion.awersomeNewMethod(){
println("This wasn't originally here")
}
//usage
Result.awersomeNewMethod()
`
Using companion object
can make clients of your APIs lives much easier, even if you put an empty one in a public class or interface. It allows them to extend functionality without building wrappers or extending classes - and this is why a lot of Kotlin features exist in the first place.
Alternative - Top-level functions
Another interesting way of creating instances are top-level functions
, known for example from:
kotlin
val intList = listOf(1, 2, 3)
val someSet = setOf("1", "2", "3")
val someMap = mapOf(1 to "1", 2 to "2")
They are especially useful for creating simple but often used objects like lists or maps. Like the name may suggest those functions exist outside (above?) of the classes, and for that reason, they are publicly available (ok, they can be also private but whats the point then?). You should be aware of accidental function shadowing, for example:
`kotlin
// adding such function in the project
fun listOf(vararg item: Int) = item.asList()
// we are shadowing function from kotlin.collections without any trace in code it self (no additional imports)
val intList = listOf(1, 2, 3) // our top-level function
val intList2 = listOf("1", "2") // function from kotlin.collections
`
It's not necessarily evil, but it may surprise you. So it's smarter to avoid too generic names, and not abuse top-level functions
. Also to not litter IDE suggestions, because they will be popping out there.
Summary
Kotlin with its syntactic sugar, such as named arguments, makes writing code much more pleasant than Java. Still using methods to create instances instead of constructors is often a good idea. Static Factory Methods are often used in Kotlin itself or by 3rd party library creators.
I tried to show interesting ways of using companion object
which is not just a container for constants, or is emulating the static
keyword from Java. Kotlin creators are extensively using it, for example in Coroutines - so I guess we should too.
Pros
- methods have descriptive names
Some languages allow naming constructors, unfortunately not Kotlin or Java. Static Factory Methods are a way of achieving this functionality.
- methods can take same the argument types in the same order
As long as they have different names, the list of arguments may be identical. You can't get this with a barebone constructor. Named arguments and default values may cover this issue just a bit.
- you don't always need a new instance
The constructor will always create a new instance, but method may first check some cache and return instance that is already created.
- any subtype can be returned
This subtype may not even be public. You can create a companion object
inside an interface and return instances of the internal types that implement it. Client will see only the public interface.
Cons
- you can't extend a class that have only a private constructor
But this can always be a strong suggestion to use composition over inheritance :)
- no easy way to distinguish Static Factory Methods from usual methods
Using constructor is clearly visible in the code, but the factory method looks like any other method. The automated documentation generation also won't be picking those methods as ways to create instance. Using common nomenclature for naming static factory methods may help, examples are here
Posted on April 30, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.