Kotlin

Kotlin is a cross-platform, statically typed, general-purpose programming language with type inference. Developed by JetBrains, it is designed to interoperate fully with Java, but provides more concise syntax and addresses many of Java's pain points, such as "null pointer exceptions." In 2019, Google announced Kotlin as its preferred language for Android app developers.

  • Interoperability: You can call Java code from Kotlin and vice-versa seamlessly within the same project.
  • Null Safety: The compiler systematically helps avoid NullPointerExceptions by distinguishing between nullable and non-nullable types.
  • Conciseness: It reduces boilerplate code significantly (e.g., Data Classes replace dozens of lines of Java getters/setters).
  • Extension Functions: Allows you to extend a class with new functionality without having to inherit from the class.
  • Coroutines: Provides a way to write asynchronous, non-blocking code that is easy to read and maintain.
  • Smart Casts: The compiler automatically tracks checks and performs type casts for you.

  • val(Value): Used for immutable variables.Once assigned, the value cannot be changed (similar to final in Java).
  • var(Variable): Used for mutable variables.The value can be reassigned later in the program.

Kotlin's type system is aimed at eliminating the "Billion Dollar Mistake" of null references.

  • Non-Nullable: By default, variables cannot hold null. var name: String = "Alex" will throw a compile error if you try to set it to null.
  • Nullable: To allow a variable to hold null, you must explicitly append a ? to the type.
  • Safe Call Operator (?.): Executes a method only if the variable is not null.

A Data Class is a concise way to create classes that are primarily used to hold data. When you declare a class as data, the compiler automatically generates standard methods like equals(), hashCode(), toString(), and copy().

Kotlin allows you to declare a primary constructor class header itself. It is the most common way to initialize a class.

Extension functions allow you to "add" methods to existing classes without modifying their source code. For example, you can add a method to the standard String class:

The Elvis Operator is used to provide a default value when an expression results in null.

Kotlin provides an intuitive way to work with ranges using the .. and step keywords, making loops very readable.

Lambdas are "function literals"—functions that are not declared but passed immediately as an expression. They are often used for high-order functions like filtering or mapping collections.

While both val and const val represent immutable values, they differ in when they are initialized and where they can be used.

Feature val const val
Initialization Determined at runtime. Determined at compile-time.
Mutability Read-only; can be assigned a function result. Truly constant; must be a primitive or String.
Placement Can be inside functions, classes, or top-level. Only at top-level or inside an object / companion object.
Performance Access involves a getter method call. Inlined by the compiler (faster access).

Example Usage:

  • const val is used for hardcoded values that never change such as (API keys or mathematical constants).
  • val is used for values that remain constant after being calculated during the application's execution.

The init Block in Kotlin
The init block is an initializer block used to run setup logic or validate parameters immediately after an instance of a class is created.It acts as a bridge for logic that cannot be placed directly within the primary constructor declaration.

  • Execution Timing: It executes immediately after the primary constructor is called and before any secondary constructors are executed.
  • Property Initialization: It runs in the order it appears in the class body, interspersed with property initializers.
  • Access: It has direct access to the parameters defined in the primary constructor.

Comparison: Primary Constructor vs. init Block

