Short Excerpts

Short Insights on Previously Covered Random Topics

Casting in Kotlin

Safe vs. Unsafe Casting in Kotlin: When to Use as? Instead of as

Type casting is a common operation in Kotlin, allowing developers to convert one type into another. Kotlin provides two main ways to perform type casts: as (unsafe casting) and as? (safe casting). Understanding the differences between these two is crucial for writing robust and error-free Kotlin code.

Understanding Type Casting in Kotlin

Before diving into safe vs. unsafe casting, let’s briefly review how type casting works in Kotlin.

Kotlin uses type casting to convert an object from one type to another. The two primary casting operators are:

  • as (Unsafe Cast): Forces a cast and throws a ClassCastException if the cast fails.
  • as? (Safe Cast): Returns null if the cast is not possible, preventing runtime exceptions.

Let’s explore both in detail.

Unsafe Casting Using as

Unsafe casting with as is straightforward but risky. If the object cannot be cast to the desired type, a ClassCastException occurs.

Kotlin
fun main() {
    val obj: Any = "Hello, Kotlin!"
    val str: String = obj as String // Successful cast
    println(str)
}

This works fine because obj is indeed a String. However, let’s see what happens when the type does not match:

Kotlin
fun main() {
    val obj: Any = 42
    val str: String = obj as String // Throws ClassCastException
    println(str)
}

Output:

Kotlin
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String

Here, obj is an Int, and forcing it into a String type results in a crash. This is where safe casting (as?) is useful.

Safe Casting Using as?

The safe cast operator as? prevents runtime exceptions by returning null if the cast is not possible.

Kotlin
fun main() {
    val obj: Any = 42
    val str: String? = obj as? String // Returns null instead of throwing an exception
    println(str) // Output: null
}

Using as?, we avoid the crash because instead of forcing an invalid cast, it simply assigns null to str.

When to Use as? Instead of as

When Type is Uncertain

  • If you’re dealing with dynamic or uncertain types, always prefer as? to avoid crashes.
Kotlin
fun safeCastExample(value: Any) {
    val number: Int? = value as? Int
    println(number ?: "Not an Int")
}

fun main() {
    safeCastExample(100)   // Output: 100
    safeCastExample("Kotlin") // Output: Not an Int
}

When Handling Nullable Types

  • Since as? returns null on failure, it works well with nullable types.
Kotlin
fun main() {
    val obj: Any = "Kotlin"
    val str: String? = obj as? String // No crash, just null if not castable
    println(str ?: "Cast failed")
}

To Avoid ClassCastException

  • Using as? ensures the program does not terminate due to a casting error.

When to Use as

Although unsafe, as is still useful when you’re certain of the type.

Kotlin
fun main() {
    val text: Any = "Safe Casting"
    val str: String = text as String // Works because the type matches
    println(str)
}

Using as is fine in cases where you control the input type and are confident about the cast.

Conclusion

Type casts in Kotlin play an essential role in handling object types. However, using as carelessly can lead to runtime crashes. To ensure safer code, always prefer as? when the type is uncertain. This approach avoids ClassCastException and makes your Kotlin applications more robust and error-free.

Abstract Classes in Kotlin

When to Use Abstract Classes in Kotlin (and When Not To)

Kotlin makes object-oriented programming easier with features like abstract classes and interfaces. But when should you use abstract classes in Kotlin, and when should you avoid them? Let’s break it down in simple terms.

What Are Abstract Classes in Kotlin?

An abstract class in Kotlin is a class that cannot be instantiated directly. It acts as a blueprint for other classes, providing a structure but leaving implementation details to subclasses. Means, It is designed to serve as a base for other classes. Abstract classes may contain both abstract (unimplemented) methods and concrete (implemented) methods.

Key Features of Abstract Classes:

  • Cannot be instantiated directly
  • Can have abstract methods (methods without implementation)
  • Can have implemented methods
  • Can hold state with properties
  • Supports constructors
Kotlin
abstract class Animal(val name: String) {
    abstract fun makeSound()
}

