Amol Pawar

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.

finalize() Method

Java finalize() Method Explained: Is It Still Relevant in 2025?

Java developers have used the finalize() method for years to handle cleanup operations before an object is garbage collected. But in 2025, does the finalize() method in Java still hold any relevance? With advancements in garbage collection and alternative resource management techniques, many developers question its necessity. In this blog post, we’ll explore the finalize()...

Membership Required

You must be a member to access this content.

View Membership Levels

Already a member? Log in here
throw and throws

How to Use throw and throws Effectively in Java

When working with Java, handling exceptions properly is crucial to writing robust and maintainable applications. Two essential keywords in Java’s exception-handling mechanism are throw and throws. While they may look similar, they serve different purposes. In this guide, we will explore how to use throw and throws effectively in Java, ensuring clarity and proper exception handling.

Understanding throw and throws in Java

Both throw and throws relate to Java’s exception-handling framework, but they are used differently:

  • throw: Used within a method to explicitly throw an exception.
  • throws: Declares exceptions that a method might throw.

Now, let’s explore both in detail with examples.

Understanding throw in Java

The throw keyword in Java is used to manually create and throw an exception object. This allows the programmer to explicitly indicate exceptional conditions in the program flow.

Java
throw new ArithmeticException("/ by zero");

In this example, an ArithmeticException object is created explicitly with the message “/ by zero” and thrown using the throw keyword.

The main objective of the throw keyword is to hand over the created exception object to the JVM manually.

The following two programs yield exactly the same result:

Without throw keyword:

Java
class Test {
    public static void main(String[] args) {
        System.out.println(10/0);
    }
}


//output

Exception in thread "main" java.lang.ArithmeticException: / by zero
    at Test.main(Test.java:3)

In this case, the main method is responsible for causing the ArithmeticException and implicitly hands over the exception object to the JVM.

With throw keyword:

Java
class Test {
    public static void main(String[] args) {
        throw new ArithmeticException("/ by zero");
    }
}


//o/p

Exception in thread "main" java.lang.ArithmeticException: / by zero
    at Test.main(Test.java:3)

In this case, the main method explicitly creates an ArithmeticException object using throw keyword and hands it over to the JVM.

Here is one more example.

Java
public class ThrowExample {
    static void checkAge(int age) {
        if (age < 18) {
            throw new IllegalArgumentException("Age must be 18 or above");
        } else {
            System.out.println("Access granted.");
        }
    }
    
    public static void main(String[] args) {
        checkAge(15);  // This will throw an exception
    }
}

Here,

  • The method checkAge(int age) checks whether the given age is 18 or more.
  • If the condition is not met, throw is used to raise an IllegalArgumentException.
  • This terminates program execution unless the exception is caught and handled. The throw statement stops execution of the current block and transfers control to the nearest exception handler.

Understanding throws in Java

In Java, if there is a possibility of a checked exception being thrown within a method, the method must either handle the exception using a try-catch block or declare that it throws the exception using the throws keyword in its method signature.

Java
import java.io.*;

class Test {
    public static void main(String[] args) {
        PrintWriter pw = new PrintWriter("abc.txt");
        pw.println("Hello");
    }
}

//Compilation Error: unreported exception java.io.FileNotFoundException; must be caught or declared to be thrown

In this example, the PrintWriter constructor can throw a FileNotFoundException, a checked exception. Since it’s not handled within the main method, it results in a compilation error.

To handle this error, you can either use a try-catch block to handle the exception:

Java
import java.io.*;

class Test {
    public static void main(String[] args) {
        try {
            PrintWriter pw = new PrintWriter("abc.txt");
            pw.println("Hello");
        } catch (FileNotFoundException e) {
            // Handle the exception
            e.printStackTrace();
        }
    }
}

Or you can declare that the method throws the exception using the throws keyword:

Java
import java.io.*;

class Test {
    public static void main(String[] args) throws FileNotFoundException {
        PrintWriter pw = new PrintWriter("abc.txt");
        pw.println("Hello");
    }
}

Using the throws keyword delegates the responsibility of handling the exception to the caller of the method.

Here is another example.

Java
import java.io.IOException;

public class ThrowsExample {
    static void readFile() throws IOException {
        throw new IOException("File not found");
    }
    
    public static void main(String[] args) {
        try {
            readFile();
        } catch (IOException e) {
            System.out.println("Exception handled: " + e.getMessage());
        }
    }
}

Here,

  • The readFile method declares throws IOException, meaning it might throw an IOException.
  • Instead of handling the exception inside readFile, it delegates handling to the caller (main method).
  • The main method catches the exception using a try-catch block, preventing the program from crashing.

Note: It’s recommended to handle exceptions using try-catch blocks where possible, as using throws may propagate exceptions up the call stack without handling them properly.

Key Differences Between throw and throws

Featurethrowthrows
PurposeUsed to explicitly throw an exceptionDeclares exceptions a method can throw
LocationInside the method bodyIn the method signature
Number of ExceptionsCan only throw one exception at a timeCan declare multiple exceptions separated by commas
PropagationStops execution immediately unless handledInforms the caller to handle the exception

Best Practices for Using throw and throws

1. Use throw for Specific Error Conditions

When you detect a problem in your logic, use throw to raise an appropriate exception. Always provide meaningful error messages to help debugging.

2. Use throws for Method-Level Exception Handling

If a method performs an operation that may result in an exception (e.g., file handling, database access), use throws to declare it and let the caller handle it.

