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.