Kotlin

Kotlin Inheritance

Kotlin Inheritance Explained: Write Smarter, Reusable Code

Kotlin makes object-oriented programming simple and powerful with its inheritance system. If you’re coming from Java or another OOP language, understanding Kotlin inheritance will help you write smarter, reusable code with fewer lines and more efficiency.

In this guide, we’ll break down Kotlin inheritance step by step, making it easy to grasp and apply in real-world scenarios.

What Is Kotlin Inheritance?

Inheritance is a fundamental concept in object-oriented programming (OOP). It allows a class to acquire the properties and behaviors of another class, reducing code duplication and improving maintainability.

In Kotlin, inheritance works by creating a base class (parent class) and allowing other classes (child classes) to derive from it.

By default, all Kotlin classes are final (cannot be inherited). To make a class inheritable, you must explicitly use the open keyword.

Kotlin
// Parent class
open class Animal {
    fun eat() {
        println("This animal is eating")
    }
}

// Child class inheriting from Animal
class Dog : Animal() {
    fun bark() {
        println("The dog is barking")
    }
}
fun main() {
    val myDog = Dog()
    myDog.eat() // Inherited method
    myDog.bark() // Child class method
}

Here,

  • The Animal class is open, making it inheritable.
  • The Dog class extends Animal using the : symbol.
  • The Dog class gets access to the eat() function from Animal.
  • The bark() function is specific to Dog.

Primary Constructor in Kotlin Inheritance

When a child class inherits from a parent class that has a constructor, it must initialize it. Here’s how it works:

Kotlin
open class Animal(val name: String) {
    fun eat() {
        println("$name is eating")
    }
}

class Dog(name: String) : Animal(name) {
    fun bark() {
        println("$name is barking")
    }
}
fun main() {
    val myDog = Dog("Buddy")
    myDog.eat()
    myDog.bark()
}

What’s Happening Here?

  • The Animal class has a primary constructor with a name parameter.
  • The Dog class calls the Animal constructor using : Animal(name).
  • When we create a Dog object, we pass a name that is used in both eat() and bark().

Overriding Methods in Kotlin

Kotlin allows child classes to modify or override methods from the parent class using the override keyword.

Kotlin
open class Animal {
    open fun makeSound() {
        println("Animal makes a sound")
    }
}

class Dog : Animal() {
    override fun makeSound() {
        println("Dog barks")
    }
}
fun main() {
    val myDog = Dog()
    myDog.makeSound()
}

Here,

  • The makeSound() function in Animal is open, allowing it to be overridden.
  • Dog provides its own implementation using override.
  • Now, when makeSound() is called on Dog, it prints “Dog barks” instead of “Animal makes a sound”.

If we remove open from the method but keep the class open, will it affect our code? Yes, we will get the following compile-time error:

Kotlin
'makeSound' in 'Animal' is final and cannot be overridden.

Using Superclass Methods

Sometimes, you want to modify a method but still call the original method from the parent class. You can do this using super.

Kotlin
open class Animal {
    open fun makeSound() {
        println("Animal makes a sound")
    }
}

class Dog : Animal() {
    override fun makeSound() {
        super.makeSound()
        println("Dog barks")
    }
}
fun main() {
    val myDog = Dog()
    myDog.makeSound()
}

Here, super.makeSound() calls the parent class method before executing the child class implementation.

Abstract Classes in Kotlin Inheritance

An abstract class cannot be instantiated and may contain abstract methods that must be implemented by child classes.

Kotlin
abstract class Animal {
    abstract fun makeSound()
}

class Dog : Animal() {
    override fun makeSound() {
        println("Dog barks")
    }
}
fun main() {
    val myDog = Dog()
    myDog.makeSound()
}

Why Use Abstract Classes?

  • They define a template for subclasses.
  • They ensure that all child classes implement necessary methods.

Interfaces vs. Inheritance in Kotlin

Kotlin also supports interfaces, which are similar to abstract classes but allow multiple implementations.

Kotlin
interface Animal {
    fun makeSound()
}

class Dog : Animal {
    override fun makeSound() {
        println("Dog barks")
    }
}
fun main() {
    val myDog = Dog()
    myDog.makeSound()
}

Key Differences:

  • A class can inherit only one superclass but implement multiple interfaces.
  • Interfaces cannot store state (i.e., no instance variables), while abstract classes can.

Conclusion

Kotlin inheritance is a powerful feature that helps you write smarter, reusable code. By using open classes, overriding methods, abstract classes, and interfaces, you can structure your code efficiently.

Here’s a quick recap:

  • Use open to make a class inheritable.
  • Override methods with override.
  • Use super to call the parent method.
  • Use abstract for mandatory implementations.

By mastering Kotlin inheritance, you can build scalable, maintainable, and clean applications.

Restrictions for public API inline functions

Restrictions For Public API Inline Functions in Kotlin and How to Avoid Them

Kotlin’s inline functions offer powerful performance optimizations, but when used in public APIs, they come with specific restrictions. Understanding these limitations helps ensure compatibility, maintainability, and adherence to best coding practices. 

In this blog, we’ll explore the restrictions for public API inline functions and discuss ways to work around them.

Restrictions for public API inline functions

In Kotlin, when you have an inline function that is public or protected, it is considered part of a module’s public API. This means that other modules can call that function, and the function itself can be inlined at the call sites in those modules.

