Kotlin Primer
Properties
I've been waiting for these for a long time... There's a concept used in Java called JavaBeans, part of which is a convention for defining properties. I've got an article on JavaBeans that goes into a ton of detail, but here are the basics.
Properties in Java
A JavaBean property is defined by one or two methods in a Java class:
// JAVA CODE
public String getFirstName() { ... }
public void setFirstName(String firstName) { ... }
Property types
- If only a
getmethod exists, we're defining a read-only property. For example, if we only hadgetFirstName()we're defining a read-only property calledfirstName(note the case.) - If only a
setmethod exists, we're defining a write-only property. For example, if we only hadsetFirstName()we're defining a write-only property calledfirstName - If both methods exist, we're defining a read/write property
These conventions could be used by various tools, such as GUI builders, to automatically determine properties that a user could configure when designing an application. These properties would often appear in a little table where the user could set values and see how the GUI changes, then generate code to set that value at runtime.
But this is horribly verbose, and nearly all get/set methods look identical in an application.
Properties in Kotlin
Kotlin fixes this by introducing properties as first-class language constructs.
class ABC {
// Define a new class (not extensible because it's not marked "open"!)
var firstName: String = "no first name"
// Define a read/write property called firstName of type String
// initialized to "no first name"
var lastName = "no last name"
// Define a read/write property called lastName of _inferred_
// type String initialized to "no last name"
}
fun main() {
val abc = ABC()
// Create an instance of class ABC and point value abc to it.
// Note that there is no "new" keyword; we just specify
// the class name followed by parens and constructor
// parameters (if any)
println(abc.firstName)
// Follow pointer abc to its ABC instance and print the value of
// its firstName property
abc.firstName = "Scott"
// Follow pointer abc to its ABC instance and change the value of
// its firstName property to "Scott"
// (Note that technically we're setting the value of the property
// to _point_ to a String with the value "Scott")
println(abc.firstName)
// Follow pointer abc to its ABC instance and print the value of
// its firstName property
}
Adding var and val definitions inside a class or interface defines a property. var properties
are read/write (you can modify them), and val properties are read-only (you cannot modify them).
Using Kotlin Properties From Java Code
Kotlin has excellent interoperability with Java. If we defined a Kotlin class
class ABC {
var firstName: String = "no first name"
var lastName = "no last name"
}
we could access it from Java as
// JAVA CODE
ABC abc = new ABC();
abc.setFirstName("Scott");
System.out.println(abc.getFirstName());
Behind the scenes, Kotlin creates a class file to run in the Java Virtual Machine, and this class
file contains get and set methods for each read/write property, or just get methods for
read-only properties.
We can also access Java code from inside Kotlin code!
Backing Fields
To explain "Backing fields" in Kotlin, let's look at a typical property implementation in Java
Remember that JavaBean properties are defined solely on the presence of get and set methods.
The implementation of those methods does not matter. However, we typically need to store a value
when set is called and return it when get is called. For example:
// JAVA CODE
class Foo {
private String name;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
Note the field name in this example. It's a field in the class that's hidden from anything
outside class Foo. The property defined by getName and setName uses the name field to
store the property value.
Kotlin properties can automatically create a behind-the-scenes field just like this. For example, when you write
class Foo {
var name: String = "no name"
}
fun main() {
val foo = Foo()
println(foo.name)
}
the Kotlin compiler generates a get and set function for us (note that Kotlin uses the name
"function" for what we would call a "method" in Java), and a "backing field" to hold the value of
the name property.
But that doesn't always happen...
In Java, we might define a get method that computes a value (often called a "derived property")
or returns a literal value. For example
// JAVA CODE
class Foo {
public String getName() {
return "no name";
}
}
In this case, we don't need a field to store the value.
If we do something like the following in Kotlin
class Foo {
val name: String = "no name"
// NOTE: a "val" property, so only defines a getter!
}
fun main() {
val foo = Foo()
println(foo.name)
}
It initializes the backing field to "no name" and returns it whenever the property is accessed.
If we want the equivalent of that literal-value get method in Java, we need to explicitly define
what the get function in Kotlin would look like. We do this as follows:
class Foo {
val name: String
get() {
return "no name"
}
}
fun main() {
val foo = Foo()
println(foo.name)
}
In this case, because we explicitly define the get() function, and do not mention the backing field, no backing field is defined.
So how do we explicitly mention the backing field? We use the field keyword. For example:
class Foo {
val name: String = "no name"
get() {
return field
}
}
fun main() {
val foo = Foo()
println(foo.name)
}
This example is pretty silly; we're explicitly defining the default behavior of a read-only property. It's more interesting if we want to modify the behavior. For example, we could print or log a message whenever the field is requested
class Foo {
val name: String = "no name"
get() {
println("name requested!")
return field
}
}
fun main() {
val foo = Foo()
println(foo.name)
}
Or more usefully, do something when a value is set. For example, suppose we wanted to ensure a Doctor was always called "Dr.":
class Doctor {
var name: String = "Dr. Nobody"
set(value) {
if (!value.startsWith("Dr. ")) {
field = "Dr. " + value
} else {
field = value
}
}
}
fun main() {
val doc = Doctor()
doc.name = "Scott"
println(doc.name)
}
Note
There are some much more "Kotlin-y" ways to write the body of that set function, but
I wanted to keep it closer to Java until we get to those concepts and idioms)
Before you comment on it...
Yes, we would have been better off modifying the get instead of the set. I just wanted to
demonstrate what the set would look like...
Here we're explicitly defining a set function for the name property. We look at the value
passed in (the type of value is inferred to be String, the same as the type of the property),
and if it doesn't call me a "Dr." (which I am not; you can just call me "Scott") it prepends
"Dr." when setting the value in the backing field.
We're not defining the get function so we get its default behavior.
Most of the time, you'll use properties without explicitly defining the get or set functions,
but occasionally these are useful.
The most-common instance of defining a get is for returning literals. This can be a little more
efficient than initializing the property value and having to keep an extra backing field inside
the class.
Backing Properties
Sometimes you'd like to create a property that has a more restrictive type outside your class. For example, suppose we have a list of friend names that we want to expose outside our class. If we used:
class Person {
val friends = mutableListOf("Pam", "Evan")
}
fun main() {
val person = Person()
println(person.friends.joinToString())
person.friends.add("Mikey") // We're not protecting our data!
println(person.friends.joinToString())
}
The caller can change our friends list without the Person knowing. This breaks encapsulation
(data protection). To fix this, we can create a pair of properties instead; one mutable, one not.
class Person {
val _friends = mutableListOf("Pam", "Evan")
val friends: List<String> = _friends
}
fun main() {
val person = Person()
println(person.friends.joinToString())
person.friends.add("Mikey") // Nope!
println(person.friends.joinToString())
}
Note
The use of a leading underscore _ is the Kotlin naming-convention for a backing property.
The caller cannot call add because friends is not a MutableList.
But what if the caller was a bit more shifty...
class Person {
val _friends = mutableListOf("Pam", "Evan")
val friends: List<String> = _friends
}
fun main() {
val person = Person()
println(person.friends.joinToString())
(person.friends as MutableList<String>).add("Mikey") // Uh oh!
println(person.friends.joinToString())
}
Grrrrrrr! Shifty caller! This might be ok in your internal code, but if you provide a library for others to use, this could result in inconsistent state inside your class!
To get around this, we can use the Decorator Pattern. This creates a wrapper around the object you want to protect. A quick example:
interface Person {
val name: String
}
class MutablePerson(
override var name: String
) : Person
class ImmutablePersonDecorator(
private val realPerson: Person
): Person {
override val name: String
get() = realPerson.name
}
fun main() {
val realPerson = MutablePerson("Scott")
val person = ImmutablePersonDecorator(realPerson)
realPerson.name = "Mickey"
person.name = "Mickey" // won't compile!
(person as MutablePerson).name = "Mickey" // class cast exception at runtime
person.realPerson.name = "Mickey" // won't compile
}
If we hand out a MutablePerson, that person's name can be modified. If we wrap it in an immutable
decorator, we cannot modify the name, nor could we cast it or access the underlying realPerson.
The ImmutablePerson that the user sees will still see changed data in the underlying
MutablePerson, and thoroughly protects our MutablePerson. To do this for our MutableList,
we can create a similar decorator.
class ImmutableList<T>(realList: List<T>): List<T> by realList
class Person {
val _friends = mutableListOf("Pam", "Evan")
val friends: List<String> = ImmutableList(_friends)
}
fun main() {
val person = Person()
println(person.friends.joinToString())
person.friends.add("Mikey") // won't compile
(person.friends as MutableList<String>).add("Mikey") // class cast exception at runtime
}
I'm gonna wave my hands at the generics in the definition of ImmutableList and just say
it's somewhat similar to Java generics. Here we're just using T to refer to the types
of elements in the realList being the same as the type we expose in ImmutableList.
The cool Kotlin thing happening here with by is interface delegation. Using List<T> by realList
creates all functions defined by List delegating them to the realList. This is the same result
as writing
class ImmutableList<T>(private realList: List<T>): List<T> {
override val size: Int
get() = realList.size
override fun isEmpty(): Boolean = realList.isEmpty()
override fun contains(element: T): Boolean = realList.contains(element)
// ... and all the other functions in List
}
This is one of those areas where Kotlin shines!
Warning
It might be tempting to wrap your mutable list in the List builder function:
class Person {
val _friends = mutableListOf("Pam", "Evan")
val friends: List<String> = List(_friends)
}
This won't work properly! It creates a copy of the list contents, and won't see any
changes you make to the _friends list inside Person!
Explicit Backing Fields
Developers reaaaaaaalllllllly don't like writing out those two property definitions for everything they want to restrict in this manner. Kotlin now supports "Explicit backing fields", where you can define the backing field to be of a subtype of the exposed type.
This looks like
class Person {
val friends: List<String>
field = mutableListOf("Pam", "Evan")
}
fun main() {
val person = Person()
println(person.friends.joinToString())
person.friends.add("Mikey") // won't compile
(person.friends as MutableList<String>).add("Mikey") // still works!
println(person.friends.joinToString())
}
While this is a nice shorthand, it doesn't prevent casting to the underlying type.
Warning
This approach can be useful in your applications, where you are the only user inside your app (and I'll use it in some of the code in this course), but again, if you're defining a library for others to use, I strongly recommend against it, and instead, recommend protecting via a Decorator