Kotlin vs Java: reasons to switch from Java to Kotlin today

Sinch
Sinch
Oct 21, 2020 · 12 min read

written by Bernardo do Amaral Teodosio, developer at Sinch;

In mid-2016 we, as Android developers, were already eyeing the Kotlin language. At Movile Group, and at Wavy, we have innovation in our DNA, and as soon as the opportunity arose to start using the new language, we decided to do so.

If you don’t know Kotlin yet, a brief description I usually use when I talk about it is:

A concise, static typing programming language created by JetBrains and which is in constant development. It is fully interoperable with Java and also allows the creation of web and native applications.

The Kotlin language was created by JetBrains, which is the same company that created the best IDEs currently on the market, such as: PHPStorm, CLion, PyCharm and IntelliJ, which is also the basis of Android Studio, Google’s official IDE for application development Android.

Today’s article is about Kotlin vs Java. I’ll give you x reasons why you should switch from Java to Kotlin today. If you are interested in the subject, read on!

Java interoperability

Kotlin being 100% interoperable with Java means you can start using it as soon as you want, in an existing Java project (be it a server or an Android application), without the need to migrate any code, and without having to wait the opportunity for a new project to start using the language.

You can insert the Kotlin code into an existing project today, call it from Java code, and vice versa, and you will have no problem with that. This is a good reason to give language a try.

Officially supported by Google

In 2016, when we started using Kotlin in our projects, Google had not yet officially commented on the use of the language for Android development.

However, as we have a culture of testing and learning fast, this silence was not an impediment — a fact that we are proud of today.

Since the official announcement by Google on the support of the Kotlin language for Android development, at Google I / O 2017, the number of Android applications written partially or entirely in Kotlin is growing, which means that more and more developers on the market are making use of it.

As of Android Studio 3.0, support for the Kotlin language was added natively in the IDE, making it even easier to develop applications using it.

Simple, concise and low-verb syntax

Kotlin has a simple and concise syntax. Codes written in this language have better readability than equivalent codes written in Java.

Below are three different ways to write a simple function that adds two numbers together.

fun sum(a: Int, b: Int): Int {
return a + b
}
fun sum2(a: Int, b: Int) = a + bpublic class JavaTests {

public static int sum(final int a, final int b) {
return a + b;
}

}

The first 2 examples are written in Kotlin, while the third is written in Java.

We can see that the Kotlin code, in addition to being more readable, is also much less verbose. We don’t need to create a class to do a simple function, as in Java.

Furthermore, it goes without saying that the parameters are final in Kotlin. They already are, by default — which is another very positive point in the language, which encourages immutability.

Oh, and another wonder: semicolons are optional!

class KotlinClass(private val firstAttribute: String,
private val secondAttribute: Int) {

private var mutableIntAttribute = 0

private var mutableStringAttribute = "Hi!"

}
class KotlinClass(private val firstAttribute: String,
private val secondAttribute: Int) {

private var mutableIntAttribute = 0

private var mutableStringAttribute = "Hi!"

}

As explained in the examples above, the Java class constructor, which does nothing but initialize the class attributes with the values ​​of the parameters provided, was rewritten in Kotlin in a much simpler way, just after the class name.

Everything in parentheses after the name of the Kotlin class are properties of the class, initialized from its primary constructor parameters.

At some point, you have certainly had to make classes that simply serve to represent a set of data in the form of a model, and nothing more. They are the famous Models / POJOs classes.

Generally, these classes have nothing special, just a few attributes: a constructor that initializes them, getters and setters methods, and sometimes override the equals (), hashcode () and toString () methods.

Although simple, in Java they tend to get very large due to the verbosity of the language.

Below is an example of a class that represents a user in Java:

public class JavaUser {

private String id;
private String firstName;
private String lastName;
private int age;

public JavaUser(final String id, final String firstName, final String lastName, final int age) {
this.id = id;
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}

public String getId() {
return id;
}

public void setId(final String id) {
this.id = id;
}

public String getFirstName() {
return firstName;
}

public void setFirstName(final String firstName) {
this.firstName = firstName;
}

public String getLastName() {
return lastName;
}

public void setLastName(final String lastName) {
this.lastName = lastName;
}

public int getAge() {
return age;
}

public void setAge(final int age) {
this.age = age;
}

@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;

final JavaUser javaUser = (JavaUser) o;

if (age != javaUser.age) return false;
if (id != null ? !id.equals(javaUser.id) : javaUser.id != null) return false;
if (firstName != null ? !firstName.equals(javaUser.firstName) : javaUser.firstName != null) return false;
return lastName != null ? lastName.equals(javaUser.lastName) : javaUser.lastName == null;
}

@Override
public int hashCode() {
int result = id != null ? id.hashCode() : 0;
result = 31 * result + (firstName != null ? firstName.hashCode() : 0);
result = 31 * result + (lastName != null ? lastName.hashCode() : 0);
result = 31 * result + age;
return result;
}

