In Java, we are used to the concept of getters and setters. A typical class may look something like this:
public class Person {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
// More methods come here
}
If we want to get a person's name, we call getName(). If we want to change it, we call setName(). That's quite simple.
If we want to set the name only once, during object instantiation, we can specify the non-default constructor and remove the setter as follows:
public class ImmutablePerson {
private String name;
public ImmutablePerson(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
All this dates back to the beginning of Java, somewhere around '95.
But if you've worked with C#, for example, you're probably familiar with the idea of properties. To understand them, let's go to the first example and change it a bit:
public class PublicPerson {
public String name;
}
Reading a person's name is not much shorter: p.name.
Also, changing the name is much more intuitive: p.name = "Alex";.
But by doing so, we lost a lot of control over our object. We cannot make PublicPerson immutable. If we want everybody to be able to read the person's name, they'll also be able to change it at any point in time. And what if later we decide that all names must be uppercase? With setter, we could do that. But not with the public field.
Properties provide a solution for all those problems:
class Person() {
var name : String = ""
}
This may look the same as the Java example, with all its problems. But actually, behind the scenes, it's compiled to a getter and setter pair, just like the first example.
And since properties in Kotlin are translated into getters and setters, we can also control their behavior:
class Person {
var name : String = ""
set(value) {
field = value.toUpperCase()
}
}
Note that we don't need to check that value is null. The String type simply cannot receive nulls.
Coming from Java, it may seem intuitive to use the following assignment: this.name = value.toUpperCase(). But, in Kotlin, this will create a circular dependency. Instead, there's a field identifier that we're using, which is provided automatically.