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 🔍.
Table of Contents
Variable Declaration
No Primitive Types
String Interpolation
Null Safety
When vs Switch
Defining Function
Handling Void
Lambda Expressions
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";
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
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
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)
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<>();
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
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 + "!";
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.")
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
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
- The
String?
type means that the variablename
can hold a String or it can be null.- By using the
?
operator (known as the "safe call operator"), you are telling Kotlin, "Check ifname
is not null before accessing its length property." Ifname
is null, it won’t throw aNullPointerException
; instead, it will return null.
- By using the
- 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");
}
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")
}
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;
}
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
}
-
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
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);
}
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)
}
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());
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]
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");
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")
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
}
}
In this Java example:
- The
Person
class has a constructor that takesname
andbirthYear
as parameters. - The
age
is calculated within the constructor, similar to theinit
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 theage
.
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)
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
}
In this Kotlin example:
- The
Person
class has a primary constructor withname
andbirthYear
parameters. - The
init
block calculates theage
based on thebirthYear
and prints the result. - When the object is created, the
init
block automatically runs to handle the initialization.
Top comments (0)