DEV Community

Cover image for kotlin vs java- the very basics
Ashley D
Ashley D Subscriber

Posted on

kotlin vs java- the very basics

A few months after bootcamp wrapped up for me, the class attended a career fair, and a few weeks later, I learned I’d be joining a primarily backend team that works in Kotlin. Since Kotlin was a new language to me, I decided to explore Kotlin fundamentals through Codecademy 📚.

With less than 3 weeks until the start of the internship ⏳, I was initially worried I wouldn’t be able to learn it all in time 😰. But as I went through the intro course, I began to see parallels with Java ☕️—which we explored at a high level in the bootcamp.

In this post, I’ll share some beginner-friendly insights into how Kotlin compares to Java, focusing on syntactical and practical differences and similarities that stood out to me 🔍.

Humorous cartoon of cramming Kotlin, a mountain to learn

Table of Contents

✍️ Syntax

Variable Declaration
No Primitive Types
String Interpolation
Null Safety
When vs Switch

💿 Functions

Defining Function
Handling Void
Lambda Expressions

💻 Misc

Collections
Class Constructors & init


Syntax

Variable Declaration

Java Approach

In Java, variables must be explicitly typed, meaning you have to specify the type of data the variable will hold when you declare it. For example:

int age = 25;
String name = "John";
Enter fullscreen mode Exit fullscreen mode

If you want to make a variable immutable (i.e., its value cannot change after it's set), you can use the final keyword:

final int age = 25; // Immutable
Enter fullscreen mode Exit fullscreen mode

Kotlin Approach

In Kotlin, you don't have to explicitly declare the type in most cases. The compiler automatically infers the type based on the assigned value.
Also, instead of using final, Kotlin uses val for immutable variables and var for mutable variables.
For example:

val age = 25 // Immutable
var name = "John" // Mutable
Enter fullscreen mode Exit fullscreen mode

The type inference reduces redundancy, making your code more concise and readable.
However, you can still explicitly define the type if needed, especially when the type is not immediately clear from the value.

val newUser: Person = Person("John", 25)
Enter fullscreen mode Exit fullscreen mode

In this example, the type of newUser is explicitly declared as Person, making it clear what type the variable will hold.


No Primitive Types

Java Approach

In Java, there are primitive types like int, double, and boolean, which are not objects. But Java also provides wrapper classes for each primitive type (e.g., Integer, Double, Boolean) that allow you to treat them as objects:

int num = 10;
List<Integer> numbers = new ArrayList<>(); 
Enter fullscreen mode Exit fullscreen mode

Kotlin Approach

Kotlin treats everything as an object, including types that would be primitive in Java. This allows for consistent type system, making it easier to work with different kinds of variables without needing to distinguish between primitive types and objects. For example:

val num: Int = 10
Enter fullscreen mode Exit fullscreen mode

String Interpolation

Java Approach

In Java, string concatenation requires the + operator, which can get messy, especially when you need to include variables in strings:

String greeting = "Hello, " + name + "!";
Enter fullscreen mode Exit fullscreen mode

Kotlin Approach

Kotlin simplifies string manipulation by allowing string interpolation which can make the code more readable and concise.
This means you can directly embed variables inside strings using ${} for more complex expressions, or $ for simple variable interpolation:

val greeting = "Hello, $name!"
println("You have ${items.size} items in your cart.")
Enter fullscreen mode Exit fullscreen mode

Null Safety

Java Approach

In Java, if you try to call a method on a null object, you'll get a NullPointerException. This is one of the most common runtime errors in Java:

String name = null;
System.out.println(name.length()); // Throws NullPointerException
Enter fullscreen mode Exit fullscreen mode

Kotlin Approach

Kotlin introduces a safer way to handle nulls. By explicitly declaring whether a variable can be null, you reduce the risk of NullPointerExceptions. You can use the ? aka elvis operator to perform safe calls, which won't crash your program even if the variable is null:

var name: String? = null // Must handle null explicitly
println(name?.length) // Safe call, prints null instead of crashing
Enter fullscreen mode Exit fullscreen mode
  • The String? type means that the variable name can hold a String or it can be null.
    • By using the ? operator (known as the "safe call operator"), you are telling Kotlin, "Check if name is not null before accessing its length property." If name is null, it won’t throw a NullPointerException; instead, it will return null.
  • So in this case, println(name?.length) will safely print null instead of crashing the program.

when vs switch

Java Approach

In Java, the switch statement is commonly used to check a variable against multiple possible values. However, it requires multiple case labels and break statements to prevent "fall-through”; that means - after each case- there must be a break statement.

// let’s pretend it’s Monday
switch (day) {

    case "Monday":

        System.out.println("Start of the week");

        // No break statement here, so it "falls through"

    case "Friday":

        System.out.println("Weekend is near");

        break;

    default:

        System.out.println("A regular day");

}
Enter fullscreen mode Exit fullscreen mode

Kotlin Approach

Kotlin's when expression prevents fall-through by design. Once a condition matches, it automatically stops execution without needing a break. This makes Kotlin's when expression more predictable and less error-prone, as there's no chance of accidentally executing multiple cases.

when (day) {
    "Monday" -> println("Start of the week")
    "Friday" -> println("Weekend is near")
    else -> println("A regular day")
}
Enter fullscreen mode Exit fullscreen mode

Functions

Defining Function

In Java, you need to define functions with a return type, parameters, and an explicit return statement if the function has a return value:

public int addNumbers(int a, int b) {
    return a + b;
}
Enter fullscreen mode Exit fullscreen mode

Kotlin Approach

In Kotlin, defining functions is more concise. You can specify the types of the parameters and the return type explicitly, just like Java, but Kotlin allows you to simplify the function body.

fun multiplyNumbers(a: Int, b: Int): Int {
    val result = a * b
    if (result > 100) {
        println("Result is large")
    }
    return result
}
Enter fullscreen mode Exit fullscreen mode
  • fun is used to declare a function in Kotlin, followed by the function name (addNumbers).
  • You define the types of the parameters (a: Int, b: Int) and the return type (: Int) just like in Java. This keeps the function strongly typed, ensuring type safety.

If the function body contains only one expression aka single expression, you can omit the return keyword and simply use the expression after the = sign, making the function shorter and easier to read.

fun addNumbers(a: Int, b: Int): Int = a + b
Enter fullscreen mode Exit fullscreen mode

Handling Void

Java Approach

In Java, if a function doesn't return anything, it is defined as void:

public void printMessage(String message) {
    System.out.println(message);
}
Enter fullscreen mode Exit fullscreen mode

Kotlin Approach

In Kotlin, functions that don't return a value return Unit (which is similar to Java's void). You don't need to explicitly declare it in the function signature:

fun printMessage(message: String) {
    println(message)
}
Enter fullscreen mode Exit fullscreen mode

Lambda Expressions

Java Approach

Java uses streams and lambda expressions to handle functions concisely. However, the syntax can still feel bulky:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> evenNumbers = numbers.stream()
    .filter(n -> n % 2 == 0)
    .collect(Collectors.toList());
Enter fullscreen mode Exit fullscreen mode

Kotlin Approach

Kotlin simplifies lambda expressions by making them shorter and easier to read. The curly braces {} define the lambda block, and it is an implicit name used for the single parameter passed to the lambda:

val numbers = listOf(1, 2, 3, 4, 5)
val evenNumbers = numbers.filter { it % 2 == 0 }
println(evenNumbers) // Output: [2, 4]
Enter fullscreen mode Exit fullscreen mode

In this example, filter { it % 2 == 0 } is a lambda expression that filters out the even numbers from the list. The it keyword refers to each element number that we're iterating over.


Misc

Collections

Collections are used to store multiple items in one variable. A list is a common type of collection that holds an ordered set of items, like a shopping list or a list of names.

Java Approach

In Java, you typically use ArrayList to work with collections of items. You need to manually specify whether the list is mutable or immutable, and you also need the new keyword to create the list:

ArrayList<String> fruits = new ArrayList<>();
fruits.add("Apple");
fruits.add("Banana");
Enter fullscreen mode Exit fullscreen mode

Kotlin Approach

In Kotlin, you can create immutable and mutable lists more easily. The listOf creates an immutable list by default, while mutableListOf creates a mutable list.
Also, you don't need the new keyword, making list initialization simpler. Kotlin automatically initializes the list with the elements you provide with listOf:

val fruits = listOf("Apple", "Banana") // Immutable
val mutableFruits = mutableListOf("Apple")
mutableFruits.add("Banana")
Enter fullscreen mode Exit fullscreen mode

Class Constructors & init

A class is like a blueprint for creating objects. It defines what properties (variables) and actions (methods) the objects created from it will have. The constructor is a special function that runs when the class is used to create an object, and it initializes the object's properties.

Java Approach

In Java, creating a class with properties involves more boilerplate code. Here, the Person class has two properties: name and age, which are set through a constructor. The constructor is a special method that takes the name and age values when a new Person object is created:

public class Person {
    private String name; // Property for name
    private int age; // Property for age

    // Constructor that sets the name and calculates age when the object is created
    public Person(String name, int birthYear) {
        this.name = name; // Initializes the name property
        int currentYear = 2024;
        this.age = currentYear - birthYear; // Calculates age based on birth year
        System.out.println("Age calculated: " + age);
    }

    // Main method to create a new Person object
    public static void main(String[] args) {
        Person person = new Person("John", 1990);  // Constructor is called to initialize the object
        // The age is automatically calculated in the constructor based on birthYear
    }
}
Enter fullscreen mode Exit fullscreen mode

In this Java example:

  • The Person class has a constructor that takes name and birthYear as parameters.
  • The age is calculated within the constructor, similar to the init block in Kotlin, and is printed as part of the initialization process.
  • When a new Person object is created, the constructor automatically runs to initialize the object's properties and calculate the age.

Kotlin Approach

Kotlin simplifies class constructors with its primary constructor --- allowing the parameters to be directly included in the class definition. This reduces the need for a separate constructor method and makes the code more concise:

class Person(val name: String, val age: Int)
Enter fullscreen mode Exit fullscreen mode

In this Kotlin example, Person is a class with two properties (name and age) declared directly in the class. These properties are automatically initialized when an object of Person is created.

Kotlin also offers a special feature called the init block. The init block runs automatically when an object is instantiated, and it allows for additional setup or property initialization. You don't need to call it explicitly---it just runs when the object is created.

For example, the Person class in Kotlin can also use an init block for initialization:

class Person(val name: String, val birthYear: Int) {
    var age: Int = 0

    // The init block is used to initialize properties or perform setup tasks
    init {
        val currentYear = 2024
        age = currentYear - birthYear
        println("Age calculated: $age")
    }
}

fun main() {
    val person = Person("John", 1990)  // Init block runs automatically
}
Enter fullscreen mode Exit fullscreen mode

In this Kotlin example:

  • The Person class has a primary constructor with name and birthYear parameters.
  • The init block calculates the age based on the birthYear and prints the result.
  • When the object is created, the init block automatically runs to handle the initialization.

Top comments (0)