If you’ve been working with Kotlin for a while, you probably know how concise and expressive the language is. One of the features that makes Kotlin so enjoyable is its support for constructor references. This feature allows you to treat a constructor like a function and use it wherever a function is expected.
In this post, we’ll break down how to create instances with constructor references in Kotlin, step by step, using examples that are easy to follow. By the end, you’ll know exactly how and when to use this feature in real-world applications.
What are Constructor References in Kotlin?
In Kotlin, functions and constructors can be passed around just like any other value. A constructor reference is simply a shorthand way of pointing to a class’s constructor.
You use the ::
operator before the class name to create a reference. For example:
class User(val name: String, val age: Int)
// Constructor reference
val userConstructor = ::User
Here, ::User
is a reference to the User
class constructor. Instead of calling User("amol", 25)
directly, we can use the userConstructor
variable as if it were a function.
Why Use Constructor References?
You might be wondering, why not just call the constructor directly?
Constructor references shine in situations where you need to pass a constructor as an argument. This is common when working with higher-order functions, factory patterns, or functional-style APIs like map
.
It keeps your code clean, avoids repetition, and makes your intent very clear.
Creating Instances with Constructor References
Let’s walk through a few practical examples of how to create instances with constructor references in Kotlin.
class Person(val name: String, val age: Int)
fun main() {
// Create a reference to the constructor
val personConstructor = ::Person
// Use the reference to create instances
val person1 = personConstructor("amol", 30)
val person2 = personConstructor("ashvini", 25)
println(person1.name) // amol
println(person2.age) // 25
}
::Person
is a function reference to the constructor ofPerson
.- You can then call it like any function:
personConstructor("amol", 30)
.
Using with Higher-Order Functions
Suppose you have a list of names and ages, and you want to turn them into Person
objects. Instead of writing a lambda, you can pass the constructor reference directly.
data class Person(val name: String, val age: Int)
fun main() {
val peopleData = listOf(
"amol" to 30,
"ashvini" to 25,
"swaraj" to 28
)
// Map each pair into a Person instance using constructor reference
val people = peopleData.map { (name, age) -> Person(name, age) }
println(people)
}
Now, let’s make it even cleaner using a constructor reference:
val people = peopleData.map { Person(it.first, it.second) }
While Kotlin doesn’t allow passing ::Person
directly here (because the data is a Pair
), constructor references can still simplify code in similar contexts.
With Function Types
Constructor references can also be stored in variables with a function type.
class Car(val brand: String)
fun main() {
// Function type (String) -> Car
val carFactory: (String) -> Car = ::Car
val car = carFactory("Tesla")
println(car.brand) // Tesla
}
::Car
matches the function type(String) -> Car
.- Whenever you call
carFactory("Tesla")
, it creates a newCar
instance.
Primary vs Secondary Constructors
Kotlin classes can have both primary and secondary constructors. Constructor references can point to either.
class Student(val name: String) {
constructor(name: String, age: Int) : this(name) {
println("Secondary constructor called with age $age")
}
}
fun main() {
val primaryRef: (String) -> Student = ::Student
val secondaryRef: (String, Int) -> Student = ::Student
val student1 = primaryRef("amol")
val student2 = secondaryRef("ashvini", 20)
}
Here:
primaryRef
points to the primary constructor.secondaryRef
points to the secondary constructor.
Real-World Use Case: Dependency Injection
In frameworks like Koin or Dagger, you often need to tell the system how to create objects. Constructor references make this simple:
class Repository(val db: Database)
class Database
fun main() {
val dbFactory: () -> Database = ::Database
val repoFactory: (Database) -> Repository = ::Repository
val db = dbFactory()
val repo = repoFactory(db)
println(repo.db) // Instance of Database
}
This pattern is common when wiring dependencies because you can pass constructor references instead of custom lambdas.
Key Takeaways
- Constructor references allow you to treat constructors like functions in Kotlin.
- Use
::ClassName
to get a reference. - They’re especially useful with higher-order functions, dependency injection, and factory patterns.
- You can reference both primary and secondary constructors.
- They make your code cleaner, shorter, and easier to maintain.
Conclusion
Knowing how to create instances with constructor references in Kotlin is a small but powerful tool in your Kotlin toolkit. It makes functional programming patterns more natural, simplifies object creation in higher-order functions, and improves readability.
If you want your Kotlin code to be more expressive and maintainable, start using constructor references where they fit. It’s a simple change with big payoffs.