
This is a chapter from the book Kotlin Essentials. You can find it on LeanPub or Amazon. It is also available as a course.
class A // Using a constructor to create an object val a = A()
object keyword and braces. This syntax for creating objects is known as object expression.val instance = object {}
Any, which is extended by all objects in Kotlin), implements no interfaces, and has nothing inside its body. Nevertheless, it is useful. Its power lies in its uniqueness: such an object equals nothing else but itself. Therefore, it is perfectly suited to be used as some kind of token or synchronization lock.class Box { var value: Any? = NOT_SET fun initialized() = value != NOT_SET companion object { private val NOT_SET = object {} } } private val LOCK = object {} fun synchronizedOperation() = synchronized(LOCK) { // ... }
Any, so Any() is an alternative to object {}.private val NOT_SET = Any()
object keyword instead of class and should not define the name or constructor.data class User(val name: String) interface UserProducer { fun produce(): User } fun printUser(producer: UserProducer) { println(producer.produce()) } fun main() { val user = User("Jake") val producer = object : UserProducer { override fun produce(): User = user } printUser(producer) // User(name=Jake) }
Any type, or the type of the class or interface it inherits from. This makes non-inherited members of object expressions hard to use in real-life projects.class Robot { // Possible, but rarely useful // prefer regular member properties instead private val point = object { var x = 0 var y = 0 } fun moveUp() { point.y += 10 } fun show() { println("(${point.x}, ${point.y})") } } fun main() { val robot = Robot() robot.show() // (0, 0) robot.moveUp() robot.show() // (0, 10) val point = object { var x = 0 var y = 0 } println(point.x) // 0 point.y = 10 println(point.y) // 10 }
taskNameView.addTextChangedListener(object : TextWatcher { override fun afterTextChanged( editable: Editable? ) { //... } override fun beforeTextChanged( text: CharSequence?, start: Int, count: Int, after: Int ) { //... } override fun onTextChanged( text: CharSequence?, start: Int, before: Int, count: Int ) { //... } })
object Point { var x = 0 var y = 0 } fun main() { println(Point.x) // 0 Point.y = 10 println(Point.y) // 10 val p = Point p.x = 20 println(Point.x) // 20 println(Point.y) // 10 }
data class User(val name: String) interface UserProducer { fun produce(): User } object FakeUserProducer : UserProducer { override fun produce(): User = User("fake") } fun setUserProducer(producer: UserProducer) { println(producer.produce()) } fun main() { setUserProducer(FakeUserProducer) // User(name=fake) }
// Java
class User {
// Static element definition
public static User empty() {
return new User();
}
}
// Static element usage
User user = User.empty()
// Kotlin class User { object Producer { fun empty() = User() } } // Usage val user: User = User.Producer.empty()
companion keyword before an object declaration defined inside a class, then we can call these object methods implicitly "on the class".class User { companion object Producer { fun empty() = User() } } // Usage val user: User = User.empty() // or val user: User = User.Producer.empty()
companion modifier, also known as companion objects, do not need an explicit name. Their default name is Companion.class User { companion object { fun empty() = User() } } // Usage val user: User = User.empty() // or val user: User = User.Companion.empty()
USD, EUR, or PLN. For convenience, each of these defines from builder functions, which simplify object creation.import java.math.BigDecimal import java.math.MathContext import java.math.RoundingMode.HALF_EVEN abstract class Money( val amount: BigDecimal, val currency: String ) class USD(amount: BigDecimal) : Money(amount, "USD") { companion object { private val MATH = MathContext(2, HALF_EVEN) fun from(amount: Int): USD = USD(amount.toBigDecimal(MATH)) fun from(amount: Double): USD = USD(amount.toBigDecimal(MATH)) fun from(amount: String): USD = USD(amount.toBigDecimal(MATH)) } } class EUR(amount: BigDecimal) : Money(amount, "EUR") { companion object { private val MATH = MathContext(2, HALF_EVEN) fun from(amount: Int): EUR = EUR(amount.toBigDecimal(MATH)) fun from(amount: Double): EUR = EUR(amount.toBigDecimal(MATH)) fun from(amount: String): EUR = EUR(amount.toBigDecimal(MATH)) } } class PLN(amount: BigDecimal) : Money(amount, "PLN") { companion object { private val MATH = MathContext(2, HALF_EVEN) fun from(amount: Int): PLN = PLN(amount.toBigDecimal(MATH)) fun from(amount: Double): PLN = PLN(amount.toBigDecimal(MATH)) fun from(amount: String): PLN = PLN(amount.toBigDecimal(MATH)) } } fun main() { val eur: EUR = EUR.from("12.00") val pln: PLN = PLN.from(20) val usd: USD = USD.from(32.5) }
MoneyMaker class, which can be extended by companion objects of different currencies. This class can offer a range of methods to create a currency. This way, we use companion object inheritance to extract a pattern that is common to all companion objects of classes that represent money.import java.math.BigDecimal import java.math.MathContext import java.math.RoundingMode.HALF_EVEN abstract class Money( val amount: BigDecimal, val currency: String ) abstract class MoneyMaker<M : Money> { private val MATH = MathContext(2, HALF_EVEN) abstract fun from(amount: BigDecimal): M fun from(amount: Int): M = from(amount.toBigDecimal(MATH)) fun from(amount: Double): M = from(amount.toBigDecimal(MATH)) fun from(amount: String): M = from(amount.toBigDecimal(MATH)) } class USD(amount: BigDecimal) : Money(amount, "USD") { companion object : MoneyMaker<USD>() { override fun from(amount: BigDecimal): USD = USD(amount) } } class EUR(amount: BigDecimal) : Money(amount, "EUR") { companion object : MoneyMaker<EUR>() { override fun from(amount: BigDecimal): EUR = EUR(amount) } } class PLN(amount: BigDecimal) : Money(amount, "PLN") { companion object : MoneyMaker<PLN>() { override fun from(amount: BigDecimal): PLN = PLN(amount) } } fun main() { val eur: EUR = EUR.from("12.00") val pln: PLN = PLN.from(20) val usd: USD = USD.from(32.5) }
// Using companion object inheritance for logging // from the Kotlin Logging framework class FooWithLogging { fun bar(item: Item) { logger.info { "Item $item" } // Logger comes from the companion object } companion object : KLogging() // companion inherits logger property }
// Android-specific example of using an abstract factory // for companion object class MainActivity : Activity() { //... // Using companion object as a factory companion object : ActivityFactory() { override fun getIntent(context: Context): Intent = Intent(context, MainActivity::class.java) } } abstract class ActivityFactory { abstract fun getIntent(context: Context): Intent fun start(context: Context) { val intent = getIntent(context) context.startActivity(intent) } fun startForResult(activity: Activity, requestCode: Int) { val intent = getIntent(activity) activity.startActivityForResult(intent, requestCode) } }
// Usage of all the members of the companion ActivityFactory val intent = MainActivity.getIntent(context) MainActivity.start(context) MainActivity.startForResult(activity, requestCode) // In contexts on Kotlin Coroutines, companion objects are // used as keys to identify contexts data class CoroutineName( val name: String ) : AbstractCoroutineContextElement(CoroutineName) { // Companion object is a key companion object Key : CoroutineContext.Key<CoroutineName> override fun toString(): String = "CoroutineName($name)" } // Finding a context by key val name1 = context[CoroutineName] // Yes, this is a companion // You can also refer to companion objects by its name val name2 = context[CoroutineName.Key]
data modifier for object declarations. It generates the toString method for the object; this method includes the object name as a string.data object ABC fun main() { println(ABC) // ABC }
class Product( val code: String, val price: Double, ) { init { require(price > MIN_AMOUNT) } companion object { val MIN_AMOUNT = 5.00 } }
String[^12_3], we can add the const modifier. This is an optimization. All usages of such variables will be replaced with their values at compile time.class Product( val code: String, val price: Double, ) { init { require(price > MIN_AMOUNT) } companion object { const val MIN_AMOUNT = 5.00 } }
private const val OUTDATED_API: String = "This is a part of an outdated API." @Deprecated(OUTDATED_API) fun foo() { ... } @Deprecated(OUTDATED_API) fun boo() { ... }
const modifier, which offers better support for constant elements defined at the top level or in object declarations.[^12_3]: So, the accepted types are
Int, Long, Double, Float, Short, Byte, Boolean, Char, and String.[^12_4]: A programming pattern where a class is implemented such that it can have only one instance.
[^12_5]: UPPER_SNAKE_CASE is a naming convention where each character is capitalized, and we separate words with an underscore, like in the UPPER_SNAKE_CASE name. Using it for constants is suggested in the Kotlin documentation in the section Kotlin Coding Convention.
[^12_6]: Do not treat them as best practices but rather as examples of what you might do with the fact that companion objects can inherit from classes and implement interfaces.
