This is part 3 of a series, that outlines the most common hints and quirks a java developer might face when switching to kotlin. This article handles following topics:
- Forgetting Default Arguments And Parameters
- Using
for
Loops Instead OfforEach
- Using
lazy
Initialization - Reference: Kotlin Coding Conventions
If you are interested in the previous articles, here you can find Part 1 and Part 2
Forgetting Default Arguments And Parameters
In contrast to Java, Kotlin allows the definition of default values for function parameters, eliminating the need for overloading functions with different parameter counts. This is especially useful when a function has many parameters that often take the same value.
Default Function Parameters
Java way:
public void greet(String name, String greeting) {
System.out.println(greeting + " " + name);
}
/**
* Overloaded "greet" method with default value
*/
public void greet(String name) {
greet(name, "Hello");
}
Kotlin way:
with default value for the parameter greeting
fun greet(name: String, greeting: String = "Hello") {
println("$greeting $name")
}
example calls:
greet("Alice") // Hello Alice
greet("Bob", "Hi") // Hi Bob
This approach removes duplicated code and reduces the method count, especially when the method has many parameters.
Default Constructor Arguments
Default values are not only allowed in function parameters, but also in constructor arguments. This is especially helpful when you need to create objects with optional parameters.
Java way:
public class Person {
private String name;
private int age;
public Person(String name) {
this(name, 42);
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public void introduce() {
System.out.println("Hi, I'm " + name + ", and I'm " + age + " years old.");
}
}
Usage in Java:
public class Main {
public static void main(String[] args) {
Person person1 = new Person("Alice"); // Uses default age 42
Person person2 = new Person("Bob", 25); // Uses custom age 25
person1.introduce(); // Outputs: Hi, I'm Alice, and I'm 42 years old.
person2.introduce(); // Outputs: Hi, I'm Bob, and I'm 25 years old.
}
}
Instead of overloading the constructor, you can use default values in Kotlin:
Kotlin way:
class Person(val name: String, val age: Int = 42) {
fun introduce() {
println("Hi, I'm $name, and I'm $age years old.")
}
}
Usage in Kotlin:
val person1 = Person("Alice") // Uses default age 42
val person2 = Person("Bob", 25) // Uses custom age 25
person1.introduce() // Outputs: Hi, I'm Alice, and I'm 42 years old.
person2.introduce() // Outputs: Hi, I'm Bob, and I'm 25 years old.
Using for
Loops Instead Of forEach
In Kotlin, you can use the higher-order function forEach
to iterate over collections, however, Kotlin's for
loop is often more idiomatic and can be a better choice in some cases. Understanding when to use which loop is crucial for writing clean Kotlin code.
The for
loop in Kotlin
Basic for
loop example using a range over a collection:
val numbers = listOf(1, 2, 3, 4, 5)
for (number in numbers) {
println(number)
}
Basic forEach
loop in Kotlin
The forEach
function is part of Kotlin's functional programming features, it takes a lambda function as an argument and executes it on each element of the collection:
val numbers = listOf(1, 2, 3, 4, 5)
numbers.forEach { number -> println(number) }
The element is available as it
by default, if you don't specify a name for the lambda parameter:
val numbers = listOf(1, 2, 3, 4, 5)
numbers.forEach { println(it) }
When to use for
loops
If you're performing simple iterations and don't need the additional power, the for
loop is usually the better choice, especially for performance reasons. forEach
involves function-calls (higher-order functions), which can incur slight overhead, while a for
loop is generally more efficient.
Example (Performance Consideration):
val numbers = listOf(1, 2, 3, 4, 5)
var sum = 0
for (number in numbers) {
sum += number
}
println(sum)
In this case, the for
loop does not involve any additional function calls, making it more efficient.
Example (Using break
and continue
):
The for
loop has built-in support for break
and continue
statements, allowing you control over the flow more easily.
val numbers = listOf(1, 2, 3, 4, 5)
for (number in numbers) {
if (number == 3) {
break // exit the loop when number is 3
}
println(number) // Will print 1, 2
}
When to use forEach
loops
1. Cleaner and more expressive for functional style programming
The forEach
function is more idiomatic when you're working with a functional programming style or when you want the code to be more declarative and expressive. If you don't need break
or continue
, the forEach
can be the more readable and elegant choice, especially for smaller functions.
Example (Functional Style):
val numbers = listOf(1, 2, 3, 4, 5)
numbers.forEach { println(it) }
2. When you want to use lambdas for more complex operations
forEach
allows you to use lambdas for more complex operations, such as filtering, mapping, or reducing elements.
Example (Using Lambda):
val numbers = listOf(1, 2, 3, 4, 5)
numbers.forEach {
val squared = it * it
println("The square of $it is $squared")
}
3. Functionally chaining operations
When working with collections, forEach
can be more intuitive when you're chaining multiple operations together like map
, `filter, etc.
Example (Chaining Operations):
`kotlin
val numbers = listOf(1, 2, 3, 4, 5)
numbers.filter { it % 2 == 0 }
.forEach { println(it) } // Prints only even numbers
`
Performance considerations
While forEach
is more convenient and expressive, there is a small performance overhead due to the lambda function calls. The difference in performance between those two is usually minimal for small collections, however, if performance is critical, especially with large collections, for
loops might be preferable.
Using lazy
Initialization
Another Kotlin-feature is the lazy initialisation, which allows to defer the creation of an object or the evaluation of an expression until it's actually needed. This can be useful to improve performance of your application by delaying expensive computations until they are actually needed. Kotlin's lazy
initialisation is thread-safe by default.
Example: Lazy Initialization
`kotlin
val name: String by lazy {
println("Initializing name")
"Kotlin"
}
fun main() {
println("Before accessing name")
println(name)
println("After accessing name")
// Output:
// Before accessing name
// Initializing name
// Kotlin
// After accessing name
}
`
Example: Lazy Initialization for Expensive Objects
Example of lazy database connection
`kotlin
class Database {
init {
println("Connecting to database...")
}
fun queryData(): String {
return "Database Data"
}
}
class DataFetcher {
val database: Database by lazy { Database() } // only created when accessed
fun fetchData() = database.queryData()
}
fun main() {
val dataFetcher = DataFetcher()
println("Before fetching data")
println(dataFetcher.fetchData()) // the database-connection is created here
println("After fetching data")
// Output:
// Before fetching data
// Connecting to database...
// Database Data
// After fetching data
}
`
Notice that the database-connection is only created when the database
property is actually accessed.
Other use-cases
Other use-cases for lazy
initialization include:
- Singletons
- Heavy computation
- Resources, such as database connections, large datasets or API clients
- Optional features, that might not be used in every execution path
Kotlin Coding Conventions
Kotlin has dedicated a whole page to coding conventions, which you can find here.
Please make yourself familiar with these conventions, as they are widely accepted in the Kotlin community.
Top comments (0)