However, there are certain risks of binary incompatibility that can arise when changes are made to the module that declares the inline function, especially if the calling module is not re-compiled after the change.

To mitigate these risks, there are restrictions placed on public API inline functions. These functions are not allowed to use non-public-API declarations, which include private and internal declarations and their parts, within their function bodies.

Using Private Declarations

Kotlin
private fun privateFunction() {
    // Implementation of private function
}

inline fun publicAPIInlineFunction() {
    privateFunction() // Error: Private declaration cannot be used in a public API inline function
    // Rest of the code
}

In this scenario, we have a private function privateFunction(). When attempting to use this private function within the public API inline function publicAPIInlineFunction(), a compilation error will occur. The restriction prevents the usage of private declarations within public API inline functions.

Using Internal Declarations

Kotlin
internal fun internalFunction() {
    // Implementation of internal function
}

inline fun publicAPIInlineFunction() {
    internalFunction() // Error: Internal declaration cannot be used in a public API inline function
    // Rest of the code
}

In this scenario, we have an internal function internalFunction(). When trying to use this internal function within the public API inline function publicAPIInlineFunction(), a compilation error will arise. The restriction prohibits the usage of internal declarations within public API inline functions.

To eliminate this restriction and allow the use of internal declarations in public API inline functions, you can annotate the internal declaration with @PublishedApi. This annotation signifies that the internal declaration can be used in public API inline functions. When an internal inline function is marked with @PublishedApi, its body is checked as if it were a public function.

Using Internal Declarations with @PublishedApi

Kotlin
@PublishedApi
internal fun internalFunction() {
    // Implementation of internal function
}

inline fun publicAPIInlineFunction() {
    internalFunction() // Allowed because internalFunction is annotated with @PublishedApi
    // Rest of the code
}

In this scenario, we have an internal function internalFunction() that is annotated with @PublishedApi. This annotation indicates that the internal function can be used in public API inline functions. Therefore, using internalFunction() within the public API inline function publicAPIInlineFunction() is allowed.

By applying @PublishedApi to the internal declaration, we explicitly allow its usage in public API inline functions, ensuring that the function remains compatible and can be safely used in other modules.

So, the restrictions for public API inline functions in Kotlin prevent them from using non-public-API declarations. However, by annotating internal declarations with @PublishedApi, we can exempt them from this restriction and use them within public API inline functions, thereby maintaining compatibility and enabling safe usage across modules.

This is just a part..! Read the full version here: [Main Article URL]

Conclusion

Restrictions for public API inline functions are crucial for ensuring stability and maintainability. While inline functions provide performance benefits, they must be used cautiously in public APIs to prevent breaking changes, excessive code duplication, and accessibility issues.

These restrictions help maintain binary compatibility and ensure that code remains adaptable to future changes. Understanding them and following best practices allows you to write efficient and robust Kotlin code that is both scalable and maintainable.

When designing public APIs with inline functions, always consider factors like binary compatibility, accessibility, and maintainability. Adhering to these principles helps you avoid unexpected issues while keeping your Kotlin code clean, efficient, and future-proof.

Kotlin OOP Essentials

Kotlin OOP Essentials: Everything About Classes, Properties & Methods

Kotlin is a powerful programming language that fully supports Object-Oriented Programming (OOP). If you’re diving into Kotlin OOP Essentials, understanding classes, properties, and methods is crucial. This guide will break down these core concepts in a simple and engaging way, ensuring you master Kotlin’s OOP capabilities.

Classes

Classes serve as the fundamental building blocks in Kotlin, offering a template that encapsulates state, behavior, and a specific type for instances (more details on this will be discussed later). Defining a class in Kotlin requires only a name. For instance:

Kotlin
class VeryBasic

While VeryBasic may not be particularly useful, it remains a valid Kotlin syntax. Despite lacking state or behavior, instances of the VeryBasic type can still be declared, as demonstrated below:

Kotlin
fun main(args: Array<String>) {
    val basic: VeryBasic = VeryBasic()
}

In this example, the basic value is of type VeryBasic, indicating that it is an instance of the VeryBasic class. Kotlin’s type inference capability allows for a more concise declaration:

Kotlin
fun main(args: Array<String>) {
    val basic = VeryBasic()
}

In this revised version, Kotlin infers the type of the basic variable. As a VeryBasic instance, basic inherits the state and behavior associated with the VeryBasic type, which, in this case, is none—making it a somewhat melancholic example.

Properties

As mentioned earlier, classes in Kotlin can encapsulate a state, with the class’s state being represented by properties. Let’s delve into the example of a BlueberryCupcake class:

Kotlin
class BlueberryCupcake {
    var flavour = "Blueberry"
}

Here, the BlueberryCupcake class possesses a property named flavour of type String. Instances of this class can be created and manipulated, as demonstrated in the following code snippet:

Kotlin
fun main(args: Array<String>) {
    val myCupcake = BlueberryCupcake()
    println("My cupcake has ${myCupcake.flavour}")
}

Given that the flavour property is declared as a variable, its value can be altered dynamically during runtime:

Kotlin
fun main(args: Array<String>) {
    val myCupcake = BlueberryCupcake()
    myCupcake.flavour = "Almond"
    println("My cupcake has ${myCupcake.flavour}")
}