@Override
public String toString() {
return "JavaUser{" +
"id='" + id + '\'' +
", firstName='" + firstName + '\'' +
", lastName='" + lastName + '\'' +
", age=" + age +
'}';
}
}

Kotlin has a special type of classes that can be used especially for these cases. These are called data classes.

Using this functionality, the equivalent Kotlin code can be seen below:

data class KotlinUser(var id: String,
var firstName: String,
var lastName: String,
var age: Int)

As we can see, the class written in Kotlin is much simpler, faster to write and also simpler to read, since it is not filled with unnecessary code that hinders reading.

By default, data classes already have a useful implementation of equals (), hashCode () and toString (), which use the properties defined in the class constructor as parameters, so it is not necessary to re-implement these methods.

In addition, data classes also have a copy () method, which is used to create copies of an instance by changing only certain attributes.

Suppose, for example, that you want to create a new user, with the same attributes as an existing user, but only changing the age. It is simple to do this using this method:

fun newUser(newAge: Int, oldUser: KotlinUser): KotlinUser {
return oldUser.copy(age = newAge)
}

If you are a Java programmer, you may have seen the following line on your monitor:

java.lang.NullPointerException

The so famous and feared NullPointerException, which occurs when we try to call a method, access an attribute or do something like that in a null instance, that is, using a reference that points nowhere, is probably the most common exception — and also the most annoying — when it comes to Java.

Worse still, it occurs at run time — which means that the execution of your application can be interrupted when this exception is thrown, severely harming its users.

With Java 8, the concept of Optionals came into the language, which helps to avoid the number of occurrences of this exception.

However, if you are an Android developer, you know how difficult it is to use features from newer versions of Java in applications.

Due to the limitations of the system versions, in order to be able to use new Java features in older versions of Android, we need to look for compatibility alternatives, such as Retrolambda.

Using Kotlin, we don’t have to worry about that. The language differentiates references that can be null from those that cannot be, according to the defined type.

If you define a type as being “non-null”, then the language guarantees, at compile time, that null values ​​will not be assigned to the reference of the non-null type.

// example 1
var notNull: String = "Hi!"

// example 2
var nullableAttempt: String = null // compile time error

// example 3
var nullable: String? = null // optional type must be used

// example 4
var inferredType = "Inferred type here is String, non null"

In the first example, we have a reference of type String, explicitly declared. This reference cannot receive null values.

In the second example, we have a reference of type String, also explicitly declared. When trying to assign the value null to this reference, we get a compilation error, since the reference is of type String and not String? (the presence of the interrogation indicates a type that can receive null values).

In the third example, we have a reference of type String ?, nullable, which can receive null values.

In the fourth example, we have a reference of type String, not explicitly declared, but which is inferred by the compiler from the value assigned to the variable.

// example function
fun getLengthPlusThree(string: String): Int {
return string.length + 3
}

// test 1
fun testGetLength(): Boolean {
val expectedResult = 6
val length = getLengthPlusThree(string = null) // compile time error
return expectedResult == length
}

// test 2
fun testGetLength2(anotherString: String?): Boolean {
val expectedLength = 4
val length = getLengthPlusThree(string = anotherString) // compile time error
return expectedLength == length
}

// test 3
fun testGetLength3(testString: String?): Boolean {
val expectedLength = 4

// if is an expression that can be used as a return
return if (testString != null) {
expectedLength == getLengthPlusThree(testString)
} else {
false
}
}

In the tests above, we see some examples of the functionality in use. We initially have an example function, getLengthPlusThree (), which returns the length of a string (received by parameter) plus three.

Note that the string received by parameter is non-null, which means that it is impossible to call this function by passing a null string as a parameter.

In the first test, we tried to call the function by directly passing a null string as a parameter. As expected, we received a compile-time error.

In the second test, we try to call the function by passing a nullable string as a parameter. Again, we have a compilation error.

Although we don’t know what the value of the anotherString parameter is, we do know that it can be null. Because of this possibility, the getLengthPlusThree () function cannot be called.

In the third test, we use an if to test the received parameter value. After ensuring that the parameter is not null, we can normally call the getLengthPlusThree () function, as there is no longer any possibility that the value of the passed parameter is null.

In these examples, we can also notice two other interesting features of the language.

The first of these is the naming of parameters. In tests 1 and 2, when calling the getLengthPlusThree () function, we explain the name of the function’s string parameter when calling it.

Although optional, this feature makes the code more readable — especially when calling functions with parameters of the boolean type.

The second feature is the return of the if expression. In Kotlin, we can use the if (and also other constructs) as a function return.

The famous Utils classes, any reasonably large Java project contains at least one: StringUtils, FileUtils, AndroidUtils. It is very likely that you have already used or even created one, at least once.

In general, Utils classes are created to group methods that perform certain operations on objects of the same type.

A FileUtils class, for example, would be full of static methods, which take a File as a parameter, and perform a certain operation with this file.

Such classes, although functional (they are, in fact, useful), are usually composed of verbose code, and consequently more laborious to read and write.

