Properties

A property is a var or val that’s part of a class.

Defining a property maintains state within a class. Maintaining state is the primary motivating reason for creating a class rather than just writing one or more standalone functions.

A var property can be reassigned, while a val property can’t. Each object gets its own storage for properties:

// Properties/Cup.kt

class Cup {
  var percentFull = 0
}

fun main() {
  val c1 = Cup()
  c1.percentFull = 50
  val c2 = Cup()
  c2.percentFull = 100

  println(c1.percentFull)
  println(c2.percentFull)
}
/* Output:
50
100
*/

Defining a var or val inside a class looks just like defining it within a function. However, the var or val becomes part of that class, and you must refer to it by specifying its object using dot notation, placing a dot between the object and the name of the property. You can see dot notation used for each reference to percentFull.

The percentFull property represents the state of the corresponding Cup object. c1.percentFull and c2.percentFull contain different values, showing that each object has its own storage.

A member function can refer to a property within its object without using dot notation (that is, without qualifying it):

// Properties/Cup2.kt

class Cup2 {
  var percentFull = 0
  val max = 100
  fun add(increase: Int): Int {
    percentFull += increase
    if (percentFull > max)
      percentFull = max
    return percentFull
  }
}

fun main() {
  val cup = Cup2()
  cup.add(50)
  println(cup.percentFull)
  cup.add(70)
  println(cup.percentFull)
}
/* Output:
50
100
*/

The add() member function tries to add increase to percentFull but ensures that it doesn’t go past 100%.

You must qualify both properties and member functions from outside a class.

You can define top-level properties:

// Properties/TopLevelProperty.kt

val constant = 42

var counter = 0

fun inc() {
  counter++
}

Defining a top-level val is safe because it cannot be modified. However, defining a mutable (var) top-level property is considered an anti-pattern. As your program becomes more complicated, it becomes harder to reason correctly about shared mutable state. If everyone in your code base can access the var counter, you can’t guarantee it will change correctly: while inc() increases counter by one, some other part of the program might decrease counter by ten, producing obscure bugs. It’s best to guard mutable state within a class. In Constraining Visibility you’ll see how to make it truly hidden.

To say that vars can be changed while vals cannot is an oversimplification. As an analogy, consider a house as a val, and a sofa inside the house as a var. You can modify sofa because it’s a var. You can’t reassign house, though, because it’s a val:

// Properties/ChangingAVal.kt

class House {
  var sofa: String = ""
}

fun main() {
  val house = House()
  house.sofa = "Simple sleeper sofa: $89.00"
  println(house.sofa)
  house.sofa = "New leather sofa: $3,099.00"
  println(house.sofa)
  // Cannot reassign the val to a new House:
  // house = House()
}
/* Output:
Simple sleeper sofa: $89.00
New leather sofa: $3,099.00
*/

Although house is a val, its object can be modified because sofa in class House is a var. Defining house as a val only prevents it from being reassigned to a new object.

If we make a property a val, it cannot be reassigned:

// Properties/AnUnchangingVar.kt

class Sofa {
  val cover: String = "Loveseat cover"
}

fun main() {
  var sofa = Sofa()
  // Not allowed:
  // sofa.cover = "New cover"
  // Reassigning a var:
  sofa = Sofa()
}

Even though sofa is a var, its object cannot be modified because cover in class Sofa is a val. However, sofa can be reassigned to a new object.

We’ve talked about identifiers like house and sofa as if they were objects. They are actually references to objects. One way to see this is to observe that two identifiers can refer to the same object:

// Properties/References.kt

class Kitchen {
  var table: String = "Round table"
}

fun main() {
  val kitchen1 = Kitchen()
  val kitchen2 = kitchen1
  println("kitchen1: ${kitchen1.table}")
  println("kitchen2: ${kitchen2.table}")
  kitchen1.table = "Square table"
  println("kitchen1: ${kitchen1.table}")
  println("kitchen2: ${kitchen2.table}")
}
/* Output:
kitchen1: Round table
kitchen2: Round table
kitchen1: Square table
kitchen2: Square table
*/

When kitchen1 modifies table, kitchen2 sees the modification. kitchen1.table and kitchen2.table display the same output.

Remember that var and val control references rather than objects. A var allows you to rebind a reference to a different object, and a val prevents you from doing so.

Mutability means an object can change its state. In the examples above, class House and class Kitchen define mutable objects while class Sofa defines immutable objects.

Exercises and solutions can be found at www.AtomicKotlin.com.