Kotlin is a powerful and modern programming language that has been gaining popularity in recent years due to its concise and expressive syntax, strong type system, and seamless interoperability with Java. One of the most important features of Kotlin that sets it apart from other languages is its support for constructors, which play a crucial role in creating objects and setting up their initial state.
Constructors in Kotlin are not just a simple way to create objects; they offer a wide range of options and flexibility to customize the object initialization process. In this article, we’ll take an in-depth look at Kotlin constructors and explore the different ways they can be used to create and configure objects, along with some best practices and examples. Whether you’re new to Kotlin or a seasoned developer, this article will provide you with a solid understanding of Kotlin constructors and how to use them to create powerful, flexible classes.
Constructors?
In object-oriented programming, a constructor is a special method that is used to initialize an object’s state when it is first created. A constructor is invoked automatically when an object is created, and it sets the initial values for the object’s properties and executes any initialization code.
Kotlin provides several types of constructors that can be used to create objects. Each constructor has a specific syntax and purpose, and we will discuss each of them in detail below.
Default Constructor
In Kotlin, a default constructor is generated automatically if no constructor is defined explicitly. This default constructor is used to create an instance of the class and initializes the class properties with their default values.
Here is an example of a class with a default constructor:
class Person {
var name: String = ""
var age: Int = 0
}
In this example, we have defined a class Person
with two properties name
and age
. As we have not defined any constructor, a default constructor is generated automatically.
We can create an instance of this class by simply calling the constructor like this:
val person = Person()
The person
object created using the default constructor will have the name
property initialized with an empty string and the age
property initialized with zero.
Primary Constructor
Kotlin provides two types of constructors for initializing objects: primary and secondary constructors.
The primary constructor is usually the main and concise way to initialize a class, and it is declared inside the class header in parentheses. It serves two purposes: specifying constructor parameters and defining properties that are initialized by those parameters.
Here’s an example of a class with a primary constructor:
class User(val nickname: String)
In this example, the block of code surrounded by parentheses is the primary constructor, and it has a single parameter named “nickname”. The “val” keyword before the parameter name declares the parameter as a read-only property.
You can also write the same code in a more explicit way using the “constructor” keyword and an initializer block:
class User constructor(_nickname: String) {
val nickname: String
init {
nickname = _nickname
}
}
In this example, the primary constructor takes a parameter named “_nickname” (with the underscore to distinguish it from the property name), and the “init” keyword introduces an initializer block that assigns the parameter value to the “nickname” property.
The primary constructor syntax is constrained, so if you need additional initialization logic, you can use initializer blocks to supplement it. You can declare multiple initializer blocks in one class if needed.
To elaborate, let’s take the example of a Person
class, which has a primary constructor that takes two parameters – name
and age
. We want to add additional initialization logic to the class, such as checking if the age is valid or not.
Here’s how we can do it using an initializer block:
class Person(val name: String, val age: Int) {
init {
if (age < 0) {
throw IllegalArgumentException("Age cannot be negative")
}
}
}
In this example, we use an initializer block to add additional initialization logic to the class. The initializer block is introduced with the init
keyword, and the code inside it is executed when an instance of the class is created.
The initializer block checks if the age
parameter is negative, and if so, throws an IllegalArgumentException
with an appropriate error message.
Note that you can declare multiple initializer blocks in a class if needed. For example, suppose we want to initialize some properties based on the constructor parameters. We can add another initializer block to the class like this:
class Person(val name: String, val age: Int) {
val isAdult: Boolean
init {
if (age < 0) {
throw IllegalArgumentException("Age cannot be negative")
}
isAdult = age >= 18
}
init {
println("Person object created with name: $name and age: $age")
}
}
In this example, we have two initializer blocks. The first one initializes the isAdult
property based on the age
parameter. The second one simply prints a message to the console.
So you can use initializer blocks to add additional initialization logic to a class, such as checking parameter values, initializing properties based on constructor parameters, or performing other setup tasks. You can declare multiple initializer blocks in a class if needed.
What about “constructor” keyword?
In Kotlin, if the primary constructor has no annotations or visibility modifiers, the constructor keyword can be omitted, and the constructor parameters are placed directly after the class name in parentheses. Here’s an example:
class User(val nickname: String)
In this case, the constructor
keyword is not used explicitly because there are no annotations or visibility modifiers.
However, if you need to add visibility modifiers, annotations, or other modifiers to the constructor, you need to declare it using the constructor
keyword. For example:
class User private constructor(val nickname: String)
In this example, we use the private
keyword to make the constructor private, and thus we need to use the constructor
keyword to declare it explicitly.
The primary constructor can also include annotations, and other modifiers as needed:
class Person @Inject constructor(private val name: String, var age: Int) {
// Class body
}
In this example, the primary constructor includes an @Inject annotation and a private visibility modifier for the name property.
So you can omit the
constructor
keyword in the primary constructor declaration if there are no modifiers or annotations. Otherwise, you need to use it to declare the constructor explicitly.
Default Parameter Values
Kotlin also allows us to provide default values for constructor parameters. This means that we can create an object without providing all the required arguments, as long as the missing arguments have default values.
class Person(val name: String, val age: Int = 0) {
// additional methods and properties can be defined here
}
In this example, the Person
class has a primary constructor with two parameters: name
and age
. However, the age
parameter has a default value of 0
, which means that we can create a Person
object with just the name
parameter:
val john = Person("John")
In this case, the john
variable is assigned a new instance of the Person
class with the name
property set to “John” and the age
property set to 0
.
Super Class Initialization
If your class has a superclass, the primary constructor also needs to initialize the superclass. You can do so by providing the superclass constructor parameters after the superclass reference in the base class list.
open class User(val nickname: String) { ... }
class TwitterUser(nickname: String) : User(nickname) { ... }
If you don’t declare any constructors for a class, a default constructor that does nothing will be generated for you.
open class Button
// The default constructor without arguments is generated.
If you inherit the Button
class and don’t provide any constructors, you have to explicitly invoke the constructor of the superclass.
class RadioButton: Button()
Here note the difference with interfaces: interfaces don’t have constructors, so if you implement an interface, you never put parentheses after its name in the supertype list.
Private Constructor
If you want to ensure that your class can’t be instantiated by other code, you have to make the constructor private. You can make the primary constructor private by adding the private
keyword before the constructor keyword.
class Secretive private constructor() {}
Here the Secretive class has only a private constructor, the code outside of the class can’t instantiate it.
Secondary Constructor
In addition to the primary constructor, Kotlin allows you to declare secondary constructors. Secondary constructors are optional, and they are defined inside the class body, after the primary constructor and initialization blocks.
A secondary constructor is defined using the constructor keyword followed by parentheses that can contain optional parameters.
open class View {
constructor(ctx: Context) {
// some code
}
constructor(ctx: Context, attr: AttributeSet) {
// some code
}
}
Don’t declare multiple secondary constructors to overload and provide default values for arguments. Instead, specify default values directly
Unlike the primary constructor, the secondary constructor must call the primary constructor, directly or indirectly, using this keyword
class Person(val name: String, val age: Int) {
constructor(name: String) : this(name, 0) // calls the primary constructor with age set to 0
}
Super Class Initialization
Here is an example that shows how to define a secondary constructor to initialize the superclass in a different way:
open class User(val nickname: String) {
// primary constructor
}
class TwitterUser : User {
constructor(email: String) : super(extractNicknameFromEmail(email)) {
// secondary constructor
}
private fun extractNicknameFromEmail(email: String): String {
// some code to extract the nickname from the email
return "someNickname"
}
}
In this example, the TwitterUser class has a secondary constructor that takes an email address as a parameter. The secondary constructor calls the primary constructor of the User class by passing a nickname value that is extracted from the email address.
Note that the secondary constructor is defined using the constructor keyword, followed by the email parameter. The constructor then calls the primary constructor of the superclass (User) using the super keyword with the extracted nickname value as the argument. Finally, the secondary constructor can perform additional initialization logic if needed.
super() or this()
In Kotlin, super()
and this()
are used to call constructors of the parent/super class and the current class respectively.
In a primary constructor, you can use this
to reference another constructor in the same class and super
to reference the constructor of the superclass.
open class View {
constructor(ctx: Context) {
// ...
}
constructor(ctx: Context, attrs: AttributeSet) {
// ...
}
}
class MyButton : View {
constructor(ctx: Context)
: this(ctx, MY_STYLE) {
// ...
}
constructor(ctx: Context, attrs: AttributeSet)
: super(ctx, attrs) {
// ...
}
// ...
}
The super()
is used to call the constructor of the immediate parent/super class of a derived class. It is typically used to initialize the properties or fields defined in the parent/super class. If the parent/super class has multiple constructors, you can choose which one to call by providing the appropriate arguments. For example:
open class Person(val name: String) {
constructor(name: String, age: Int) : this(name) {
// Initialize age property
}
}
class Employee : Person {
constructor(name: String, age: Int, id: Int) : super(name, age) {
// Initialize id property
}
}
In the above example, the Employee
class has a secondary constructor that calls the primary constructor of its parent/super class Person
with name
and age
arguments using super(name, age)
.
On the other hand, the this()
function is used to call another constructor of the same class. It can be used to provide multiple constructors with different parameters. If you call another constructor with this()
, it must be the first statement in the constructor. For example:
class Person(val name: String, val age: Int) {
constructor(name: String) : this(name, 0) // calls the primary constructor with age set to 0
}
In this example, the Person
class has a primary constructor that takes both name
and age
as parameters. It also has a secondary constructor that takes only the name
parameter and calls the primary constructor with age
set to 0 using the this()
keyword. this()
is useful when you have multiple constructors in a class and you want to avoid duplicating initialization logic.
Primary Constructor vs Secondary Constructor
- Syntax: The primary constructor is defined as part of the class header, inside parentheses, while secondary constructors are defined inside the class body and are prefixed with the
constructor
keyword. - Purpose: The primary constructor is mainly used to initialize the class properties with values passed as parameters, while secondary constructors provide an additional way to create objects of a class with different initialization logic.
- Constraints: The primary constructor has some constraints such as not allowing code blocks, while secondary constructors can have default parameter values and can contain code blocks.
- Invocation: The primary constructor is always invoked implicitly when an object of the class is created, while secondary constructors can be invoked explicitly by calling them with the
constructor
keyword. - Number: A class can have only one primary constructor, while it can have multiple secondary constructors.
- Initialization of superclass: The primary constructor can initialize the superclass by calling the superclass constructor in the class header, while the secondary constructor can initialize the superclass by calling the superclass constructor inside the constructor body with the
super
keyword.