Chapters

Hide chapters

Kotlin Apprentice

Third Edition · Android 11 · Kotlin 1.4 · IntelliJ IDEA 2020.3

Before You Begin

Section 0: 4 chapters
Show chapters Hide chapters

Section III: Building Your Own Types

Section 3: 8 chapters
Show chapters Hide chapters

Section IV: Intermediate Topics

Section 4: 9 chapters
Show chapters Hide chapters

12. Objects
Written by Victoria Gonda

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

Kotlin introduces a new keyword that is unavailable in other languages Kotlin is often compared to, such as Java and Swift: The object keyword.

Like Java and Swift, Kotlin supports object-oriented programming. As you saw in Chapter 11, “Classes,” you use classes to define custom types, and instances of classes are called objects.

Kotlin uses object to denote a custom type that can only have a single instance. The name choice for the object keyword can sometimes lead to confusion with class instances, since they’re also called objects. As you’ll see in this chapter, you can also use object to create anonymous objects, for which a new instance is created each time the anonymous object is used, another potential source of confusion.

This chapter will help you make sense of this potential confusion.

In discussing your code, you’ll often have to rely on the context to determine whether an “object” is a class instance, the single instance of an entity created using object or an anonymous object.

The object keyword lets you easily implement a common pattern in software development: The singleton pattern.

Singletons

The singleton pattern is one of the more straightforward of the software engineering design patterns. In most object-oriented languages, the pattern is used when you want to restrict a class to have a single instance during any given run of an application.

There are a few hurdles you must typically jump in order to create a singleton, in terms of setting up the single instance and performing the restriction to one object. Use of singletons is sometimes discouraged because they introduce a global state into your application; therefore, you must be careful when accessing a singleton from different application threads. But singletons are useful in certain use cases wherein the scope of use is limited.

Kotlin addresses some of these concerns by giving you a built-in way to create singletons.

Named objects

The object keyword in Kotlin lets you define a type that only has a single instance — a named object.

Getting started

Open the chapter starter project and create a new file by right-clicking on the src folder and choosing New ▸ Kotlin File/Class and naming the file X:

object X {
  var x = 0
}

public final class X {
   private static int x;
   public static final X INSTANCE;

   public final int getX() {
      return x;
   }

   public final void setX(int var1) {
      x = var1;
   }

   static {
      X var0 = new X();
      INSTANCE = var0;
   }
}

Singleton use cases

An example use case for a singleton is an in-memory repository for a set of data. Consider an app that needs a registry of students who are defined with the following data class. Add this class to objects.kt:

data class Student(
  val id: Int,
  val firstName:
  String, val
  lastName: String
) {
  var fullName = "$lastName, $firstName"
}
val marie = Student(1, "Marie", "Curie")
val albert = Student(2, "Albert", "Einstein")
val emmy = Student(3, "Emmy", "Noether")
object StudentRegistry {
  val allStudents = mutableListOf<Student>()

  fun addStudent(student: Student) {
    allStudents.add(student)
  }

  fun removeStudent(student: Student) {
    allStudents.remove(student)
  }

  fun listAllStudents() {
    allStudents.forEach {
      println(it.fullName)
    }
  }
}
StudentRegistry.addStudent(marie)
StudentRegistry.addStudent(albert)
StudentRegistry.addStudent(emmy)

StudentRegistry.listAllStudents()
// > Curie, Marie
// > Einstein, Albert
// > Noether, Emmy
object JsonKeys {
  const val JSON_KEY_ID = "id"
  const val JSON_KEY_FIRSTNAME = "first_name"
  const val JSON_KEY_LASTNAME = "last_name"
}

Comparison to classes

While constructors are not allowed for objects, they do have many similarities with classes:

Using static members

One of the students you define in this chapter, Emmy Noether, was a key contributor to the theory of conservation laws in physics. There appears to be a “law of conservation of keywords” because, while Kotlin has gained the object keyword, it’s also lost a keyword found in other languages like Java and Swift: There is no static keyword in Kotlin.

