Skip to end of metadata
Go to start of metadata
You are viewing an old version of this page. View the current version. Compare with Current  |   View Page History

Classes and Primary constructors

Here is an example of a class declaration in Kotlin:

class Example(param : Int) { 
  val property = param 
}

Let's start with the header that says

class Example(param : Int) {

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:

val e = Example(10)

(Note that one does not need the new keyword.)

If a class does not have any constructors declared, one cannot instantiate it:

class Some {} 
 
val s = 
Some()
 // Error: no constructor for Some

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 property.

Additionally, one can place "anonymous initializers" in the body of a class, just surrounding pieces of code with curly braces:

class ExampleWithAnonymousInitializer(param : Int) { 
  val property = HashSet<Int>() 
  { 
    property.add(param) 
  } 
}

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 Bean(val prop1 : Int, val prop2 : String)

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.

Class members

Classes have the following kinds of members:

Inheritance

All classes in Kotlin have a common superclass Any, that is a default super for a class with no supertypes declared:

class Example // Implicitly inherits from Any

Any is not java.lang.Object; in particular, it does not have any members, not even equals(), hashCode or 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:

open class Base(p : Int) 
 
class Derived(p : Int) : Base(p)

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:

open class A { 
  fun a() {} 
} 
 
open class B(val b : Int) 
 
class C(x : Int) : A, B(x) { 
  // fun a() {} is inherited from A 
  // val b : Int is inherited from B, and initialized with the value of constructor parameter x 
}

Overriding members

As we mentioned before, we stick to making things explicit in Kotlin. And unlike Java, Kotlin requires explicit annotations for virtual members and for overrides:

open class Base { 
  virtual fun v() {} 
  fun nv() {} 
} 
class Derived() : Base { 
  override fun v() {} 
}

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:

open class AnotherDerived() : Base { 
  final override fun v() {} 
}

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:

  1. Best practices say that you should not allow these hacks anyway
  2. People successfully use other languages (C++, C#) that have similar approach
  3. 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...

Overriding rules

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<Base>:

open class A { 
  virtual fun f() { print("A") } 
  fun a() { print("a") } 
} 
 
open class B { 
  virtual fun f() { print("B") } 
  virtual fun b() { print("b") } 
} 
 
class C() : A, B { 
  // The compiler requires f() to be overridden: 
  override fun f() { 
    this<A>.f() // call to A.f() 
    this<B>.f() // call to B.f() 
  } 
}

It's fine to inherit from both A and B, and we have no problems with a() and b() since 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 f() in C and provide our own implementation that eliminates the ambiguity.

Abstract classes

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:

abstract class A { 
  abstract fun f() 
} 
 
open class B { 
  virtual fun f() { print("B") } 
} 
 
class C() : A, B { 
  // We are not required to override f() 
}

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:

open class Base { 
  virtual fun f() {} 
} 
 
abstract class Derived : Base { 
  override abstract fun f() 
}

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:

open class Base { 
  virtual val p : Int 
    get() = 1 
} 
class Derived : Base { 
  override val p : Int 
    get() = 2 
}

One can make individual accessors virtual and override them one-by-one:

open class Base { 
  var p : Int 
    get() = 1 
    virtual set(value) { /* do nothing */ } 
} 
class Derived : Base { 
  var p : Int 
    /* get is inherited as it was */ 
    override set(value) { print(value) } 
}

Diamond-inheritance

Now, let's look at the famous Diamond problem:

open class A(virtual var v : int) 
 
open class B(v : Int) : A(v) 
open class C(v : Int) : A(v) 
 
class D(v : Int) : B(v), C(v) 
 
fun main(args : Array<String>) { 
  val d = D(10) 
  d.v = 5 
  print(d.v) 
}

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:

class D(v : Int) : B(v), C(v) { 
  override var v : Int 
    get() = this<B>.v 
    set(value) { this<B>.v = value } 
}

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.

Delegation

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:

open class Base(x : Int) { 
  fun print() { print(x) } 
} 
 
class Derived(b : Base) : Base by b 
 
fun main() { 
  val b = Base(10) 
  Derived(b).print() // prints 10 
}

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 b.

Generic classes

See Generics

Class objects

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:

class C() { 
  class object { 
    fun create() = C() 
  } 
} 
 
fun main() { 
  val c = C.create() // C denotes the class object here 
}

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:

abstract class Factory<out T> { 
  fun create() : T 
} 
 
open class C() { 
  class object : Factory<C> { 
    override fun create() : C = C() 
  } 
} 
 
fun main() { 
  val factory = C // C denotes the class object 
  val c = factory.create() 
}

Note that class objects are never inherited:

class D : C() 
 
val d = 
D
.create() // Error: no class object for D

A description of some more interesting features related to class objects can be found in the Generic constaints section.

Note: if you think that class objects are a great way of implementing singletons in Kotlin, please see Object expressions and Declarations.

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.

What's next

Labels:
None
Enter labels to add to this page:
Please wait 
Looking for a label? Just start typing.