In reality, cupcakes do not change their flavor, unless they become stale. To mirror this in code, we can declare the flavour property as a value, rendering it immutable:

Kotlin
class BlueberryCupcake {
    val flavour = "Blueberry"
}

Attempting to reassign a value to a property declared as a val results in a compilation error, as demonstrated below:

Kotlin
fun main(args: Array<String>) {
    val myCupcake = BlueberryCupcake()
    myCupcake.flavour = "Almond" // Compilation error: Val cannot be reassigned
    println("My cupcake has ${myCupcake.flavour}")
}

Now, let’s introduce a new class for almond cupcakes, the AlmondCupcake class:

Kotlin
class AlmondCupcake {
    val flavour = "Almond"
}

Interestingly, both BlueberryCupcake and AlmondCupcake share identical structures; only the internal value changes. In reality, you don’t need different baking tins for distinct cupcake flavors. Similarly, a well-designed Cupcake class can be employed for various instances:

Kotlin
class Cupcake(val flavour: String)

The Cupcake class features a constructor with a flavour parameter, which is assigned to the flavour property. In Kotlin, to enhance readability, you can use syntactic sugar to define it more succinctly:

Kotlin
class Cupcake(val flavour: String)

This streamlined syntax allows us to create several instances of the Cupcake class with different flavors:

Kotlin
fun main(args: Array<String>) {
    val myBlueberryCupcake = Cupcake("Blueberry")
    val myAlmondCupcake = Cupcake("Almond")
    val myCheeseCupcake = Cupcake("Cheese")
    val myCaramelCupcake = Cupcake("Caramel")
}

In essence, this example showcases how Kotlin’s concise syntax and flexibility in property declaration enable the creation of classes representing real-world entities with ease.

Methods

In Kotlin, a class’s behavior is defined through methods, which are technically member functions. Let’s explore an example using the Cupcake class:

Kotlin
class Cupcake(val flavour: String) {
    fun eat(): String {
        return "nom, nom, nom... delicious $flavour cupcake"
    }
}

In this example, the eat() method is defined within the Cupcake class, and it returns a String value. To demonstrate, let’s call the eat() method:

Kotlin
fun main(args: Array<String>) {
    val myBlueberryCupcake = Cupcake("Blueberry")
    println(myBlueberryCupcake.eat())
}

Executing this code will produce the following output:

Kotlin
nom, nom, nom... delicious Blueberry cupcake

While this example may not be mind-blowing, it serves as an introduction to methods.

Conclusion

Mastering Kotlin OOP Essentials is key to writing scalable and maintainable applications. By understanding classes, properties, and methods, you can design robust object-oriented programs in Kotlin. Whether you’re building simple applications or large-scale projects, these concepts will serve as a strong foundation.

Type Erasure

Understanding Type Erasure in Kotlin Generics

Generics are a powerful feature in Kotlin that allow us to write flexible and reusable code. However, one of the key aspects to understand when working with generics is Type Erasure. If you’ve ever tried to inspect a generic type at runtime and found unexpected behavior, you’ve likely encountered type erasure in Kotlin generics.

In this post, we’ll break down Type Erasure in Kotlin Generics, explain why it happens, and how to work around its limitations.

What is Type Erasure in Kotlin Generics?

Type erasure is a process where generic type information is removed (erased) at runtime. This happens because Kotlin, like Java, relies on the JVM, which does not retain generic type parameters during runtime. Instead, all generic types are replaced with their upper bound (often Any?) when the code is compiled.

This means that while we can specify a generic type at compile time, that type information is lost when the code is running. As a result, certain operations that rely on runtime type checking, such as type comparisons, become problematic.

Let’s first see a simple example to understand type erasure.

Kotlin
fun <T> printType(value: T) {
    println("Type: " + value!!::class.simpleName)  //Expression in class literal has nullable type 'T'. so used '!!' to make the type non-nullable.
}

fun main() {
    printType(42)       // Output: Type: Int
    printType("Hello")  // Output: Type: String
}

Here, this will work well because value!!::class.simpleName gives us some runtime type information. But the problem arises when we try to inspect a collection of generics.

Consider the following code:

Kotlin
fun <T> isListOfStrings(list: List<T>): Boolean {
    return list is List<String>
}

fun main() {
    val strings = listOf("Kotlin", "Generics")
    println(isListOfStrings(strings))
}

You might expect isListOfStrings(strings) to return true, but instead, it won’t compile (CE: Cannot check for instance of erased type ‘kotlin.collections.List<kotlin.String>) because Kotlin prevents direct generic type checks due to type erasure.

At runtime, List<String> and List<Int> both become just List<?>, meaning the type distinction is lost. This is what type erasure does—it removes specific type details.

Why Does Type Erasure Happen?

Kotlin runs on the Java Virtual Machine (JVM), which historically did not support reified generics. Java introduced generics in Java 5, but to maintain backward compatibility, the compiler removes type parameters at runtime. Kotlin inherits this behavior from Java.

For example, both List<String> and List<Int> compile down to just List in bytecode. This allows Java and Kotlin to remain compatible with older Java versions, but at the cost of losing runtime type safety.

How to Work Around Type Erasure

Even though type erasure limits what we can do at runtime, Kotlin provides workarounds to help mitigate these issues.

Use Reified Type Parameters with Inline Functions

One of Kotlin’s best solutions for type erasure is inline functions with reified type parameters. Normally, generic types are erased, but reified types retain their type information at runtime.