3. Catch and Handle Exceptions Properly

Declaring exceptions using throws does not replace exception handling. Always use try-catch blocks where necessary.

4. Avoid Overusing throws

Overusing throws can make a method difficult to use, as the caller must handle multiple exceptions. Only declare exceptions when handling them inside the method is impractical or when the caller needs to decide how to respond.

Conclusion

Understanding how to use throw and throws effectively in Java is key to writing robust applications. throw is used to generate exceptions manually, while throws is used to declare potential exceptions in a method. By following best practices, you can ensure better error handling and maintainable code.

Try-Catch-Finally in Java

Try-Catch-Finally in Java: How Multiple Catch Blocks Fit into Java’s Exception Handling Flow

Try-Catch-Finally in Java is a crucial part of Java programming. It helps prevent programs from crashing due to unexpected errors. Java provides a structured way to handle exceptions using try, catch, and finally blocks. In this post, we’ll explore how multiple catch blocks work in Java’s exception handling flow and how they improve code reliability....

Membership Required

You must be a member to access this content.

View Membership Levels

Already a member? Log in here
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...

Membership Required

You must be a member to access this content.

View Membership Levels

Already a member? Log in here
Java Exceptions

Java Exceptions Explained: A Beginner’s Guide to Handling Errors

Errors in Java can be frustrating, but understanding exceptions can make debugging easier. This guide will help you grasp Java exceptions, how they work, and how to handle them effectively.

What Are Java Exceptions?

In Java, an exception is an event that disrupts the normal flow of a program. It occurs when something unexpected happens, like dividing by zero or accessing an invalid array index. Java provides a robust exception-handling mechanism to deal with such situations.

Types of Java Exceptions

Java exceptions are categorized into three main types:

1. Checked Exceptions

These are exceptions that must be handled at compile-time. The compiler checks whether you have written code to handle them; otherwise, it throws an error. Examples include IOException, SQLException, and FileNotFoundException.

Kotlin
import java.io.*;

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

2. Unchecked Exceptions (Runtime Exceptions)

These exceptions occur during execution and are not checked at compile-time. They usually indicate programming mistakes such as dividing by zero or accessing an out-of-bounds array index. Common examples include NullPointerException, ArithmeticException, and ArrayIndexOutOfBoundsException.

Kotlin
public class UncheckedExceptionExample {
    public static void main(String[] args) {
        int a = 10, b = 0;
        try {
            int result = a / b; // Throws ArithmeticException
        } catch (ArithmeticException e) {
            System.out.println("Cannot divide by zero: " + e.getMessage());
        }
    }
}

3. Errors

Errors are serious problems that a program cannot handle. They usually stem from system-level failures, such as StackOverflowError or OutOfMemoryError. These should not be caught using exception handling; instead, you should focus on fixing the underlying issue.

How to Handle Java Exceptions

Java provides several ways to handle exceptions, ensuring programs continue running smoothly.

1. Try-Catch Block

The try block contains code that may throw an exception, while the catch block handles it.

Kotlin
public class TryCatchExample {
    public static void main(String[] args) {
        try {
            int[] numbers = {1, 2, 3};
            System.out.println(numbers[5]); // Throws ArrayIndexOutOfBoundsException
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("Index out of bounds: " + e.getMessage());
        }
    }
}

2. Finally Block

The finally block contains code that runs regardless of whether an exception occurs.

Kotlin
public class FinallyExample {
    public static void main(String[] args) {
        try {
            int num = Integer.parseInt("abc"); // Throws NumberFormatException
        } catch (NumberFormatException e) {
            System.out.println("Invalid number format.");
        } finally {
            System.out.println("Execution completed.");
        }
    }
}

3. Throwing Exceptions

You can manually throw exceptions using the throw keyword.

Kotlin
public class ThrowExample {
    static void checkAge(int age) {
        if (age < 18) {
            throw new IllegalArgumentException("Age must be 18 or above.");
        }
        System.out.println("Access granted.");
    }

    public static void main(String[] args) {
        checkAge(16);
    }
}

4. Using Throws Keyword

The throws keyword is used in method signatures to indicate potential exceptions.

Kotlin
import java.io.*;

public class ThrowsExample {
    static void readFile() throws IOException {
        FileReader file = new FileReader("data.txt");
    }
    public static void main(String[] args) {
        try {
            readFile();
        } catch (IOException e) {
            System.out.println("File error: " + e.getMessage());
        }
    }
}

Best Practices for Handling Java Exceptions

  1. Catch Specific Exceptions: Avoid catching generic Exception unless necessary.
  2. Log Exceptions Properly: Use logging frameworks like Log4j instead of System.out.println.
  3. Don’t Suppress Exceptions: Handle them appropriately instead of leaving catch blocks empty.
  4. Use Custom Exceptions When Needed: Create user-defined exceptions for better clarity.
  5. Keep Your Code Readable: Avoid excessive nesting in try-catch blocks.

For more details, visit: Exception Handling in Java

Conclusion

Java exceptions are essential for handling errors in a structured way. By understanding the different types of exceptions and how to manage them, you can write robust and error-free Java programs. Follow best practices to ensure clean and maintainable code.

Now that you have a solid grasp of Java exceptions, start practicing by handling different error scenarios in your projects!

Happy Exception Handling..!

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

Membership Required

You must be a member to access this content.

View Membership Levels

Already a member? Log in here
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...

Membership Required

You must be a member to access this content.

View Membership Levels

Already a member? Log in here
error: Content is protected !!