Classes and Primary constructors
Here is an example of a class declaration in Kotlin:
Let's start with the header that says
This is a declaration of a class
Example that has a constructor that takes one parameter of type
Int. Such constructors, declared immediately in the class header, are called primary constructors.
To create a new instance of the class
Example, one simply calls its constructor, as if it were a regular function:
(Note that one does not need the new keyword.)
If a class does not have any constructors declared, one cannot instantiate it:
Note that a class with no constructors must not declare any state (properties with backing fields), or inherit from classes that have constructors.
What does the primary constructor do? It executes the initializers of properties, in the order of declaration. These initializers can use the constructor's parameters. In the example above, the only thing the constructor does is initialization of
Additionally, one can place "anonymous initializers" in the body of a class, just surrounding pieces of code with curly braces:
Here, the primary constructor first creates a new
HashSet, assigns it to
property and then adds the value of
param to this set.
Parameters of primary constructors can be prefixed with val or var keywords to declare corresponding properties in-place:
Class body is optional, and the example above is a complete declaration of a class that has two properties prop1 and prop2 and a primary constructor with two parameters, that initializes these properties in an obvious way.
So, it does not take you much code to declare a bean-style data class in Kotlin, at least it is a lot less code than in Java.
Classes have the following kinds of members:
All classes in Kotlin have a common superclass
Any, that is a default super for a class with no supertypes declared:
Any is not
java.lang.Object; in particular, it does not have any members, not even
toString(). This does not mean that you can not call, say,
toString() on any object: you can, but it will be an extension function. See Java interoperability#Object methods for more details.
To declare an explicit supertype, one puts it after a colon in the class header:
As you can see, the base type can (and must) be initialized right there, using the parameters of the primary constructor.
The open annotation on a class is the opposite of Java's final: it allows others to inherit from this class. By default, all classes in Kotlin are final, which corresponds to Item 17 of Effective Java: Design and document for inheritance or else prohibit it.
A class may have many supertypes. In Kotlin we do not distinguish between classes and interfaces: we only have classes that can declare properties and functions, and one class can inherit from multiple other classes, or as one might say, mix them in, by listing them comma-separated in the class header:
The override annotation is required for
Derived.v(). If it were missing, the compiler would complain. If there is no virtual annotation on a function, like
Base.nv(), declaring a method with the same signature in a subclass is illegal, either with override or without it. In a final class (e.g. a class with no open annotation), virtual members are prohibited.
A member marked override is itself virtual, i.e. it may be overridden in subclasses. If you want to prohibit re-overriding, use final:
Wait! How will I hack my libraries now?!
One issue with our approach to overriding (classes and members final by default) is that it would be difficult to subclass something inside the libraries you use to override some method that was not intended for overriding by the library designer, and introduce some nasty hack there.
We think that this is not a disadvantage, for the following reasons:
- Best practices say that you should not allow these hacks anyway
- People successfully use other languages (C++, C#) that have similar approach
- If people really want to hack, there still are ways: in some cases it will be possible to write your hack in Java, and Aspect frameworks always work for these purposes...
In Kotlin, implementation inheritance is regulated by the following rule: if a class inherits many implementations of the same member from its immediate superclasses, it must override this member and provide its own implementation (perhaps, using one of the inherited ones). To denote the supertype from which the inherited implementation is taken, we use this qualified by the supertype name in angle brackets, e.g. this
It's fine to inherit from both
B, and we have no problems with
C inherits only one implementation of each of these functions. But for
f() we have two implementations inherited by
C, and this we have to override
C and provide our own implementation that eliminates the ambiguity.
As in Java, a class and some of its members may be declared abstract. An abstract member does not have an implementation in its class. Thus, when some descendant inherits an abstract member, it does not count as an implementation:
Note that we do not need to annotate an abstract class open – it goes without saying. Neither need we annotate an abstract function virtual.
One can override a non-abstract virtual member with an abstract one:
Virtual properties and accessors
A property may be declared virtual as well as a function. This actually means that accessors of this property can be overridden:
One can make individual accessors virtual and override them one-by-one:
Now, let's look at the famous Diamond problem:
Now the question is: what happens in
main()? The answer is: nothing, really, this does not compile. Because
D inherits two (identical) implementations for each accessor of
v, and we should have overridden both these accessors:
Now, it's kind of obvious what happens in
main(), right? An object of
D contains two fields for different
v's: one for class
B and one for
C, but the overridden accessors of the property work with only one of them, and we get a reasonable result, namely, five is printed.
Of course, we could choose another implementation for our accessors. For example, we could do nothing in our setter, or write to
this<A>.v (in which case,
main() would print 10), or we could alter the getter. In any case, it would be unambiguous what happens.
The Delegation pattern has proven to be a good alternative to implementation inheritance, and Kotlin supports it natively requiring zero boilerplate code. A class
Derived can inherit from a type
Base and delegate all of its public methods to a specified object:
The by-clause in the supertype list for
Derived indicates that
b will be stored internally in objects of
Derived and the compiler will generate all the methods of
Base that forward to
In Kotlin, unlike Java, classes do not have static methods. In most cases, namespace-level functions form a good substitute for them, but there are a few cases when they don't. These cases involve access to class' internals (private members).
For example, to replace a constructor with a Factory method, one makes the constructor private and provides a function that calls the constructor. But if this function in located outside the class in question, it would not have any access to the constructor.
To address this issue (and to provide some other interesting features), Kotlin introduces a concept of a class object (the closest analog in other languages would be Companion objects in Scala). Roughly speaking, a class object for class
C is an object (in the sense of Object declaration) that is associated to
C. There may be not more than one class object for each class. A class object is declared inside its associated class, and thus it can access its private members. A class object for
C itself is (usually) not and instance of
C. For example:
At first you may think that this is just a way of grouping static members of a class together instead of mixing them with instance members: in Java we access static members of
C by calling
C.foo(), and the same happens with class object's members in Kotlin. But in fact there is an important difference: a class object can have supertypes, and
C, as an expression denotes this object as a value, so one can pass it around, say, as an argument for a function. Let's modify our example to demonstrate this:
Note that class objects are never inherited:
A description of some more interesting features related to class objects can be found in the Generic constaints section.
Best practices related to this feature
Effective Java Second Edition by Joshua Bloch
Item 16: Favor composition over inheritance
Item 17: Design and document for inheritance or else prohibit it
Item 18: Prefer interfaces to abstract classes
Similar features in other languages
IDEs automatically generate delegating methods.
Project Lombok implements delegation in Java with annotations.
Scala has traits.
Groovy has Categories and Mixins.