Kotlin
inline fun <reified T> isTypeMatch(value: Any): Boolean {
    return value is T
}

fun main() {
    println(isTypeMatch<String>("Hello")) // true
    println(isTypeMatch<Int>("Hello"))    // false
}

Since the function is inline, the compiler replaces it with actual type arguments during compilation, preserving type information.

Use Explicit Type Tokens

Another workaround is using explicit type tokens by passing a Class<T> reference:

Kotlin
fun <T> printType(type: Class<T>) {
    println("Type is: ${type.simpleName}")
}

fun main() {
    printType(String::class.java) // Output: Type is: String
}

Conclusion

Understanding type erasure in Kotlin generics is essential when working with generics on the JVM. Due to type erasure, generic type information is removed at runtime, leading to certain limitations like the inability to check generic types, create instances, or overload functions based on generic parameters.

However, Kotlin provides solutions like reified type parameters, class references, and type tokens to mitigate these issues. By using these techniques, you can work around type erasure and write more effective, type-safe Kotlin code.

fully and partially checked exceptions

How Java Handles Fully and Partially Checked Exceptions: A Deep Dive

Exception handling is a crucial part of Java programming. It ensures that programs can gracefully handle unexpected situations without crashing. Java categorizes exceptions into checked and unchecked exceptions, but a lesser-known distinction exists within checked exceptions: fully and partially checked exceptions.

In this blog, we’ll explore how Java handles fully and partially checked exceptions, using clear explanations and examples to help you understand the topic deeply.

Understanding Checked Exceptions

Checked exceptions are exceptions that the compiler mandates you to handle explicitly. If your code calls a method that throws a checked exception, you must either handle it using a try-catch block or declare it using the throws keyword.

Java
import java.io.*;

public class CheckedExceptionExample {
    public static void main(String[] args) {
        try {
            FileReader file = new FileReader("path\test.txt");
        } catch (FileNotFoundException e) {
            System.out.println("File not found: " + e.getMessage());
        }
    }
}

Here, FileNotFoundException is a checked exception. The compiler forces us to handle it because it is anticipated that a file may not always be available.

Fully Checked Exceptions in Java

A fully checked exception is an exception where all its subclasses are also checked exceptions. This means that whenever you work with this exception or any of its subclasses, the compiler enforces explicit handling.

Example of a Fully Checked Exception

IOException is a fully checked exception because all its subclasses, such as FileNotFoundException and EOFException, are also checked exceptions.

Java
import java.io.*;

public class FullyCheckedExceptionExample {
    public static void main(String[] args) {
        try {
            throw new IOException("IO Error Occurred");
        } catch (IOException e) {
            System.out.println("Caught IOException: " + e.getMessage());
        }
    }
}

Here, the IOException must be handled explicitly, and its subclasses follow the same rule, making it a fully checked exception.

Partially Checked Exceptions in Java

A partially checked exception is an exception where some of its subclasses are checked exceptions, while others are unchecked exceptions.

The classic example of a partially checked exception is Exception. While many of its subclasses (like IOException and SQLException) are checked exceptions, others (like RuntimeException and its subclasses) are unchecked exceptions.

Example of a Partially Checked Exception

Java
public class PartiallyCheckedExceptionExample {
    public static void main(String[] args) {
        try {
            throw new Exception("General Exception");
        } catch (Exception e) {
            System.out.println("Caught Exception: " + e.getMessage());
        }
    }
}

Here, Exception is a checked exception, but its subclass RuntimeException is unchecked, making Exception a partially checked exception.

How Java Handles Fully and Partially Checked Exceptions

Handling Fully Checked Exceptions

For fully checked exceptions, Java forces you to handle them at compile time. This ensures robust error handling but may sometimes lead to boilerplate code.

Handling Partially Checked Exceptions

With partially checked exceptions, the handling depends on which subclass you’re dealing with. If it’s a checked subclass (like IOException), you must handle it explicitly. However, if it’s an unchecked subclass (like NullPointerException), you don’t have to handle it, though it’s still advisable to do so where necessary.

Key Differences Between Fully and Partially Checked Exceptions

Exception TypeFully Checked ExceptionPartially Checked Exception
DefinitionAll subclasses are checkedSome subclasses are checked, some are unchecked
ExampleIOException (all subclasses checked)Throwable (Exception is checked, but Error is unchecked)
Compiler HandlingMust handle or declare all subclassesMust handle only the checked subclasses

Some More Examples:

  • IOException: Fully checked exception.
  • RuntimeException: Unchecked exception.
  • InterruptedException: Fully checked exception.
  • Error: Unchecked exception.
  • Throwable: Partially checked exception.
  • ArithmeticException: Unchecked exception.
  • NullPointerException: Unchecked exception.
  • Exception: Partially checked exception.
  • FileNotFoundException: Fully checked exception.

Why Does Java Make This Distinction?

The main reason behind fully and partially checked exceptions is flexibility. Java enforces explicit handling for critical exceptions (e.g., file handling issues) while allowing unchecked exceptions (e.g., null pointer dereferences) to be handled at runtime without forcing developers to clutter code with try-catch blocks.

Conclusion

