Classes, objects, and in 4 terfaces
COMPANION-OBJECT EXTENSIONS
But you may need to work with Java code that requires a member of your class to be static. You can achieve this with the @JvmStatic annotation on the corresponding member. If you want to declare a static field, use the@JvmField annotation on a top-level property or a property declared in an object. These features exist specifically for interoperability purposes and are not, strictly speaking, part of the core language. We’ll cover annotations in detail in chapter 10.
Note that Kotlin can access static methods and fields declared in Java classes, using the same syntax as Java.
COMPANION-OBJECT EXTENSIONS
As you saw in the section 3.3, extension functions allow you to define methods that can be called on instances of a class defined elsewhere in the codebase. But what if you need to define functions that can be called on the class itself, like companion-object methods or Java static methods? If the class has a companion object, you can do so by defining extension functions on the companion object. More specifically, if class C has a companion object, and you define an extension functionfunc on C.Companion, you can call it asC.func().
For example, imagine that you want to have a cleaner separation of concerns for your
Person class. The class itself will be part of the core business-logic module, but you don’t want to couple that module to any specific data format. Because of that, the deserialization function needs to be defined in the module responsible for client/server communication. You can accomplish this using extension functions. Note how you use the default name (Companion) to refer to the companion object that was declared without an explicit name:
// business logic module
class Person(val firstName: String, val lastName: String) { companion object {
} }
// client/server communication module
fun Person.Companion.fromJSON(json: String): Person { } ...
val p = Person.fromJSON(json)
Declares an empty companion object Declares an extension function
You call thefromJSON function because it was defined in the companion object, but it’s an extension to companion object. As always with extension functions, it looks like a member, but it’s not. But note that you have to declare a companion object in your class, even an empty one, in order to be able to define extensions to it.
You’ve seen how useful companion objects can be. Now let’s move to the next feature in Kotlin that’s expressed with the sameobject keyword: object expressions.
4.4.4 Object expressions: anonymous inner classes rephrased
The object keyword can be used not only for declaring named singleton-like objects, but also for declaring anonymous objects. Anonymous objects replace Java’s use of anonymous inner classes. For example, let’s see how you can convert a typical use of a Java anonymous inner class—an event listener—into Kotlin:
Declares an anonymous object extending MouseAdapter Overrides MouseAdapter methods
As you can see, the syntax is the same as with object declarations, except that you omit the name of the object. The object expression declares a class and creates an instance of that class, but it doesn’t assign a name to the class or the instance. Typically, neither is necessary, because you’ll use the object as a parameter in a function call. If you do need to assign a name to the object, you can store it in a variable:
window.addMouseListener( obje ct : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) { // ...
}
override fun mouseEntered(e: MouseEvent) { // ...
} } )
val listener = object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) { ... } override fun mouseEntered(e: MouseEvent) { ... } }
Unlike a Java anonymous inner class, a Kotlin anonymous object can implement multiple interfaces or no interfaces (even though the latter is unlikely to be useful).
Just as with Java’s anonymous classes, code in an object expression can access the variables in the function where it was created. But unlike in Java, this isn’t restricted to
final variables; you can also modify the values of variables from within an object expression.
For example, let’s see how you can use the listener to count the number of clicks in a window:
Declares a local variable
Updates the value of the variable
NOTE Note
Unlike object declarations, anonymous objects aren’t singletons.
Every time an object expression is executed, a new instance of the object is created.
fun countClicks(window: Window) { var clickCount = 0
window.addMouseListener(object : MouseAdapter() { override fun mouseClicked(e: MouseEvent) {
clickCount++
}) } // ...
}
NOTE Note
Object expressions are mostly useful when you need to override multiple methods in your anonymous object. If you only need to implement a single-method interface (such as Runnable), you can rely on Kotlin’s support for SAM conversion (converting a function literal to an implementation of an interface with a single abstract method) and write your implementation as a function literal (lambda). We’ll discuss lambdas and SAM conversion in much more detail in chapter 5.
We’ve finished our discussion of classes, interfaces, and objects. In the next chapter, we’ll move on to one of the most interesting areas of Kotlin: lambdas and functional programming.
4.5 Summary
Interfaces in Kotlin are similar to Java’s but can contain default implementations and properties.
All declarations arefinal andpublicby default.
To make a declaration non-final, mark it asopen.
internal declarations are visible in the same module.
Nested classes aren’t inner by default. Use the keywordinner to store a reference to an outer class.
Asealed class can only have subclasses nested in its declaration.
Initializer blocks and secondary constructors provide flexibility for initializing class instances.
You use thefield identifier to reference a property backing field from the accessor body.
Data classes provide compiler-generatedequals(),hashCode(),toString(),copy(), and other methods.
Class delegation helps to avoid many similar delegating methods in your code.
Object declaration is Kotlin’s way to define a singleton class.
Companion objects (along with package-level functions and properties) replace Java’s static method and field definitions.
Companion objects, like other objects, can implement interfaces or have extension functions or properties.
Object expressions are Kotlin’s replacement for Java’s anonymous inner classes, with added power such as the ability to implement multiple interfaces and to modify the variables defined in the scope where the object is created.