Properties in Kotlin classes can be declared either as mutable using the var
keyword,
or as read-only using the val
keyword.
class Address {
var name: String = "Holmes, Sherlock"
var street: String = "Baker"
var city: String = "London"
var state: String? = null
var zip: String = "123456"
}
To use a property, simply refer to it by its name:
fun copyAddress(address: Address): Address {
val result = Address() // there's no 'new' keyword in Kotlin
result.name = address.name // accessors are called
result.street = address.street
// ...
return result
}
The full syntax for declaring a property is the following.
var <propertyName>[: <PropertyType>] [= <property_initializer>]
[<getter>]
[<setter>]
The initializer, getter and setter are optional. Property type is optional if it can be inferred from the initializer (or from the getter return type, as shown below).
var initialized = 1 // has type Int, default getter and setter
// var allByDefault // ERROR: explicit initializer required, default getter and setter implied
The full syntax of a read-only property declaration differs from a mutable one in two ways:
it starts with val
instead of var
and does not allow a setter:
val simple: Int? // has type Int, default getter, must be initialized in constructor
val inferredType = 1 // has type Int and a default getter
You can define custom accessors for a property. If you define a custom getter, it will be called every time you access the property (this way you can implement a computed property). Here's an example of a custom getter:
val isEmpty: Boolean
get() = this.size == 0
If you define a custom setter, it will be called every time you assign a value to the property except its initialization. A custom setter looks like this:
var stringRepresentation: String
get() = this.toString()
set(value) {
setDataFromString(value) // parses the string and assigns values to other properties
}
By convention, the name of the setter parameter is value
, but you can choose a different name if you prefer.
You can omit the property type if it can be inferred from the getter:
val isEmpty get() = this.size == 0 // has type Boolean
If you need to change the visibility of an accessor or to annotate it, but don't need to change the default implementation, you can define the accessor without defining its body:
var setterVisibility: String = "abc"
private set // the setter is private and has the default implementation
var setterWithAnnotation: Any? = null
@Inject set // annotate the setter with Inject
In Kotlin, a field is only used as a part of a property to hold its value in memory. Fields can not be declared directly.
However, when a property needs a backing field, Kotlin provides it automatically. This backing field can be referenced
in the accessors using the field
identifier:
var counter = 0 // the initializer assigns the backing field directly
set(value) {
if (value >= 0)
field = value
// counter = value // ERROR StackOverflow: Using actual name 'counter' would make setter recursive
}
The field
identifier can only be used in the accessors of the property.
A backing field will be generated for a property if it uses the default implementation of at least one of the accessors,
or if a custom accessor references it through the field
identifier.
For example, in the following case there will be no backing field:
val isEmpty: Boolean
get() = this.size == 0
If you want to do something that does not fit into this implicit backing field scheme, you can always fall back to having a backing property:
private var _table: Map<String, Int>? = null
public val table: Map<String, Int>
get() {
if (_table == null) {
_table = HashMap() // Type parameters are inferred
}
return _table ?: throw AssertionError("Set to null by another thread")
}
On JVM: Access to private properties with default getters and setters is optimized to avoid function call overhead.
{type="note"}
If the value of a read-only property is known at the compile time, mark it as a compile time constant using the const
modifier.
Such properties need to fulfil the following requirements:
- Top-level, or member of an
object
declaration or a companion object. - Initialized with a value of type
String
or a primitive type - No custom getter
Such properties can be used in annotations:
const val SUBSYSTEM_DEPRECATED: String = "This subsystem is deprecated"
@Deprecated(SUBSYSTEM_DEPRECATED) fun foo() { ... }
Normally, properties declared as having a non-null type must be initialized in the constructor. However, fairly often this is not convenient. For example, properties can be initialized through dependency injection, or in the setup method of a unit test. In this case, you cannot supply a non-null initializer in the constructor, but you still want to avoid null checks when referencing the property inside the body of a class.
To handle this case, you can mark the property with the lateinit
modifier:
public class MyTest {
lateinit var subject: TestSubject
@SetUp fun setup() {
subject = TestSubject()
}
@Test fun test() {
subject.method() // dereference directly
}
}
The modifier can be used on var
properties declared inside the body of a class (not in the primary constructor, and only
when the property does not have a custom getter or setter) as well as for top-level properties and
local variables. The type of the property or variable must be non-null, and it must not be a primitive type.
Accessing a lateinit
property before it has been initialized throws a special exception that clearly identifies the property
being accessed and the fact that it hasn't been initialized.
To check whether a lateinit var
has already been initialized, use .isInitialized
on
the reference to that property:
if (foo::bar.isInitialized) {
println(foo.bar)
}
This check is only available for the properties that are lexically accessible, when declared in the same type or in one of the outer types, or at top level in the same file.
The most common kind of properties simply reads from (and maybe writes to) a backing field. On the other hand, with custom getters and setters one can implement any behaviour of a property. Somewhere in between, there are certain common patterns of how a property may work. A few examples: lazy values, reading from a map by a given key, accessing a database, notifying listener on access, etc.
Such common behaviours can be implemented as libraries using delegated properties.