Understanding fully and partially checked exceptions in Java helps developers write better error-handling code. Fully checked exceptions require mandatory handling, ensuring code safety. Partially checked exceptions offer a balance between strict compilation checks and runtime flexibility. By mastering this distinction, you can write cleaner, more efficient Java programs while maintaining robust exception-handling practices.

By following Java’s structured approach to exception handling, you can build more reliable applications that gracefully recover from errors, leading to a better user experience and easier debugging.

crossinline modifier

Why Does Kotlin Have crossinline Modifier? Exploring the Need for This Modifier

Kotlin provides several function modifiers that improve code safety, performance, and flexibility. One such modifier is crossinline. If you’ve ever used inline functions in Kotlin, you might have come across crossinline but wondered why it’s needed. In this blog, we’ll break it down step by step, explaining why Kotlin has the crossinline modifier, when to use it, and how it works.

Before diving into crossinline, let’s first understand inline functions.

Understanding inline Functions in Kotlin

Kotlin allows functions to be marked as inline, meaning the compiler replaces function calls with the actual code at compile time. This helps reduce memory overhead caused by lambda expressions and improves performance.

Kotlin
inline fun execute(block: () -> Unit) {
    println("Before execution")
    block()
    println("After execution")
}

fun main() {
    execute {
        println("Inside block")
    }
}

//Output

Before execution
Inside block
After execution

Here, execute is an inline function, meaning the lambda function passed as block gets inlined at the call site, reducing the function call overhead.

Why Do We Need crossinline Modifier?

While inline functions provide performance benefits, they also introduce a limitation: they allow non-local returns from lambda expressions passed as parameters. This means a lambda can return from the calling function, which can sometimes be dangerous.

Kotlin
inline fun perform(action: () -> Unit) {
    action()
    println("This will not execute if action() returns early")
}

fun main() {
    perform {
        println("Executing action")
        return  // Non-local return, exits `main()` function
    }
    println("This will not print")
}

// Output
 
Executing action

The return inside the lambda exits the main() function completely, skipping the rest of the code.

This Is Where crossinline Helps..!

If you don’t want the lambda to allow non-local returns, you use crossinline. This forces the lambda to behave like a regular function, preventing premature function exits.

How Does crossinline Modifier Work?

When a lambda parameter is marked with crossinline, it means that the lambda cannot use non-local returns but can still be inlined. This is particularly useful when passing the lambda to another function inside the inline function.

Kotlin
inline fun performSafely(crossinline action: () -> Unit) {
    println("Before action")
    
    // Lambda is stored in a Runnable object instead of being executed immediately
    val runnable = Runnable { action() } 
    
    // Now the lambda is executed
    runnable.run()
    
    println("After action")
}

fun main() {
    performSafely {
        println("Executing action")
        // return  // This will cause a compilation error
    }
}

// Output 

Before action
Executing action
After action
  • Here, if we try to use return inside the lambda, Kotlin throws a compilation error because crossinline prevents non-local returns.
  • Without Runnable, the lambda (action) runs immediately inside performSafely.
  • But when we use Runnable, the lambda gets stored inside an object and runs later.

When We Should Use crossinline Modifier?

We should use crossinline in the following cases:

  1. When passing a lambda to another function: If you store the lambda inside an object or pass it to another function, a non-local return might not make sense. crossinline prevents such issues.
  2. When ensuring predictable execution: If you want the lambda to behave like a normal function (without returning early), crossinline ensures safe execution.
  3. When working with multi-threading: If the lambda gets executed in another thread, a non-local return would cause unexpected behavior, making crossinline necessary.

Avoid using crossinline modifier if:

  • You need the flexibility of non-local returns.
  • The function is simple and doesn’t require lambda constraints.

For an in-depth guide, visit: [Main Article URL]

Conclusion

The crossinline modifier in Kotlin serves a crucial role in ensuring safe execution of lambdas in inline functions. It prevents unexpected non-local returns, making code more predictable when working with higher-order functions.

Next time you’re writing an inline function and passing lambdas around, think about whether crossinline is needed to prevent unintended control flow issues.

Understanding the crossinline modifier helps you write more robust and predictable Kotlin code. Now that you know why Kotlin has crossinline, you can use it effectively in your projects..!

Type Checks in Kotlin

Understanding Type Checks in Kotlin: How is and !is Work

Type checking is a fundamental concept in programming, and Kotlin makes it simple with is and !is. These operators help you check whether an object is of a certain type and allow you to write safe and concise code. In this post, we’ll break down how type checks (is and !is) in Kotlin work, with clear examples to help you understand their use cases.

What Are Type Checks in Kotlin?

In Kotlin, every variable has a type. Sometimes, you need to confirm whether a variable belongs to a particular type before performing operations on it. The is operator checks if an object is of a specified type, while !is checks if it is not.

So, Kotlin provides two primary operators for type checking:

  • is – Checks if an object is of a particular type.
  • !is – Checks if an object is NOT of a particular type.

Let’s explore each of these in detail with examples.

How the is Operator Works

The is operator checks whether an object is of a specific type. If the object matches the type, it returns true; otherwise, it returns false.

Kotlin
fun checkType(value: Any) {
    if (value is String) {
        println("It's a string with length: ${value.length}")
    } else {
        println("Not a string")
    }
}

fun main() {
    checkType("Hello, Kotlin!")  // Output: It's a string with length: 14
    checkType(42)                 // Output: Not a string
}

