Apr 28, 2023, Mobile

Kotlin vs Java – similarities & differences, p. 2

Marta Tomaszewska Android Developer
image
In the previous part of the article I discussed key features, pros, cons, and usage of both Java and Kotlin. This time I will focus on their main similarities and most of all - differences, showing some examples of code and thoroughly explaining the topic.
So, let’s get started!

Kotlin vs Java - similarities

  • The most visible similarity is that both compile to bytecode, which is executed by the JVM.

  • Both Java and Kotlin support object-oriented programming.

  • Kotlin and Java have similar programming syntax, although they are not exactly the same. Nevertheless, a Java programmer can understand Kotlin code without major difficulties

Kotlin vs Java differences

Extension Functions

Kotlin

App Creator can easily extend the existing class with all new functionality without necessarily having to inherit from a class. Developers are able to create extend functionalities by prefixing the name of a class to the name of a specific new function:

fun String.addExclamation(): String {
    return this + "!"
}

To use the Kotlin extension function can be called using the member access syntax:

val greeting = "Hello, world"
val excitedGreeting = greeting.addExclamation() // "Hello, world!"

Java

The extension functions are not available to extend the functionality of the present class. Developers need to create a new class and then inherit the parent class.  In other words, the extension function is not available.

Java Static Utility Method:

public class StringUtils {
    public static String addExclamation(String str) {
        return str + "!";
    }
}

To use the Java static utility method, you must call it using the class name:

String greeting = "Hello, world";
String excitedGreeting = StringUtils.addExclamation(greeting); //"Hello, world!"

Checked Exceptions

Kotlin 

Kotlin doesn’t have any checked exceptions, so developers don’t need to catch or declare them.

If you call a Java method that throws a checked exception, the Kotlin compiler will treat it as an unchecked exception.

// Java method that throws a checked exception
@Throws(IOException::class)
fun readFile() {
    // code that may throw IOException
}
// calling the Java method from Kotlin
fun main() {
    try {
        readFile()
    } catch (e: IOException) {
        // handle exception here
    }
}

Java

Developers have checked exceptions support. They must catch and declare them. On the one hand, this can be frustrating and time-consuming, but on the other, it ensures robust code and errors handling. So, checked exception support has its pros and cons. Ultimately, it depends on what each developer prioritizes the most.

If you call a method that throws a checked exception, you must either handle the exception with a try-catch block or declare that the method throws the exception with the throws keyword.

// method that throws a checked exception
public static void readFile() throws IOException {
    // code that may throw IOException
}
// calling the method from Java
public static void main(String[] args) {
    try {
        readFile();
    } catch (IOException e) {
        // handle exception here
    }
}

Data Classes

Kotlin 

To have a class that saves data, developers can declare a class with the keyword ‘data’ in the class definition. Afterward, the compiler will finish the job such as creating constructors, getter, and setter methods for numerous fields.

data class Person(val name: String, val age: Int)

Java

Record class is a feature that allows developers to declare classes in a more concise and expressive way. It combines the features of a class and a data class, making it easier to define classes that are primarily used to store data.

public record Person(String name, int age) {}

Functional Programming

Kotlin 

Kotlin is both the procedural and functional programming language, and consists of a number of methods for developers including operator overloading, lazy evaluation, lambda, higher-order functions, etc.

val numbers = listOf(1, 2, 3, 4, 5)
// Filter even numbers and map to their squares
val result = numbers.filter { it % 2 == 0 }.map { it * it }
println(result) // [4, 16]

Java

Until Java 8 it did not have any functional programming support. It supports the only subset of Java 8 features while the mobile app development.

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// Filter even numbers and map to their squares
List<Integer> result = numbers.stream()
                               .filter(n -> n % 2 == 0)
                               .map(n -> n * n)
                               .collect(Collectors.toList());
System.out.println(result); // [4, 16]

Null Safety

Kotlin

It is not possible to attribute null values to variables or objects by default. If we try to do so, the code will fail at compile time. Therefore, there are no NullPointerExceptions in Kotlin. However, if the developer wishes to assign a null value, it is possible to mark the variable in question as nullable explicitly. To do so, you must add a question mark:

var str: String? = null
// safe call operator
val length = str?.length
// elvis operator
val message = str ?: "default message"

Java