class Dog(name: String) : Animal(name) {
    override fun makeSound() {
        println("Woof! Woof!")
    }
}
fun main() {
    val myDog = Dog("Buddy")
    myDog.makeSound() // Outputs: Woof! Woof!
}

Here,

  • Animal is an abstract class that defines an abstract method makeSound().
  • Dog is a concrete subclass that implements makeSound().
  • We cannot create an instance of Animal directly, only its subclass (Dog).

When to Use Abstract Classes in Kotlin

1. When You Need to Share Common State

If multiple subclasses share common properties or behavior, an abstract class helps avoid code duplication.

Kotlin
abstract class Vehicle(val speed: Int) {
    fun showSpeed() {
        println("Speed: $speed km/h")
    }
}

class Car(speed: Int) : Vehicle(speed)
class Bike(speed: Int) : Vehicle(speed)

Both Car and Bike inherit the speed property and showSpeed() method from Vehicle.

2. When You Want to Provide Partial Implementation

Sometimes, an abstract class provides some default behavior while requiring subclasses to define specific methods.

Kotlin
abstract class Appliance {
    fun plugIn() {
        println("Appliance plugged in")
    }
    abstract fun operate()
}

class WashingMachine : Appliance() {
    override fun operate() {
        println("Washing clothes")
    }
}

Here, Appliance has a plugIn() method that is common to all appliances, but operate() must be defined by each specific appliance.

3. When You Need a Base Class with Constructors

Unlike interfaces, abstract classes can have constructors to initialize properties.

Kotlin
abstract class Employee(val name: String, val id: Int) {
    abstract fun work()
}

class Developer(name: String, id: Int) : Employee(name, id) {
    override fun work() {
        println("Writing code")
    }
}

Here, Employee initializes name and id, saving boilerplate code in subclasses.

When NOT to Use Abstract Classes in Kotlin

1. When You Only Need Functionality, Not State

If you only need to define behavior (methods) without storing data, use interfaces instead.

Kotlin
interface Flyable {
    fun fly()
}

class Bird : Flyable {
    override fun fly() {
        println("Bird is flying")
    }
}

Interfaces allow multiple inheritance, whereas abstract classes do not.

2. When You Need Multiple Inheritance

Kotlin does not support multiple class inheritance, but a class can implement multiple interfaces.

Kotlin
interface Drivable {
    fun drive()
}

interface Floatable {
    fun float()
}
class AmphibiousCar : Drivable, Floatable {
    override fun drive() {
        println("Driving on road")
    }
    override fun float() {
        println("Floating on water")
    }
}

If we used an abstract class instead of an interface, we couldn’t achieve this flexibility.

3. When You Want Simplicity

Abstract classes add structure, but sometimes, simple data classes or regular classes work just fine.

Kotlin
data class Product(val name: String, val price: Double)

If all you need is a simple data container, an abstract class is unnecessary.

Conclusion

Use abstract classes in Kotlin when you need a base class that shares state or provides partial implementation. If you only need to define behavior, interfaces are a better choice. 

When designing your Kotlin applications, ask yourself:

  • Do I need to share logic? → Use an abstract class.
  • Do I just need a contract without implementation? → Use an interface.
  • Do I need multiple inheritance? → Use interfaces.

Choosing the right approach makes your code cleaner, more maintainable, and flexible.

Generic Type Parameters in Kotlin

A Deep Dive into Generic Type Parameters in Kotlin

Generics are a fundamental concept in Kotlin that helps make code more flexible, reusable, and type-safe. If you’ve ever wondered how Kotlin allows functions and classes to operate on different data types while maintaining type safety, you’re in the right place. In this article, we’ll explore Generic Type Parameters in Kotlin in a simple and approachable way.

What Are Generic Type Parameters?

Generic type parameters allow you to write code that can work with different types while enforcing compile-time type safety. Instead of specifying a fixed type, you define a placeholder (like T), which can represent any type the user provides.

For example, without generics, you’d need multiple implementations of the same function for different types:

Kotlin
fun printInt(value: Int) {
    println(value)
}

fun printString(value: String) {
    println(value)
}

With generics, you can write a single function:

Kotlin
fun <T> printValue(value: T) {
    println(value)
}

Now, printValue can accept any type while still ensuring type safety!

Declaring Generic Classes

Generics shine when defining reusable classes. Let’s take a simple example of a generic class:

Kotlin
class Box<T>(private val item: T) {
    fun getItem(): T {
        return item
    }
}

Here, T is a type parameter that can be replaced with any type at the time of object creation:

Kotlin
val intBox = Box(10)
val stringBox = Box("Hello, Kotlin!")

println(intBox.getItem()) // Output: 10
println(stringBox.getItem()) // Output: Hello, Kotlin!

This approach makes our Box class more versatile and eliminates the need for multiple implementations.

Bounded Type Parameters

Sometimes, you may want to restrict the type that can be used as a generic parameter. Kotlin allows you to specify upper bounds using :, ensuring that only subtypes of a specified class/interface can be used.

For example, let’s create a function that works only with numbers:

Kotlin
fun <T : Number> doubleValue(value: T): Double {
    return value.toDouble() * 2
}

Now, calling doubleValue with an Int, Double, or Float works, but passing a String results in a compilation error:

Kotlin
println(doubleValue(5)) // Output: 10.0
println(doubleValue(3.5)) // Output: 7.0
// println(doubleValue("Hello")) // Compilation Error!

This ensures that our function is used correctly while retaining the benefits of generics.

Variance in Kotlin Generics

Variance determines how generics behave with subtype relationships. Kotlin provides two keywords: out and in to handle variance properly.

Covariance (out)

When you declare out T, it means the type parameter is only produced (returned) and not consumed (accepted as a function parameter). This is useful for read-only data structures like List<T>.

Kotlin
interface Producer<out T> {
    fun produce(): T
}

Since T is only returned, we can safely use out to allow subtype assignment:

Kotlin
val stringProducer: Producer<String> = object : Producer<String> {
    override fun produce() = "Hello"
}

val anyProducer: Producer<Any> = stringProducer // Allowed because of 'out'

println(anyProducer.produce())  // O/P - Hello

Contravariance (in)

Conversely, in T means the type parameter is only consumed (accepted as a function parameter) and never produced. This is useful for function parameters.

Kotlin
interface Consumer<in T> {
    fun consume(item: T)
}

Since T is only an input, we can safely use in to allow supertype assignment:

Kotlin
val anyConsumer: Consumer<Any> = object : Consumer<Any> {
    override fun consume(item: Any) {
        println("Consumed: $item")
    }
}

val stringConsumer: Consumer<String> = anyConsumer // Allowed because of 'in'
 
println(stringConsumer.consume("Hello"))   // O/P - Consumed: Hello

Understanding variance prevents type mismatches and allows for better design patterns when working with generics.

Reified Type Parameters in Inline Functions

Kotlin has a limitation where type parameters are erased at runtime (type erasure). However, using reified type parameters in inline functions allows you to access the actual type at runtime.

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

println(isTypeMatch<String>("Kotlin")) // Output: true
println(isTypeMatch<Int>("Kotlin")) // Output: false

This is especially useful when working with reflection, type-checking, or factory methods.

Conclusion

Understanding Generic Type Parameters in Kotlin is key to writing flexible and type-safe code. By using generics, you can create reusable functions and classes while ensuring compile-time safety. Whether it’s defining generic classes, enforcing type constraints, or handling variance, generics make Kotlin a powerful and expressive language.

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

Java Exception Hierarchy

Java Exception Hierarchy Explained: A Complete Guide

Java is a powerful, object-oriented programming language that provides a structured way to handle errors using exceptions. Understanding the Java Exception Hierarchy is crucial for writing robust, error-free code. In this guide, we’ll break down Java’s exception system, explore its hierarchy, and show you how to use it effectively.

Java Exception Hierarchy

In Java’s Exception Hierarchy, the Throwable class serves as the root. This class defines two main child classes:

Exception: Exceptions primarily arise from issues within our program and are typically recoverable.

Example:

Java
try {
    // Read data from the remote file located in London
} catch (FileNotFoundException e) {
    // Use a local file and continue the rest of the program normally
}

Error: Errors, on the other hand, are non-recoverable. For instance, if an OutOfMemoryError occurs, programmers are generally powerless to address it, leading to the abnormal termination of the program. It becomes the responsibility of system administrators or server administrators to tackle issues like increasing heap memory.

Java
Throwable
├── Exception
│   ├── RuntimeException
│   │   ├── ArithmeticException
│   │   ├── NullPointerException
│   │   ├── ClassCastException
│   │   ├── IndexOutOfBoundsException
│   │   │   ├── ArrayIndexOutOfBoundsException
│   │   │   └── StringIndexOutOfBoundsException
│   │   └── IllegalArgumentException
│   │       └── NumberFormatException
│   ├── IOException
│   │   ├── EOFException
│   │   ├── FileNotFoundException
│   │   └── InterruptedIOException
│   └── ServletException
└── Error
    ├── VirtualMachineError
    │   ├── StackOverflowError
    │   └── OutOfMemoryError
    ├── AssertionError
    └── ExceptionInInitializerError

Let’s explore each of these in detail.

Exception: Recoverable Issues

Exceptions are events that disrupt the normal flow of a program but are recoverable. These are further categorized into checked and unchecked exceptions.

Checked Exceptions

Checked exceptions must be handled using a try-catch block or declared in the method signature using throws. The compiler ensures they are properly managed.

Common Checked Exceptions

IOException — Related to input/output operations.

  • EOFException: Thrown when an unexpected end of a file or stream is reached.
  • FileNotFoundException: Occurs when a specified file is missing.
  • InterruptedIOException: Thrown when an I/O operation is interrupted.

ServletException — Occurs when an error happens in a Java Servlet.

Unchecked Exceptions (Runtime Exceptions)

Unchecked exceptions are subclasses of RuntimeException and are not checked at compile time. They occur due to programming logic errors and can usually be avoided with proper coding practices.

Common Unchecked Exceptions

ArithmeticException — Thrown when illegal arithmetic operations occur (e.g., division by zero).

NullPointerException — Occurs when trying to access an object reference that is null.

ClassCastException — Happens when an object is cast to an incompatible type.

IndexOutOfBoundsException — Thrown when trying to access an index beyond valid bounds.

  • ArrayIndexOutOfBoundsException: Raised when an array index is out of range.
  • StringIndexOutOfBoundsException: Raised when a string index is invalid.

IllegalArgumentException — Thrown when an invalid argument is passed to a method.

  • NumberFormatException: A specific subclass that occurs when attempting to convert a non-numeric string into a number.

Error: Unrecoverable Issues

Errors represent serious problems that occur at the system level and are usually beyond the control of the application. They typically indicate problems related to the Java Virtual Machine (JVM) or the system itself.

Common Errors in Java

VirtualMachineError — Errors occurring due to resource exhaustion.

  • StackOverflowError: Happens when the call stack overflows due to deep or infinite recursion.
  • OutOfMemoryError: Raised when the JVM runs out of memory and cannot allocate more objects.

AssertionError— Thrown when an assertion fails in Java (assert statement used for debugging).

ExceptionInInitializerError — Occurs when an exception happens inside a static initializer block or a static variable initialization.

Unlike exceptions, errors are not meant to be caught or handled in most cases. Instead, they indicate fundamental issues that require fixing at a deeper level (e.g., optimizing memory usage).

Key Differences Between Exceptions and Errors

Exception Vs. Error

Conclusion

Understanding the Java Exception Hierarchy is key to writing reliable applications. Java categorizes exceptions into checked and unchecked types, each serving a distinct purpose. By handling exceptions effectively, you can prevent crashes, improve debugging, and ensure your application runs smoothly.

Happy Exception Handling..!

error: Content is protected !!