Here,

  • value is String checks if value is of type String.
  • If value is indeed a String, Kotlin automatically casts it within the if block.
  • This smart casting avoids explicit type conversion, making the code cleaner.

How the !is Operator Works

The !is operator is the opposite of is. It checks whether an object is not of a certain type.

Kotlin
fun checkNotType(value: Any) {
    if (value !is Int) {
        println("Not an Integer")
    } else {
        println("It's an Integer with value: $value")
    }
}

fun main() {
    checkNotType(100)    // Output: It's an Integer with value: 100
    checkNotType("Kotlin") // Output: Not an Integer
}
  • value !is Int returns true if value is not an Int.
  • If value is not an Int, it prints “Not an Integer”.
  • Otherwise, it prints the integer value.

Type Checking with when Expressions

Kotlin’s when expression works well with type checks (is and !is). This is especially useful for handling multiple types in a concise manner.

Kotlin
fun identifyType(value: Any) {
    when (value) {
        is String -> println("It's a string of length ${value.length}")
        is Int -> println("It's an integer: $value")
        is Double -> println("It's a double: $value")
        else -> println("Unknown type")
    }
}

fun main() {
    identifyType("Hello")  // Output: It's a string of length 5
    identifyType(10)       // Output: It's an integer: 10
    identifyType(5.5)      // Output: It's a double: 5.5
    identifyType(true)     // Output: Unknown type
}
  • when checks the type of value using is.
  • Depending on the type, it prints an appropriate message.
  • The else block ensures unhandled types don’t cause errors.

Smart Casting and Type Safety

One of Kotlin’s biggest advantages is smart casting, which reduces the need for explicit type casting.

Kotlin
fun printLength(obj: Any) {
    if (obj is String) {
        // Smart cast: No need to explicitly cast obj to String
        println("String length: ${obj.length}")
    }
}

fun main() {
    printLength("Kotlin")  // Output: String length: 6
}

Why is this useful?

  • No need for explicit casting (obj as String).
  • Improves readability and prevents unnecessary type conversion errors.

Common Mistakes and Best Practices

1. Forgetting the Smart Cast Scope

Kotlin
fun invalidCast(obj: Any) {
    if (obj is String) {
        // Smart cast works here
        println(obj.length)
    }
    // println(obj.length) // Error: Smart cast is lost outside if block
}

Solution: Store the casted value in a variable if needed outside the block.

2. Using is with Nullable Types

Kotlin
fun checkNullable(value: Any?) {
    if (value is String) {
        println(value.length) // Safe
    }
}
  • is works with nullable types, but the variable remains nullable outside the if block.
  • Use the safe call operator (?.) to avoid NullPointerException.

3. Also, use when for Multiple Type Checks – This makes code cleaner and more readable.

Conclusion

Type checks (is and !is) in Kotlin are powerful tools for ensuring type safety and improving code clarity. The is operator checks if a variable belongs to a specific type, while !is ensures it doesn’t. These operators shine in if conditions, when expressions, and smart casting scenarios, making Kotlin a safer and more intuitive language.

capturing mutable variables in Kotlin lambdas

Capturing Mutable Variables in Kotlin Lambdas: Why Kotlin Developers Struggle

Kotlin is a powerful and expressive language, but it introduces some challenges when dealing with mutable variables inside lambdas. If you’ve ever encountered issues while capturing mutable variables in Kotlin lambdas, you’re not alone. Many developers struggle with this concept, leading to unexpected behavior, performance concerns, and even compiler errors.

In this blog post, we’ll dive deep into capturing mutable variables in Kotlin lambdas, explore why developers often face difficulties, and discuss best practices to handle them effectively.

Before we dive into the struggles, let’s first understand what it means to capture a mutable variable in a lambda.

Understanding Capturing Mutable Variables in Kotlin Lambdas

A lambda expression in Kotlin can access and “capture” variables from its surrounding scope. This is a feature known as closures. However, how a variable is captured depends on whether it is mutable or immutable.

When a lambda function is defined inside another function, it can access variables declared in the outer function. If the lambda captures a mutable variable (a var), it essentially maintains a reference to that variable rather than making a copy of its value.

Kotlin
fun main() {
    var count = 0 // Mutable variable
    val increment = { count++ } // Lambda capturing 'count'
    
    increment()
    increment()
    
    println(count) // Output: 2
}

In this case, the lambda increment captures the mutable variable count, modifying it every time it is called.

Why Kotlin Developers Struggle with Capturing Mutable Variables

In above example, the lambda captures count, allowing it to be modified inside the lambda. But there’s a catch: Kotlin captures mutable variables by reference. This means any change inside the lambda affects the original variable.

This is where developers often struggle, especially when working with concurrency or multi-threading.

Concurrency Issues with Mutable Variables

When working in multi-threaded applications, capturing mutable variables in Kotlin lambdas can lead to unpredictable behavior due to race conditions.

Kotlin
var sharedCount = 0

fun main() {
    val workers = List(1000) {
        Thread { sharedCount++ }
    }
    
    workers.forEach { it.start() }
    workers.forEach { it.join() }
    
    println(sharedCount) // Unpredictable result
}

Since sharedCount is modified by multiple threads, the final value is unpredictable. The correct way to handle this in Kotlin is by using Atomic variables:

Kotlin
import java.util.concurrent.atomic.AtomicInteger

val sharedCount = AtomicInteger(0)