Portrait of Emmy Noether
Povjtaag op Algb Zaezzar

Creating companion objects

You create the companion object by prepending companion to an object defined in the class. Add this class to your file:

class Scientist private constructor(
  val id: Int,
  val firstName: String,
  val lastName: String
) {

  companion object {
    var currentId = 0

    fun newScientist(
      firstName: String,
      lastName: String
    ): Scientist {
      currentId += 1
      return Scientist(currentId, firstName, lastName)
    }
  }

  var fullName = "$firstName $lastName"
}
object ScientistRepository {
  val allScientists = mutableListOf<Scientist>()

  fun addScientist(scientist: Scientist) {
    allScientists.add(scientist)
  }

  fun removeScientist(scientist: Scientist) {
    allScientists.remove(scientist)
  }

  fun listAllScientists() {
    allScientists.forEach {
      println("${it.id}: ${it.fullName}")
    }
  }
}
val emmy = Scientist.newScientist("Emmy", "Noether")
val isaac = Scientist.newScientist("Isaac", "Newton")
val nick = Scientist.newScientist("Nikola", "Tesla")

ScientistRepository.addScientist(emmy)
ScientistRepository.addScientist(isaac)
ScientistRepository.addScientist(nick)

ScientistRepository.listAllScientists()
// 1: Emmy Noether
// 2: Isaac Newton
// 3: Nikola Tesla

Companion naming and accessing from Java

The companion object is given an implicit name of Companion. You can use a custom name by adding it after the companion object keywords:

companion object Factory {
  // companion object members
}
// java
Scientist isaac =
    Scientist.Factory.newScientist("Isaac", "Newton");

Mini-exercise

Update the Student data class from above to keep track of how many students have been created. Use a companion object method numberOfStudents() to get the number of student instances.

Using anonymous objects

Anonymous classes are used in Java to override the behavior of existing classes without the need to subclass, and also to implement interfaces without defining a concrete class. In both cases, the compiler creates a single anonymous instance, to which no name need be given. You’ll learn more about inheritance in Chapter 15, “Advanced Classes,” and interfaces in Chapter 17, “Interfaces.”

interface Counts {
  fun studentCount(): Int
  fun scientistCount(): Int
}
val counter = object : Counts {
  override fun studentCount(): Int {
    return StudentRegistry.allStudents.size
  }

  override fun scientistCount(): Int {
    return ScientistRepository.allScientists.size
  }
}

println(counter.studentCount()) // > 3
println(counter.scientistCount()) // > 3
<undefinedtype> counter = new Counts() {
  public int studentCount() {
    return StudentRegistry.INSTANCE.getAllStudents().size();
  }

  public int scientistCount() {
    return ScientistRepository.INSTANCE
      .getAllScientists().size();
  }
};

Challenges

  1. Create a named object that lets you check whether a given Int value is above a threshold. Name the object Threshold and add a method isAboveThreshold(value: Int).
  2. Create a version of the Student class that uses a factory method loadStudent(studentMap: Map<String, String>) to create a student with a first and last name from a map such as mapOf("first_name" to "Neils", "last_name" to "Bohr"). Default to using “First” and “Last” as the names if the map not contain a first name or last name.
  3. Create an anonymous object that implements the following interface:
interface ThresholdChecker {
  val lower: Int
  val upper: Int

  /**
  * Returns true if value is higher than the upper threshold
  * and false otherwise
  */
  fun isLit(value: Int): Boolean
  /**
  * Returns true if value is less than the lower threshold
  * and false otherwise
  */
  fun tooQuiet(value: Int): Boolean
}

Key points

Where to go from here?

As you’ve seen in this chapter, just like classes, objects have properties and methods, and there’s more to learn about for both. In the next chapter, Chapter 13, “Properties,” you’ll do a deeper dive into class and object properties.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2024 Kodeco Inc.

You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now