Kotlin inherits Java’s rich support for object-oriented programming, while also providing a significant reduction in ceremony and some nice new features.
We’ve already seen one formulation for classes in Kotlin: data classes.
Data classes include several useful features: a copy constructor, a nice
toString method, and an
.equals method (
== in Kotlin) that compares two
objects based on their fields:
This is frequently exactly what you want, an so a good rule of thumb when
designing classes in Kotlin is to start with a data class and see how you go.
If you need some of the features that data classes don’t support you can always
data modifier later.
Following this advice, at the beginning of this lesson we’ll be working with
We’ll only remove the
data modifier when we need to.
Just like Java Kotlin classes define both the data that object should store and
how they should behave.
We can borrow the same familiar terminology from Java.
A class defines a category of objects that all have certain properties and
An instance refers to one member of a class.
Unlike Java, Kotlin does not require us to use the
new keyword when creating
The data classes we have seen so far have only stored data, and so only included
Each instance of the class
Student defined above must have a
name which is a
String and an
id which is an
But like other programming languages, Kotlin classes can also have instance methods:
Now each instance of
Student also has a
greet method as defined within the
Instance methods can access instance variables and retrieve the values stored on
the instance on which the method is called.
Instance methods can also modify instance variables, assuming that the variable has been declared to be mutable:
The code above fails because
birthday was marked as a
val in the class
For it to work we need to mark
birthday as being mutable:
If you are familiar with Java you may be wondering: where are the Kotlin class constructors? In Java and other object-oriented programming languages, there is a special function called a constructor called each time an instance of a class is created. The constructor is responsible for doing any class-specific setup that might need to be completed for the new instance to work properly.
We’ve been looking at Kotlin constructors all along—we just didn’t know how to recognize them. In Kotlin the primary class constructor immediately follows the class declaration:
Again, if you are coming from Java or Python this constructor looks odd. It just looks like a method declaration with no body. So what is it doing?
In the spirit of reduction of ceremony the primary Kotlin constructor declares and generates code to set the values of the class instance variables.
Primary constructors are powerful and reflect a typical design pattern whereby
the constructor copies parameters into class instance variables.
Kotlin’s primary constructors also allow us to set variable visibility (
by default) and whether the instance variable is immutable (
val) or mutable
In many cases this default form of a constructor works just fine. But what if we want to create a synthetic field that is set based on other fields? The synthetic field should not be part of the constructor and we need to write some code to create it based on the value of other class instance variables.
To support this kind of behavior Kotlin also allows us to declare class instance variables inside the class declaration itself—more similar to what you might be use to in Java or Python:
However, the code above won’t run yet.
We’ve told Kotlin about the extra field
name that is not set in the
But Kotlin requires us to initialize the values of each instance variable when
the instance is created.
This requires that we add code to the default constructor, which we can do via
init has access to the same parameters passed to the primary
constructor—in this case
You can include multiple
init blocks in your class if you want, and think of
them as running in order top to bottom.
But it’s usually better to collapse them into one.
init blocks essentially become part of the primary constructor, and all the
code inside the
init block is run as soon as the primary constructor is run.
This becomes important to understand if and when you want to add secondary
constructors to your Kotlin classes.
At this point we’ve reached the limit of what we can discuss using data classes.
We’ll remove the
data modifier going forward.
Keep in mind that by doing so we lose some of the nice features that we got for
free with data classes—such as copy constructors and equals.
So we’ll need to provide this as needed going forward.
Full classes in Kotlin work quite similarly to the data classes that we’ve been using so far. But there are some limitations to data classes that are removed when we move to full classes that are immediately apparent.
For example, non-data classes can have an empty constructor:
Parameters to Kotlin constructors that are marked with
automatically create fields on that class:
If we remove the
var then the field is not automatically
created, even though the constructor will still accept the parameter:
However, these parameters are still available to the
init block that is run
as part of the primary constructor.
So a mimic of the more traditional Java or Python approach to object
construction in Kotlin would look like this:
Note that we don’t need to provide initial values even for non-nullable instance variables if Kotlin can convince itself that they will be set in the primary constructor. So the code above works, but this code does not:
Normally if all you are going to do in your
init block is set the variables
based on the constructor parameters, you might as well just mark them as
var and let Kotlin handle that for you.
But there are cases when you need to declare your instance variable inside the
class for other reasons.