fun main() {
    val workers = List(1000) {
        Thread { sharedCount.incrementAndGet() }
    }
    
    workers.forEach { it.start() }
    workers.forEach { it.join() }
    
    println(sharedCount.get()) // Always consistent
}

Note: When you run both programs, you might think they give the same result. So, what’s the difference? The catch is that in the second case, the result is always consistent, no matter what. But in the first case, it’s unpredictable—even if it looks correct sometimes. Try stress testing it, and you’ll see the difference.

Detecting This Issue with a Stress Test

To reliably expose the race condition, increase the number of threads and iterations:

Kotlin
var sharedCount = 0

fun main() {
    val workers = List(10000) {
        Thread {
            repeat(100) { sharedCount++ }
        }
    }
    
    workers.forEach { it.start() }
    workers.forEach { it.join() }
    
    println(sharedCount) // Unpredictable, usually much less than 1,000,000
}

To really see the difference, try pushing the program harder — bump up the number of threads and iterations. Run the test, and you’ll notice the final count is all over the place, much lower than expected. That’s the unpredictability we’re talking about. Hope it’s clear now..!

Conclusion

Capturing mutable variables in Kotlin lambdas can be tricky due to variable scoping, reference capturing, and concurrency issues. By understanding these challenges and following best practices, you can write safer and more predictable Kotlin code.

If you’re struggling with such issues in your Kotlin projects, try applying the techniques discussed here. 

Happy Capturing..!

Non-Local Returns

Understanding Non-Local Returns in Kotlin: A Deep Dive into Lambda Behavior

Kotlin is known for its expressive and concise syntax, but one of the lesser-understood features is non-local returns in Kotlin lambda. This concept plays a crucial role in handling control flow inside lambda expressions. In this blog, we will explore what non-local returns are, how they work, and when to use them effectively.

What Are Non-Local Returns in Kotlin?

In Kotlin, functions use the return keyword to exit and pass back values. However, when using lambdas, returning from a lambda doesn’t always behave as expected.

A non-local return is when a return statement inside a lambda exits not just the lambda but also its enclosing function. This can be useful but also tricky to handle.

Kotlin
fun findEven(numbers: List<Int>) {
    numbers.forEach { number ->
        if (number % 2 == 0) {
            println("Even number found: $number")
            return  // Non-local return
        }
    }
    println("This will not execute if an even number is found.")
}

fun main() {
    findEven(listOf(1, 3, 5, 4, 7, 9))
}

// Output 

Even number found: 4

Here, when number % 2 == 0 becomes true for 4, the return statement exits the findEven function entirely, skipping the last println() statement. This is what makes it a non-local return.

How Do Non-Local Returns Work?

Non-local returns work because Kotlin allows the return keyword inside a lambda to return from the nearest function that is marked as inline. The forEach function is inline, so the return statement jumps out of findEven() instead of just the lambda.

What if function is non-inline?

So in Kotlin, non-local returns (return from a lambda that exits the enclosing function) are allowed only inside inline functions. If a function is not marked as inline, attempting a non-local return will result in a compilation error.

Kotlin
fun nonInlineFunction(action: () -> Unit) {
    action()
}

fun main() {
    nonInlineFunction {
        println("Before return")
        return  // Compilation Error: 'return' is prohibited (or not allowed) here
    }
}

This code fails because nonInlineFunction is not marked as inline. This happens because return inside a lambda tries to exit the main function, which is not allowed unless the function is inline.

So,

  • Non-local returns work only in inline functions.
  • If the function isn’t inline, the compiler throws an error.

To return only from the lambda (not the enclosing function), we use labeled returns (return@forEach).

Using Labeled Returns to Prevent Non-Local Returns

If we want to return only from the lambda and not from the enclosing function, we use a labeled return. Let’s modify the previous example (findEven):

Kotlin
fun findEvenCorrectly(numbers: List<Int>) {
    numbers.forEach { number ->
        if (number % 2 == 0) {
            println("Even number found: $number")
            return@forEach // Only exits this lambda, not findEvenCorrectly
        }
    }
    println("This will always execute.")
}

fun main() {
    findEvenCorrectly(listOf(1, 3, 5, 4, 7, 9))
}

// OUTPUT

Even number found: 4
This will always execute.

Here, return@forEach ensures that the return only exits the lambda without affecting findEvenCorrectly().

Why Use Non-Local Returns?

Non-local returns can be useful in early exits, especially in cases like:

  • Searching for an item: Exiting a function once a condition is met.
  • Validating data: Stopping execution once invalid data is detected.
  • Short-circuiting loops: Avoiding unnecessary iterations.
Kotlin
fun containsNegative(numbers: List<Int>): Boolean {
    numbers.forEach {
        if (it < 0) return true // Exits containsNegative() immediately
    }
    return false
}

fun main() {
    println(containsNegative(listOf(1, 2, -3, 4)))  // Output: true
    println(containsNegative(listOf(1, 2, 3, 4)))   // Output: false
}

Here, return true immediately exits containsNegative() without checking the remaining numbers.

When to Avoid Non-Local Returns

Despite their benefits, non-local returns in Kotlin lambda should be used with caution. They can sometimes make code harder to read and debug.

Avoid them if:

  • You don’t need early exits. If processing all items is required, non-local returns aren’t necessary.
  • You’re using nested functions. Multiple layers of non-local returns can make it confusing where execution stops.
  • Your function isn’t marked inline. Non-local returns work only in inline functions; otherwise, they cause a compilation error.

