ApriorIT

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.

Contents:

Kotlin vs Java comparison

Function-to-function comparison

1. Data classes

2. Null safety

3. Extension functions

4. Sealed classes

5. Higher-order functions and inline modifier

6. Standard functions: run, apply, with, let, also

7. Delegated properties

Conclusion

Kotlin vs Java comparison

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.

Kotlin vs Java: Key advantages

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).

Read also:
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: 

  1. Data classes
  2. Null safety
  3. Extension functions
  4. Sealed classes
  5. Higher-order functions and inline modifiers
  6. Standard functions
  7. Delegated properties

Function-to-function comparison

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.

Function-to-function comparison

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.

1. Data classes

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:

public class City {
    private String name;
    private String country;
    private String imageUrl;
    private String description;
 
    public City(String name, String country, String imageUrl, String description) {
        this.name = name;
        this.country = country;
        this.imageUrl = imageUrl;
        this.description = description;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public String getCountry() {
        return country;
    }
 
    public void setCountry(String country) {
        this.country = country;
    }
 
    public String getImageUrl() {
        return imageUrl;
    }
 
    public void setImageUrl(String imageUrl) {
        this.imageUrl = imageUrl;
    }
 
    public String getDescription() {
        return description;
    }
 
    public void setDescription(String description) {
        this.description = description;
    }
 
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        City city = (City) o;
        return Objects.equals(name, city.name) &&
                Objects.equals(country, city.country) &&
                Objects.equals(imageUrl, city.imageUrl) &&
                Objects.equals(description, city.description);
    }
 
    @Override
    public int hashCode() {
        return Objects.hash(name, country, imageUrl, description);
    }
 
    @Override
    public String toString() {
        return "City{" +
                "name='" + name + '\'' +
                ", country='" + country + '\'' +
                ", imageUrl='" + imageUrl + '\'' +
                ", description='" + description + '\'' +
                '}';
    }

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:

data class City (val name: String, val country: String, val imageUrl: String, val description: String)

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. 

Related services

Custom Mobile App Development Services

Each 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:

val london = City(name = "London", country = "England", imageUrl = "https://londonImagePath.com", description = "Long description of London")
val liverpool = london.copy(name = "Liverpool", imageUrl = "https://liverpoolImagePath.com", description = "Long description of Liverpool")

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 val or var.
  • 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.

Read also:
Pentesting Android Applications: Tools and Step-by-Step Instructions

2. Null safety

Unlike Java, Kotlin differentiates nullable and non-nullable types. This is why Kotlin helps you avoid a NullPointerException during compilation and saves you a lot of nerves. 

By default, Kotlin assumes that a variable of any reference type can’t be null. Let’s try to assign null as a city value:

var city: City = City(name = "London", country = "England", imageUrl = "https://londonImagePath.com", description = "Long description of London")
city = null

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:

var city: City? = null

This code will compile without errors. But now every time you want to access city, you should check that it’s not null first:

var city: City? = null
......
var cityName: String? = null
if (city != null) {
    cityName = city.name
}

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:

var city: City? = null
......
var cityName: String? = city?.name

Moreover, you can make this code even safer using the Elvis operator (?:):

var city: City? = null
......
var cityName: String = city?.name ?: ""

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:

var city: City? = null
......
var cityName: String = city!!.name

In this case, if the reference contains null, the result will contain a NullPointerException.

Read also:
Top 7 Methods of Data Encryption in Android Applications

3. Extension functions

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:

public static void makeVisibleOrGone(View view, boolean visible) {
    view.setVisibility(value ? View.VISIBLE : View.GONE);
}

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:

fun View.makeVisibleOrGone(visible: Boolean) {
    this.visibility = if (visible) View.VISIBLE else View.GONE
}

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

view.makeVisibleOrGone(false)

Let’s take a look at the decompiled Java version of this extension function:

public static final void makeVisibleOrGone(@NotNull View $receiver, @NotNull Boolean visible) {
    ...
}

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:

open class City (val name: String, val country: String)
class Capital(name: String, country: String) : City(name, country)
 
fun City.isCapital(): Boolean {
    return false
}
 
fun Capital.isCapital(): Boolean {
    return true
}
 
fun isCapitalCity(city: City): Boolean {
    return city.isCapital()
}
 
.............
 
println(Capital("London", "Great Britain").isCapital())

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 City class.

Read also:
How to Receive and Handle SMS on Android

4. Sealed classes

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:

public enum CitiesViewEvent {
    SHOW_ERROR,
    SHOW_ANOTHER_SCREEN
}
 
 
class CitiesViewModel {
    val viewEvent: SingleLiveEvent<citiesviewevent> = SingleLiveEvent()
    ........
     
