In 2019, Google stated that Kotlin is the preferred language for Android development. Does that mean Kotlin is going to replace Java for Android development? Actually, it’s already happening. Stack Overflow’s 2019 Developer Survey discovered that Kotlin is among the top four most loved languages.
In this article, we explore the advantages of Kotlin over Java and overview seven Kotlin features that make development faster and more fun. This information will be useful for Android developers who are considering using Kotlin programming language features and want to know more about them.
Since its release in 1996, Java has become one of the most popular languages in the world according to the PopularitY of Programming Language index. But today, Java gets a fair amount of criticism for its design flaws, forced object-oriented programming, and issues with security and performance.
In 2015, JetBrains released Kotlin — a new open-source object-oriented programming language designed as a better version of Java. Since then, there’s been an ongoing argument between fans of both languages as to which is better. Let’s try to figure out if Kotlin will completely substitute Java one day and when it’s better to choose one language over the other.
Let’s see why it’s beneficial to use Kotlin:
- Full interoperability with Java. When working with Kotlin, you can freely call Java code and Java interfaces. You can also use Java frameworks, libraries, etc. And if you decide to implement Kotlin in an existing Java project, you don’t need to rework any existing code.
- Intuitive and laconic syntax. Kotlin code is simple and understandable. Developers that have never studied Kotlin can easily conduct a code review. For an experienced developer, learning the syntax of this language takes several hours.
- Backend support. Kotlin has ktor, http4k, and other native frameworks for backend developers. Spring Framework 5.0 and higher also supports Kotlin.
- Compatibility with all Android versions. Being a primary language for Android development, Kotlin is supported by all Android versions, while other languages have some restrictions. For example, most Java 8 APIs are supported only starting from Android Nougat (API level 24).
Rust vs C++ Comparison
With such benefits, Kotlin proves to be a promising, vibrant, and rapidly developing programming language. However, Java has several benefits of its own:
- Fast and stable compilation. In clean builds, Java compiles 10–15% faster than Kotlin. Also, the Java compiler is more stable. However, the compilation speed during partial builds is almost identical.
- Robust ecosystem. Over the years, the Java community has become one of the largest, most supportive, and most well-equipped. Java developers can study thousands of tutorials, guides, and books that can help them solve any issue.
- Flexibility. Java can run anywhere, from a virtual machine to a browser window.
Code speaks louder than words, so let’s compare the following features in Kotlin and Java:
- Data classes
- Null safety
- Extension functions
- Sealed classes
- Higher-order functions and inline modifiers
- Standard functions
- Delegated properties
Kotlin developers claim their language has unique functionality that makes development easier and faster than with Java. Let’s take a look at Kotlin features and possible alternatives provided by Java.
Check out this comparison from the Kotlin documentation to find out more about Kotlin functions that are not available in Java.
Both languages provide a great number of benefits to developers, but they serve different purposes. If you have a complex application in mind, Java provides you with the knowledge, tools, and speed to develop it. But if you need to build an Android app and have strict time limitations (or simply want to make your life easier), it’s best to go with Kotlin.
Now let’s find out how to use some of Kotlin’s best features and what benefits they provide.
The main purpose of data classes in Kotlin is to hold data, i.e. your data models, in the domain layer. To illustrate the benefit of Kotlin data classes, let’s first consider the following class written in Java:
Quite a lot of code for a class that holds only four strings, isn’t it? However, you can’t avoid creating such boilerplate code in Java. This is what coding in Java is like. Now let’s look at the implementation of the same class in Kotlin:
That’s pretty much it; just one line of code! The Kotlin compiler will generate the rest for you. It’s worth mentioning that we’ll get the same functionality as in the Java example and even more! Data classes in Kotlin also make it easier to copy an object while altering some fields and keeping the rest unchanged.
data class object comes with a default copy function that can be used to create exact or modified copies of the original object while keeping it intact:
Notice that we didn’t pass the
country value when creating
liverpool, and it will remain unchanged (
England like in the first data class).
While Kotlin offers a simple and convenient way of creating data classes, there are some restrictions you should keep in mind:
- The primary constructor needs to have at least one parameter.
- All primary constructor parameters need to be marked as
- Data classes cannot be abstract, open, sealed, or inner.
- Data classes don’t play well with inheritance: there’s no way to implement
equals()correctly in a hierarchy of non-abstract classes.
By default, Kotlin assumes that a variable of any reference type can’t be
null. Let’s try to assign
null as a
Such code simply will not compile, and we’ll see the message “Null cannot be a value of a non-null type String.” If you really need to declare a nullable type, you simply need to add a question mark to the type:
This code will compile without errors. But now every time you want to access
city, you should check that it’s not
Luckily, Kotlin supports Safe Calls that allow us not to write boilerplate code. So if you want to access the name field in a safe way, you need to add a question mark before the dot:
Moreover, you can make this code even safer using the Elvis operator (
The Elvis operator works like this: If the left side is not
null, then it returns the result; otherwise, it returns the value on the right. Now you have a non-nullable
cityName value which is safer than the previous version.
Kotlin has a way to access the nullable field directly without checks. There’s a special syntax for this — two exclamation marks before the dot. But it’s rather dangerous and should be used wisely because if we use
!! to access a field that doesn’t exist, the program will crash.
Here’s an example of using such syntax:
In this case, if the reference contains
null, the result will contain a
At least once, every developer has had to write a utility method for some framework class that they couldn’t extend due to a lack of control. Here’s an example of the utility method from an Android project:
In this example, the View is instantiated by the framework, so it’s out of our control. The code given above has to be written quite often, which is why it’s wrapped in the makeVisibleOrGone method. With Kotlin, you can call this method in a much simpler manner than in Java.
Like any modern language (and unlike Java), Kotlin supports extension functions. They offer a way of extending the existing functionality of a class without needing to inherit it or use design patterns like Decorator. To create such a function, you need to declare a regular function and prefix its with the receiver type and a dot:
Now, you can call the method declared above on every instance of the View class as if it were implemented inside the class by calling
Let’s take a look at the decompiled Java version of this extension function:
And here’s the key thing about extension functions: they’re always dispatched statically. They don’t modify classes they extend. The called extension function is determined by the type of expression on which the function is invoked.
Here’s a code snippet to illustrate this:
The result of this call is
false. Why? Because the extension function being called depends only on the declared type of the
city parameter, which is an instance of the
How to Receive and Handle SMS on Android
A sealed class represents structured and restricted class hierarchies that contain values assigned with only one type from a limited set of types. A sealed class is abstract and can’t be directly instantiated. It’s similar to the enum class with one exception: subclasses of sealed class may have multiple instances containing states. Let’s look at the difference between these two classes.
Let's assume we have an Android application with a Model–View–ViewModel architecture. This application has events (e.g. showing an error, navigating to another screen) in the ViewModel that need to be passed to the View. We can use enum classes to describe instances that represent these events:
While this solution works, it’s rather limited. In most cases, we would want to pass some parameters with the event (e.g. an error message string, some argument for the screen to open). It's difficult to do this because enums cannot form hierarchies with other classes.
In our case, it’s best to use sealed classes instead. Let’s rewrite the above example using sealed classes instead of enums:
It’s very convenient to use sealed classes in conjunction with the
when expression because in that case the compiler tracks the
is checks and applies smart casts. So when you access the event variable in the above example, it’s already been cast to the right type.
In contrast to Java, Kotlin has built-in support for higher-order functions. These are functions that take other functions as arguments or return a function. Consequently, Kotlin has a special type that represents a function:
The closest analog of a function type in Java is a functional interface. There are several ways to obtain an instance of a Kotlin function type. First, you can use a lambda expression. It’s essentially an undeclared function that passes as an expression, and it’s called with the following code:
Also, you can call for an anonymous function — a function with an alternative syntax to a lambda expression. But contrary to a lambda expression, an anonymous function specifies the return type. Here’s how you can call one:
Finally, you can use a callable reference to an existing declaration. For example, we can get a reference of the getSize function of the
list of City instances, store it in a callable variable, and then call that variable as if it were a list method:
This function has the following definition:
The Filter function returns a list of elements that match the given predicate, which, in turn, is also a function. We passed the predicate function into the Filter function so it can be executed later.
You may be wondering what the inline keyword is in the function signature above. The goal of inlining is to reduce the runtime overhead created by lambda expressions and function references. Keep in mind that every function type is implemented as an interface, meaning that every lambda expression will actually be compiled as an anonymous class under the hood.
While inlining could be a topic for a separate article, let’s look closer at functional programming features in Kotlin. In order to make it easier to understand some of the standard functions, we’ll first define them all:
The run, apply, with, let, and also functions do similar things: they take the receiver argument and the block of code and then execute the provided block of code on the provided receiver. You can call all of them using the Safe calls we discussed earlier. But it’s obvious that they differ in signature and implementation and were designed to be used in different situations. Let’s take a closer look at each of them.
You can use the run function whenever you want to limit the scope of several variables or to compute some value and return it. Here’s an example of using this function:
In this example, if the
null, the run function executes the code block in lambda on the
Bundle object. Here,
containsKey(String key) and
getParcelable(String key) are Bundle class methods.
The apply function is useful when you aren’t going to access any functions of the receiver within your block and also want to return the same receiver. For example, you can initialize objects with this function:
In the example above, the apply function puts the
City object in the
Intent extras before returning it.
When you need to access receiver properties and aren’t particularly interested in the result, it’s appropriate to use the with function:
This function is also useful when you need to introduce a helper object with properties or functions required for calculating a value.
The most popular use case of the let function is to execute some code block if a receiver is not
In our example, the let block will only be executed if the
cities collection is not
null and the variable
it inside lambda is actually a receiver.
If you aren’t going to access or mutate receiver parameters, use the also function. This function is very handy when you need to execute side effects on an object or validate its data before assigning it to a property. For example, when you need to swap variables, you can do this:
As a result, the values of variables a and b will be swapped.
The main idea behind property delegation is that the class owning the property can delegate the property accessor to a delegate object instead of handling it by itself. According to Kotlin documentation, delegating properties is appropriate (but not limited to) the following situations:
- Lazy properties: the value gets computed only upon the first access
- Observable properties: listeners get notified about changes to this property
- Storing properties in a map instead of in a separate field for each property
Let’s look at an example of property delegation:
In this example, the instance of the
SingleLiveEvent will not be instantiated until someone attempts to access the
viewEvent variable. The
lazy delegate is provided by the Kotlin standard library.
But let’s consider another delegation that’s also quite popular:
Each time the
cities property is updated, the code inside the delegate’s lambda will be executed so you can detect that the
cities collection was changed and react accordingly.
Kotlin is a powerful and elegant language that can make the life of Java and Android developers a lot easier. It’s modern and easy to learn, and the features of Kotlin make development faster and safer. And with the support of Google, this language has all the chances to dominate Android development.
At Apriorit, we’ve already used Kotlin for mobile development. If you want to develop your next project with this language, contact us for useful advice and expert developers!