Want more details? Check out the full guide: [Main Article URL]

Conclusion

Non-local returns in Kotlin lambda allow you to exit an enclosing function from within a lambda, but they work only with inline functions. They are useful in cases where early exits improve readability but should be used cautiously to avoid confusion.

Understanding this behavior helps you write more efficient and expressive Kotlin code. Try experimenting with inline and non-inline functions to see how Kotlin handles lambda returns in different scenarios..!

Capturing Non-Final Variables in Kotlin Lambdas

Workarounds for Capturing Non-Final Variables in Kotlin Lambdas

Kotlin lambdas are powerful, but they come with a constraint: they can only capture final (effectively immutable) variables from their enclosing scope. This can be a challenge when you need to modify a variable inside a lambda.

In this blog, we will explore why this restriction exists and the workarounds you can use to capture non-final variables in Kotlin lambdas.

What Happens When Capturing Non-Final Variables in Kotlin Lambdas?

In Kotlin, when a lambda captures a non-final variable (i.e., a var variable), the variable is essentially wrapped in an internal object. This allows the lambda to modify the value of the variable even after it has been captured. This behavior is different from languages like Java, where lambda expressions can only capture final or effectively final variables.

Let’s look at a simple example where a lambda captures a non-final variable:

Kotlin
fun outerFunction(): () -> Unit {
    var counter = 0
    return { println(counter++) }
}

// Place the following code inside main()

val lambda = outerFunction()
lambda() // prints "0"
lambda() // prints "1"

Here,

  1. outerFunction declares a variable counter initialized to 0.
  2. It then returns a lambda that prints the current value of counter and increments it.
  3. When we invoke lambda(), it prints the current value and increases it, demonstrating that counter retains its state across multiple lambda executions.

This behavior occurs because the variable counter is wrapped in an object that allows its modification even after being captured by the lambda.

You won’t believe this..! Wait a minute—just check the Java bytecode for outerFunction(), and you’ll see.

Java
@NotNull
public static final Function0 outerFunction() {
    final Ref.IntRef counter = new Ref.IntRef();
    counter.element = 0;
    // ...
}

So, what is Ref.IntRef? Is it a mutable wrapper object?

Yes, Ref.IntRef is a mutable wrapper object used by the Kotlin compiler to allow lambdas to capture and modify integer values.

Actually, Ref.IntRef is an internal class in Kotlin’s standard library (kotlin.jvm.internal package). It is used when lambdas capture a mutable var of type Int because primitive types (int) cannot be directly captured by lambdas in Java due to Java’s pass-by-value nature.

This wrapper enables mutability, meaning that changes made inside the lambda affect the original variable.

Note- Kotlin provides similar wrappers for other primitive types also.

Now, What Happens When You Try to Modify a Captured Variable from Outside?

While lambdas can modify captured variables, you cannot modify those variables from outside the lambda. If you try to do so, the Kotlin compiler will raise an error.

Kotlin
fun outerFunction(): () -> Unit {
    var counter = 0
    return { println(counter++) }
}

val lambda = outerFunction()
lambda.counter = 10 // Compilation error: "Unresolved reference: counter"

Why Does This Happen?

The reason this code fails is that counter is wrapped inside an object, and the lambda is the only one with access to that object. Attempting to modify counter from outside the lambda results in a compilation error since counter is not a property of lambda itself.

Workaround (Recommended): Use an Explicitly Mutable Object

If you need to modify a captured variable externally, one approach is to use a mutable wrapper object. For example:

Kotlin
class Counter(var value: Int)

fun outerFunction(counter: Counter): () -> Unit {
    return { println(counter.value++) }
}

fun main() {
    val counter = Counter(0)
    val lambda = outerFunction(counter)

    lambda() // Prints "0"
    lambda() // Prints "1"

    counter.value = 10 // Successfully modifies the counter
    println(counter.value) // Prints "10"
}

Here,

  • Instead of a simple var counter, we use a Counter class to hold the value.
  • The lambda captures an instance of Counter, allowing external modification.
  • Now, counter.value can be updated externally without compiler errors.

Alternative (Just for Understanding): Using a Local Variable Inside the Lambda

Another approach is to declare a new local variable inside the lambda itself. However, note that this does not allow external modification of the original captured variable:

Kotlin
fun outerFunction(): () -> Unit {
    var counter = 0
    return {
        val newCounter = 10
        println(newCounter)
    }
}

fun main() {
    val lambda = outerFunction()
    lambda() // Prints "10"
}

Here,

  • The newCounter variable exists only within the lambda and does not affect counter.
  • This is useful when you need a temporary, independent variable inside the lambda.

Note: This is just for the sake of understanding; it’s not recommended.

Want more details? Check out the full guide: [Main Article URL]

Conclusion

Capturing non-final variables in Kotlin lambdas provides flexibility, but it also requires an understanding of how Kotlin wraps these variables internally. While lambdas can modify captured variables, external modification is not allowed unless an explicit wrapper object is used. By following best practices, you can ensure safe and maintainable code when working with non-final variable captures in Kotlin.

By mastering these concepts, you’ll be better equipped to leverage Kotlin’s powerful functional programming features while writing efficient and robust code.

error: Content is protected !!