    private fun showErrorMessage() {
        viewEvent.value = CitiesViewEvent.SHOW_ERROR
    }
}
 
 
class CitiesView {
    private fun subscribeForModel() {
        .....
        viewModel.viewEvent.observe(this, Observer { event ->
            when (event) {
                CitiesViewEvent.SHOW_ERROR -> showErrorSnackbar()
                .........
            }
        })
    }
}

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:

sealed class CitiesViewEvent {
    class ShowError(val message: String): CitiesViewEvent()
    class ShowCityDetails(val city: City): CitiesViewEvent()
}
 
 
class CitiesViewModel {
 
    val viewEvent: SingleLiveEvent<CitiesViewEvent> = SingleLiveEvent()
    ........
     
    fun onCityClicked(city: City) {
        viewEvent.value = CitiesViewEvent.ShowCityDetails(city)
    }
 
    private fun showErrorMessage(stringResId: Int) {
        viewEvent.value = CitiesViewEvent.ShowError(resources.getString(stringResId))
    }
}
 
 
class CitiesView {
 
    private fun subscribeForModel() {
        .....
        viewModel.viewEvent.observe(this, Observer { event ->
            when (event) {
                is CitiesViewEvent.ShowError -> showSnackbar(event.message)
                is CitiesViewEvent.ShowCityDetails -> showCityDetails(event.city)
            }
        })
    }
}

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.

Related services

Mobile Device and Application Management

5. Higher-order functions and inline modifiers

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:

(SomeParameterType) -> ReturnType
(SomeParameterType1, SomeParameterType2) -> ReturnType
(SomeParameterType1, SomeParameterType2, SomeParameterType3) -> ReturnType
......

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:

val sum = { a: Int, b: Int -> a + b }

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:

val sum = fun(a: Int, b: Int): Int = a + b

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:

val cities = listOf<City>()
val citiesCountFunc = c::size
val citiesCount = citiesCountFunc()

Now let’s take a look at a higher-order function from the Kotlin standard library. Filter is one of the most popular functions for dealing with a collection of objects:

val cities = listOf<city>(City(name = "Liverpool", country = "Great Britain"), Capital(name = "London", country = "Great Britain"))
 
val predicate = { city: City -> city.isCapital() }
val capitals = cities.filter(predicate)

This function has the following definition:

public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T>

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. 

Read also:
How to Access Private App Data on Android (No Root)

6. Standard functions: run, apply, with, let, also

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:

inline fun <T, R> with(receiver: T, block: T.() -> R): R {
    return receiver.block()
}
 
 
inline fun <T> T.also(block: (T) -> Unit): T {
    block(this)
    return this
}
 
 
inline fun <T> T.apply(block: T.() -> Unit): T {
    block()
    return this
}
 
 
inline fun <T, R> T.let(block: (T) -> R): R {
    return block(this)
}
 
 
inline fun <T, R> T.run(block: T.() -> R): R {
    return block()
}

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.

run

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:

..................
intent.extras?.run {
    if (containsKey(CITY_EXTRA_KEY)) {
        viewModel.init(Parcels.unwrap(getParcelable(CITY_EXTRA_KEY)))
    }
}

In this example, if the extras isn’t 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. 

apply

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:

fun getCallingIntent(activity: Activity, city: City): Intent {
    return Intent(activity, CityDetailsActivity::class.java)
        .apply { putExtra(CITY_EXTRA_KEY, Parcels.wrap(city)) }
}

In the example above, the apply function puts the City object in the Intent extras before returning it.

with

When you need to access receiver properties and aren’t particularly interested in the result, it’s appropriate to use the with function:

with(cities_loading_progressbar) {
    if (isLoading) show()
    else hide()
}

This function is also useful when you need to introduce a helper object with properties or functions required for calculating a value.

let

The most popular use case of the let function is to execute some code block if a receiver is not null:

localDataSource.getCities()?.let { emitter.onSuccess(it) }

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.

also

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:

var a = 1
var b = 2
 
a = b.also { b = a }

As a result, the values of variables a and b will be swapped.

7. Delegated properties

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:

val viewEvent: SingleLiveEvent<CitiesViewEvent> by lazy { SingleLiveEvent<CitiesViewEvent>() }
 
 
fun onCityClicked(city: City) {
    viewEvent.value = CitiesViewEvent.ShowCityDetails(city)
}

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:

val cities: List<City> by Delegates.observable(listOf()) { prop, old, new ->
    println("$old -> $new")
}

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.

Conclusion

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!

 

Let's talk

4000 chars left
Attach a file
Browse
By clicking Send you give consent to processing your data

Book an Exploratory Call

Do not have any specific task for us in mind but our skills seem interesting? Get a quick Apriorit intro to better understand our team capabilities.

Book time slot

Contact Us

P: +1 202-780-9339
E: [email protected]

8 The Green, Suite #7106, Dover, DE 19901
United States

D-U-N-S number: 117063762

btnUp