The famous NullPointerExceptions in Java gives quite a headache. The idea behind NullPointerExceptions is that it enables users to attribute a null value to any variable. Yet, suppose users attempt to use an object reference that happens to have a null value. In that case, Java’s NullPointerExceptions come into play and open an exception that developers need to handle.

String str = null;
// check for null before accessing length
int length = (str != null) ? str.length() : 0;
// use ternary operator for default message
String message = (str != null) ? str : "default message";

Code

One of the key differences between Kotlin and Java is that Kotlin requires way less code. It is a very concise language which reduces the chances of making errors and simplifies the developers’ work.

Overall, Kotlin’s brevity makes it more manageable to write large projects considering that it typically requires fewer lines of programming than Java to write the exact same functions. Plus, it knows how to keep it short and straight to the point without compromising syntax’s readability.

Coroutines

In Android, by default, components that belong to the same application run in the same process and thread, typically referred to as the main thread and responsible for the UI. Network I/O and CPU-intensive operations are considered lengthy. When either one of these operations is initiated, the respective calling thread is blocked until the entire operation is completed.

Kotlin

Kotlin provides the capability to create multiple threads. Nonetheless, it introduces a better and more straightforward solution which is coroutines:

import kotlinx.coroutines.*
fun main() = runBlocking<Unit> {
    val job = launch {
        // suspend function call
        delay(1000)
        println("World!")
    }
    println("Hello, ")
    job.join() // wait for job to complete
}

How do coroutines work? Well, coroutines are stackless and allow the developer to write code, suspend the execution and later resume it again. This enables non-blocking asynchronous code that seems synchronous. Instead of creating multiple threads that the developer must handle later, coroutines avoid having too many threads.

Java

Java 19 has a very similar concept of Virtual Threads which is also known as fibers or continuations, and are implemented using the concept of coroutines. Unlike traditional threads, which are scheduled by the operating system, virtual threads are scheduled by the JVM. This means that virtual threads are significantly lighter in weight and can be created in large numbers without incurring a significant overhead on the system.

import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class Main {
    
    public static void main(String[] args) throws Exception {
        var executor = Executors.newVirtualThreadExecutor();
        
        var job = executor.submit(() -> {
            try {
                TimeUnit.MILLISECONDS.sleep(1000);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            System.out.println("World!");
        });
        
        System.out.println("Hello, ");
        job.get(); // wait for job to complete
        executor.shutdown();
    }
}

Primitive types

Kotlin

As soon as you initiate a variable of a primitive type, it will be automatically considered an object.

val myNumber: Int = 42

Java

Variables of a primitive type are not objects; they are predefined Java’s data types. Java has eight different primitive data types: int, byte, short, double, float, boolean, char, and long. Consequently, these variables cannot be an object represented by a struct or a class. Even though primitive types are not classes, you can use classes that can wrap a value of the primitive type. To do so using Java, the developer must indicate it explicitly.

int myNumber = 42;

Smart Casts

Kotlin

The compiler is able to automatically cast an object to a more specific type when it is guaranteed to be of that type based on the flow of control.

fun printLength(obj: Any) {
    if (obj is String) {
        println("Length of string is ${obj.length}")
    } else if (obj is IntArray) {
        println("Length of array is ${obj.size}")
    }
}

Java

In order to cast an object in Java, the developer must check the variables’ type in consonance to the operation. Need to explicitly cast the object to the appropriate type in each case.

void printLength(Object obj) {
    if (obj instanceof String) {
        String str = (String) obj;
        System.out.println("Length of string is " + str.length());
    } else if (obj instanceof int[]) {
        int[] arr = (int[]) obj;
        System.out.println("Length of array is " + arr.length);
    }
}

Summary

Java and Kotlin are two popular programming languages that have their strengths and weaknesses. Java is well-suited for developing large applications with numerous features that need to run on different platforms like Android, iOS, Windows, or Linux. It benefits from mature libraries that support application development across various platforms effectively.

On the other hand, Kotlin is a better option for applications that require high performance, such as those running on older Android phones or used for image editing. Kotlin’s streamlined and efficient design enables it to perform better in such situations, especially when scaling is a concern. Furthermore, Kotlin is ideal for cross-platform applications that require platform independence and can be cross-compiled for various platforms, including Android. This is because Kotlin can perform these functions efficiently while Java cannot due to its bytecode that can only compile code for one specific platform at a time.

Share