10 reasons to switch from Java to Kotlin right now!
v-aksenov
Posted on August 18, 2021
My name is Viacheslav Aksenov, I am developer of big and complex backend systems. I started from Java 7, than upped to Java 8 and sometimes Java 11 for developing. Last year I Writing all new code on Kotlin and this is totally changed my life. I didn't know before that Kotlin can be used for backend developing and more than - for backend developing using all top Java framework like Spring, Jackson, etc.
So I want to share my happiness with you and give 10 reasons of why you should to take a see on Kotlin and integrate it to your projects.
1. Null safety
In Kotlin, it is required to explicitly indicate whether a particular method can return null or not. Thus, we can assume that all data is already wrapped in an analogue of Optional. And NullPointerException will be so rare by you that you will miss it.
fun saveReviewNullable(r: Review): Review? = reviewRepository.save(r)
fun bar(r: Review) {
val savedReviewNullable: Review = saveReviewNullable(r)!! // can throw NPE. Not safety
val savedReviewNotNull: Review = saveReviewNullable(r) ?: Review() // safety way
}
2. Highlighting the main constructor
The bottom line is: there is a Primary constructor and a Secondary constructor. Helpers are required to call the main one as a constructor of the parent class.
class Cat(val name: String, val color: String, val height: Int) {
constructor(name: String) : this(
name = name,
color = "fixed color",
height = 10
)
}
3. Initialization and logic of working with DTO classes
The example is hackneyed, but as clear as possible.
data class Cat(val name: String, val color: String, val height: Int)
And now it's the same in Java:
public class Cat {
private final String name;
private final String color;
private final Integer height;
public Cat(String name, String color, Integer height) {
this.name = name;
this.color = color;
this.height = height;
}
public String getName() {
return name;
}
public String getColor() {
return color;
}
public Integer getHeight() {
return height;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Cat cat = (Cat) o;
return Objects.equals(name, cat.name) && Objects.equals(color, cat.color) && Objects.equals(height, cat.height);
}
@Override
public int hashCode() {
return Objects.hash(name, color, height);
}
}
It seems to me that even comments are superfluous here.
Data class pointers in Kotlin by default imply getter, setter (for var fields), equals, hashcode, toString for all fields. If you want, you can override each of these methods in your own way, but this is rarely required.
class Cat(val name: String, val color: String, val height: Int) {
override fun toString(): String = "overridden toString"
}
4. Explicit declaration of mutable and immutable fields - var/val
Another advantage: in Kotlin, you get pretty simple and elegant constructs. If you want the dto field to be changeable, then var is used to declare it. Then the setter method will be created and the field will not be final.
And if you need to make the field immutable, you should use val for the declaration. It looks very nice and simple. Plus no need to follow up on helper methods.
Example: the color and height fields can be changed after creation, and the name can only be changed when the object is initialized:
data class Cat(val name: String, var color: String, var height: Int)
5. Collections are immutable by default
What appeared in Java a little later has long been in Kotlin - creating collections is immediately immutable.
val list = listOf("one", "two")
val map = mapOf(1 to "one", 2 to "two")
val set = setOf(1, 2 ,3)
Any changes to these collections will create a new immutable collection after conversion:
val list = listOf("one", "two")
val list2 = list.plus("three")
But you won't be able to change any element separately. For classic mutable collections, explicitly mutable analogs are used:
val list = mutableListOf("one", "two")
val map = mutableMapOf(1 to "one", 2 to "two")
val set = mutableSetOf(1, 2 ,3)
6. Extensions.
For example you have really uncomfortable model from other API, that store data in really high nesting. In Java you would need to write converter from one useless model to usefull one.
In Kotlin you can just add Exctension for any class, even for class from external libriary and use it as you want:
data class SomeBadModel(
val innerModel: EvenBadModel
)
data class EvenBadModel(tons of fields)
fun SomeBadModel.doGoodStuff(val any: Any?): Any? = usefullLogicHere
7. Working with complex classes using primitive methods
An advantage of Kotlin, which I never cease to rejoice in is the ability to use operators for basic operations on complex classes. If you need to add BigDecimal numbers, you take them and write them through plus. You do not need to explicitly call the method on the first term.
val a = BigDecimal(1)
val b = BigDecimal(2)
val sum = a + b
In Java, you need to call a special method:
BigDecimal a = new BigDecimal(1);
BigDecimal b = new BigDecimal(2);
BigDecimal sum = a.add(b);
It's the same with arrays: if you want to remove an element from a mutable array, you write the array minus this element. And if the element is present, then it will be removed.
val list = listOf("one", "two") - "one" // list = ["two"]
8. Possibility of one-line methods really on one line
If the method is simple and consists of one operation or a chain of operations written in one line, then it is not necessary to write curly braces and return. You write directly:
fun getReviewByTitle(title: String): List<Review> = reviewRepository.getAllByTitle(title)
Instead of Java option:
public List<Review>(String title) {
return reviewRepository.getAllByTitle(title);
}
9. Scope functions
Interesting moves towards functional programming in the spirit of highlighting context: lambdas can be rotated as you like.
There are let, apply, also, with, run functions. Because of their abundance, at first the question arises: what is suitable for a particular case. But when you get used to it, it becomes unclear how you used to live without them.
A simple example: take the result and process it somehow:
fun save(review: Review): Review = repository.save(review)
fun bar(r: Review) = saveReview(r).let { it.comment + it.date}
Or initialize the object and additionally initialize its var fields:
class Cat(val name: String, val height: Int) {
var color: String? = null
}
fun bar() = Cat("Fred",10).apply { color = daoService.getPopularColor() }
10. Easy to integrate into an existing Java project.
If you are just looking at Kotlin for the backend, then keep in mind that in the environment that launches your project in Java 8, you can run a compiled project in Kotlin without dancing with a tambourine. Yes, on the same jvm, on the same environment and with a minimum of effort.
It was a discovery for me that even within the same application there can be classes in Java and Kotlin. All the magic happens at compile time. Depending on the settings, you can specify what to build first: Kotlin classes or Java classes.
Compilation of Kotlin sources into byte code from Java LTS - 8, 11 and (so far experimental) 16 is now available.
Conclusions
Someone might say that this is sugar sugar and they will be right. But on my own I will say: if a large number of boilerplate things are included in the language, and you do not need to constantly think about them, the development process becomes simpler, the number of errors is less. But you need to think about the code style more often, because without it you can screw a lot.
Now I still continue to write and see code in Java, but in 99% of cases this is due to educational programs in which I take part as a mentor.
I advise everyone to try it - at least on a pet project to understand whether the Kotlin paradigms are right for you or not.
Posted on August 18, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.