Feature Primary Constructor init Block
Purpose Declares properties and defines entry point. Executes logic/code (loops, validation, logging).
Code Execution Cannot contain code blocks. Can contain any valid Kotlin code.
Quantity Only one per class. Multiple init blocks allowed (run top-to-bottom.

Code Example:

Inheritance in Kotlin

In Kotlin, inheritance allows a class (subclass) to inherit properties and methods from another class (superclass). By design, Kotlin favors composition over inheritance, leading to a stricter default behavior than Java.

  • The open Keyword: All classes and methods in Kotlin are final by default, meaning they cannot be inherited or overridden unless explicitly marked with the open keyword.
  • The override Keyword: Subclasses must use the override modifier when redefining a method or property from the superclass.
  • Single Inheritance: A class can only inherit from one superclass (but can implement multiple interfaces).
  • Syntax: The colon (:) is used for both inheritance and interface implementation.

Why Classes are Final by Default

Kotlin follows the design principle "Design and document for inheritance or else prohibit it," as popularized by Joshua Bloch in Effective Java.

Reason Impact
Fragile Base Class Problem Prevents developers from accidentally breaking subclasses when changing the internal logic of a base class.
Encapsulation Ensures that a class’s behavior is predictable and cannot be altered externally without the author's permission.
Performance Allows the compiler to perform optimizations (like inlining) because it knows a method's implementation cannot be swapped out via a subclass.
API Stability Forces library authors to be intentional about which parts of their code are intended for extension.

Example Implementation:

The open Keyword in Kotlin

In Kotlin, the open keyword is the opposite of final. It explicitly permits a class or a member (method/property) to be subclassed or overridden. Since Kotlin classes are closed by default to ensure robustness, you must use open to enable inheritance.

  • For Classes: Applying open to a class header allows other classes to inherit from it.
  • For Members: Applying open to a property or function within an open class allows subclasses to provide a custom implementation using the override keyword.

When to Use open

You should use the open keyword specifically when you intend to design a class for extension. Common scenarios include:

Scenario Application
Abstract Base Logic When creating a base class that holds shared logic but requires specialization (e.g., a BaseActivity in Android).
Template Method Pattern When a class defines a fixed algorithm but allows certain steps (marked open) to be modified by subclasses.
Testing/Mocking When using certain mocking frameworks (like Mockito) that require classes to be non-final to create "mock" versions of them.
UI Components When building a library where users need to extend your views or components to add custom behavior.

Example:

Abstract Classes vs. Interfaces in Kotlin

In Kotlin, both Abstract Classes and Interfaces are used to define contracts for subclasses. However, they differ significantly in their structural capabilities and how they store state.

Feature Abstract Class Interface
State (Properties) Can store state (can have instance fields/backing fields). Cannot store state (no backing fields allowed).
Constructor Can have constructors with parameters. Cannot have constructors.
Inheritance A class can inherit from only one abstract class. A class can implement multiple interfaces.
Default Behavior Can have both abstract and fully implemented methods. Can have abstract methods or methods with default implementations.
Visibility Members can be protected or internal. Members are always public (though can be private in some contexts).

When to Use Which

  • Use Abstract Classes when you want to share a common base of code and state among closely related classes (e.g., a Vehicle base class that stores a fuelLevel property).
  • Use Interfaces when you want to define a common behavior (a "can-do" relationship) across unrelated classes (e.g., both a User and a Document might implement Printable).

Code Example:

Implementing Multiple Interfaces

In Kotlin, a class can implement any number of interfaces. This is the primary way to achieve polymorphism across different behavioral sets without the limitations of single inheritance.

  • Syntax: List the interfaces separated by commas after the colon (:).
  • Conflict Resolution: If two interfaces provide a default implementation for a method with the same name, the subclass must override that method to resolve the ambiguity.
  • Super Calls: To call a specific interface's implementation within the override, use the super< InterfaceName >.methodName() syntax.

Handling Method Conflicts

When multiple interfaces share a method signature, Kotlin requires explicit resolution to prevent the "Diamond Problem."

Component Usage
Override Mandatory if method signatures collide.
super A Directs the compiler to use Interface A's logic.
super B Directs the compiler to use Interface B's logic.

Code Example:

Sealed Classes in Kotlin

A Sealed Class is used to represent restricted class hierarchies. It allows you to define a set of subclasses that are known at compile-time, ensuring that no other subclasses can be created outside the file where the sealed class is defined.

  • Exhaustive Checks: When used in a when expression, the compiler verifies that all possible subclasses are covered, removing the need for an else branch.
  • State Management: Unlike Enums, each subclass of a sealed class can have multiple instances and hold unique data (state).

Sealed Class vs. Enum

Feature Enum Class Sealed Class
Instance Constraints Only one instance of each constant (Singleton). Can have multiple instances of each subclass.
Data/State All constants share the same properties/types. Each subclass can have different properties and methods.
Hierarchy Flat list of values. Can be a full hierarchy (subclasses, data classes, or objects).
Usage Best for constant values (Days of week, Colors). Best for state-machine logic (Success, Error, Loading).

Code Example:

The object Keyword in Kotlin

In Kotlin, the object keyword is a thread-safe, first-class citizen used to define a Singleton—a class that has exactly one instance. Unlike Java, where you must manually implement the Singleton pattern using private constructors and static methods, Kotlin handles the instantiation and lifecycle automatically.

  • Initialization: The object is initialized lazily the first time it is accessed.
  • Thread Safety: The initialization is thread-safe by default.
  • Functionality: Objects can inherit from classes and implement interfaces, just like regular classes.
  • Global Access: It can be accessed directly by its name without using a constructor.

Use Cases for object

Type Purpose
Singleton Object For global utility classes, database managers, or configuration holders.
Companion Object To define members that belong to the class rather than instances (replaces Java's static).
Anonymous Object Created using object : InterfaceName { ... } to implement interfaces or override classes on the fly.

Code Example:

The companion object in Kotlin

A companion object is a specialized object declared inside a class. It is used to define members (properties and methods) that belong to the class itself rather than to specific instances of that class.

In Java, these are called static members. Kotlin does not have the static keyword; instead, it uses companion objects to group these members while maintaining a strictly object-oriented structure.

  • Access: Members are called using the class name (e.g., MyClass.myFunction()).
  • Interface Support: Unlike Java statics, a companion object can implement interfaces and extend classes.
  • Quantity: Only one companion object is allowed per class.
  • Access to Private Members: The companion object can access private members of the containing class.

Companion Object vs. Java Static

Feature Java static Kotlin companion object
Type A keyword for members. A real object instance.
Interfaces Cannot implement interfaces. Can implement interfaces.
Inheritance Does not support polymorphism. Can extend other classes.
Naming N/A Can be named (default is Companion).

Code Example: Factory Pattern

A common use case for companion objects is the Factory Pattern, where you hide the constructor and provide a static-like method to create instances.

Higher-Order Functions in Kotlin

A Higher-Order Function is a function that does at least one of the following:

  • Takes one or more functions as parameters.
  • Returns a function as its result.

In Kotlin, functions are "first-class citizens," meaning they can be stored in variables and passed around just like integers or strings. This is a core pillar of functional programming in Kotlin.

Key Components

Component Description
Function Type The signature of the function, e.g., (Int, Int) -> Int.
Lambda Expression The actual block of code passed to the function, e.g., { a, b -> a + b }.
it Keyword Implicit name for a single parameter in a lambda.

Code Examples:

  1. Passing a Function as a Parameter

    This is commonly used for callbacks or defining custom behavior for a process.

  2. Returning a Function

    Useful for "function factories" where the output depends on the input configuration.

Common Built-in Higher-Order Functions

Kotlin's standard library uses these extensively for collection processing:

  • .filter { ... }: Takes a predicate function to filter a list.
  • .map { ... }: Takes a transformation function to change list items.
  • .forEach { ... }: Takes a function to execute for every element.

List vs. MutableList vs. Array

In Kotlin, these three structures are used to store collections of elements, but they differ in mutability, memory representation, and flexibility.

Feature Array List MutableList
Mutability Size is fixed; elements can be changed (mutable content). Immutable; neither size nor elements can be changed. Mutable; both size and elements can be changed.
Memory Optimized at the JVM level as primitive arrays (e.g., int[]). Objects; usually backed by java.util.ArrayList. Objects; backed by java.util.ArrayList.
Type Safety Invariant (an Array<String> is not an Array<Any>). Covariant (a List<String> is a List<Any>). Invariant (a Mutable MutableList<String> is not MutableList<Any>).
Performance Fastest for low-level indexing and primitive storage. Lightweight for read-only data. Slight overhead due to resizing logic.

Key Differences Explained

  • Array<T>: Use this when you know the number of elements in advance and need high-performance access. It is a low-level structure where the size is locked upon initialization.
  • List<T>: This is an interface for a read-only collection. You cannot add or remove items. It provides a view of the data that ensures it won't be modified, which is essential for thread safety.
  • MutableList<T>: Use this when you need to dynamically grow or shrink your collection. It includes methods like .add(), .remove(), and .clear().

Code Example:

Collection Transformations: Filter, Map, and Reduce

These are higher-order functions used to process collections in a functional style. They allow you to transform or analyze data without using manual for loops.

Function Purpose Return Type
filter Selects elements that satisfy a specific condition. A new collection of the same type (or subset).
map Transforms each element into something else. A new collection of the same size, potentially a different type.
reduce Combines all elements into a single value using an accumulator. A single value of the same type as the elements.

Detailed Breakdown

  • filter : Takes a predicate (a function returning a Boolean).If the predicate is true, the element is included in the new list.
    • Example: numbers.filter { it > 10 }
  • map : Applies a transformation function to every element. This is useful for extracting specific properties from a list of objects.
    • Example: users.map { it.name } (Turns a list of Users into a list of Strings).
  • reduce : Starts with the first element as the "accumulator" and sequentially applies an operation. Note: It throws an exception if the collection is empty.(Use fold if you need an initial starting value).
    • Example: numbers.reduce { acc, next -> acc + next }

Combined Code Example

This example demonstrates a typical data pipeline:

Sequences vs. Iterables

In Kotlin, both IterableIterable (like List or Set) and Sequence offer functional processing (map, filter, etc.), but they differ fundamentally in evaluation strategy and performance for large datasets.

  • Iterable (Eager Evaluation): Processes the entire collection at each step. If you chain three operations, Kotlin creates two intermediate lists before the final result.
  • Sequence (Lazy Evaluation): Processes elements one by one through the entire chain. No intermediate collections are created. It performs "lazy" computation, meaning nothing happens until a terminal operation (like toList() or first()) is called.

Key Comparison

Feature Iterable (List/Set) Sequence
Evaluation Eager (step-by-step). Lazy (element-by-element).
Intermediate Collections Created at every step (Memory heavy). None created (Memory efficient).
Order of Execution Completes filter for all, then map for all. Completes filter + map for element 1, then element 2...
Best For Small collections or simple transformations. Large datasets or complex processing chains.
Terminal Operation Not required (executes immediately). Required to trigger execution.

Behavioral Example

Consider a list of numbers where we want to find the first squared even number:

In this example, the Sequence is significantly faster because it stops as soon as the condition is met, whereas the Iterable finishes processing the entire list even though only the second element was needed.

Infix Functions in Kotlin

An Infix Function allows you to call a member function or an extension function using a more natural, English-like syntax by omitting the dot (.) and parentheses (). This is commonly used to create Domain Specific Languages (DSLs) or more readable mathematical operations.

Requirements for an Infix Function

To mark a function with the infix keyword, it must satisfy these three conditions:

  • It must be a member function or an extension function.
  • It must have exactly one parameter.
  • The parameter must not accept a variable number of arguments (vararg) and must not have a default value.

Precedence

Infix functions have lower precedence than arithmetic operators, type casts, and the rangeTo operator. For example:

  • 1..10 step 2 is parsed as (1..10) step 2.
  • a + b combinedWith c is parsed as (a + b) combinedWith c.

Code Example: Creating an Infix Function

In this example, we create an extension function for the String class that allows us to "pair" two strings using the word onto.

Common Built-in Infix Functions

Function Usage Result
to val map = mapOf("A" to 1) Creates a Pair object.
step for (i in 1..10 step 2) Defines the increment in a loop.
and/or if (a and b) Bitwise logical operations.

The when Expression in Kotlin

The when expression is Kotlin's more powerful, flexible version of the switch statement found in Java or C. It matches its argument against all branches sequentially until a condition is satisfied.

  • Expression vs. Statement: Unlike a switch statement, when can be used as an expression, meaning it can return a value that you can assign to a variable.
  • No break Needed: Once a branch is matched, the execution finishes for that when block. You don't need to add break keywords to prevent "fall-through."
  • Exhaustiveness: When used as an expression, the compiler forces you to cover all possible cases (often requiring an else branch), making the code safer.

Flexible Branching Logic

when is not limited to constants; it can handle ranges, types, and logic.

Logic Type Syntax Example
Multiple Values 0, 1 -> "Binary"
Ranges in 1..10 -> "Small number"
Type Checking is String -> "It's a text"
Negation !in 10..20 -> "Outside range"
Function Result s.toInt() -> "Matches integer"

Code Example

  1. As an Expression (Assigning to a Variable)
  2. Without an Argument (Replacingif-else if)

    If no argument is supplied, the branch conditions act as simple boolean expressions.

From The Same Category

Swift

Browse FAQ's

C++

Browse FAQ's

Golang

Browse FAQ's

C Programming

Browse FAQ's

Java

Browse FAQ's

Python

Browse FAQ's

Javascript

Browse FAQ's

DocsAllOver

Where knowledge is just a click away ! DocsAllOver is a one-stop-shop for all your software programming needs, from beginner tutorials to advanced documentation

Get In Touch

We'd love to hear from you! Get in touch and let's collaborate on something great

Copyright copyright © Docsallover - Your One Shop Stop For Documentation