Kotlin has a feature that solves the problem of the Utils classes and makes the code more simple, readable and beautiful.

In addition, it also facilitates its maintenance: the Extension Functions. Basically, Kotlin provides the ability to add functionality to an existing class, without having to inherit it for this.

fun String.suffixedWithAmazing(): String {
return this + " is amazing!"
}

In the example above, we created a function that extends the String class. Now, we can call this function on any string, as in the example below:

fun testExtension() {
println("Kotlin".suffixedWithAmazing()) // prints "Kotlin is amazing!"
}

If the function is used in a file other than the one in which it was declared, it is necessary to import it.

Now consider the example below, in which an Extension Function was created to calculate the size in megabytes of a file.

Instead of creating a FileUtils class to do this, we can write it in the following format, in Kotlin:

private const val BYTES_TO_MEGABYTES_CONVERTER = 1000000.0

fun File.sizeInMegaBytes(): Double {
return this.length() / BYTES_TO_MEGABYTES_CONVERTER
}

fun testExtensionFunction(vararg files: File) {
files.forEach { file -> println(file.sizeInMegaBytes()) }
}

The vararg keyword indicates that the number of files that the method will receive as a parameter is undefined (variable number of arguments).

If you develop Android, you’ve probably used Glide or Picasso, famous libraries for uploading images.

We can use Extension Functions to create an abstraction around these libraries which, in addition to making the code smaller and much more readable, also facilitates an eventual replacement of libraries.

The example below illustrates this situation:

fun ImageView.load(url: String) {
Glide.with(this.context).load(url).into(this)
}

private fun displayImageTest(imageView: ImageView) {
imageView.load("https://kotlinisreallyawesome.org/kotlin.png")
}

Kotlin supports high-order functions. It is possible to use functions like types and parameters, and even return functions from a function.

With this functionality, a wide range of possibilities for functional programming using Kotlin is open.

But, not only for this reason, it is possible to create a much cleaner, concise and simple code, using high-order functions than in Java, which only (partially) introduced this functionality from Java 8.

If you use RxJava for Android development, you probably also use some additional library to use lambdas, as this functionality was only introduced in Java 8, and is fundamental to the functioning of RxJava.

Using Kotlin, no additional library is needed, as the language natively supports this functionality.

In addition, the language also has streams and operators for streams, extremely useful features that were only introduced in Java 8.

Below is an example of Kotlin code that uses high-order streams and functions:

import java.util.concurrent.Future
import kotlin.concurrent.thread

data class FetchOrderResult(val success: Boolean)

fun testStreams(orderCallback: (FetchOrderResult) -> Unit) {
val bigSmokeOrder = listOf(
"I want two number nines",
"a number nine large",
"a number six with extra dip",
"a number seven",
"two number forty-five",
"one with cheese",
"and a large soda")

val lightBigSmokeOrder = bigSmokeOrder
.filter { it.contains("nine") }
.filter { line -> !line.contains("large") }
.map { it.toUpperCase() }

lightBigSmokeOrder.forEach { println(it) }
lightBigSmokeOrder.forEach(::println) // same thing as above
thread {
val order = fetchOrder(orderList = lightBigSmokeOrder).get()
orderCallback(order)
}
}

fun fetchOrder(orderList: List<String>): Future<FetchOrderResult> {
TODO() // do something with orderList and create a FetchOrderResult
}

The testStreams () function builds a list of items for an order, filters them based on certain criteria and then sends the order, in a new thread, invoking the fetchOrder () function.

When this is completed, the function invokes orderCallback (), the callback with the order’s response. Note that orderCallback () is a function that was passed as a parameter to the initial testStreams () function.

You may have noticed that when it comes to Kotlin vs. Java, Kotlin wins. In addition to all these reasons and in addition to your productivity increasing a lot, it is fun to program in Kotlin.

It’s fun not having to write a bunch of boilerplate to do something simple, discovering a new way of programming and not getting stuck in a certain version of the language and failing to try new features because of that.

The world spins, time passes and we are not obliged to continue in the past. Take a chance on something new and try Kotlin.

In this and this link you can find parts of a live in which I participated, talking about how we use the language at Movile.

Below, a presentation I made at Kotlin Night Campinas, an event that took place in 2017, in which I had the opportunity to talk a little about the language. The audio of the presentations of the event can be found here.

Sinch Blog

Stories about how we create incredible experiences for people through technology

Sinch Blog

Chatbots, Conversational Commerces, Artificial Intelligence, Products and Engineering are our passions. See stories by Sinchers about it and our Culture. Dream Big, Win Together, Kep It Simple and Make It Happen: this is how we create experiences.

Sinch

Written by

Sinch

Follow our publication to see stories about technology and culture written by Sinchers! medium.com/wearesinch

Sinch Blog

Chatbots, Conversational Commerces, Artificial Intelligence, Products and Engineering are our passions. See stories by Sinchers about it and our Culture. Dream Big, Win Together, Kep It Simple and Make It Happen: this is how we create experiences.