Rebutting the advantages of Java over Kotlin

Pere Casafont
10 min readJul 7, 2019

--

Photo by Nghia Le on Unsplash

A week ago I made a troll post in which I pretended to be someone who after having used Kotlin for 2 years had a clear view of why Java is better. This is the first thing to rebute: no one will ever claim Java is better after having used Kotlin properly (with all if not most its features) for such a long time. He/she may say they are different at the very most.

From my point of view, though, Kotlin has absolutely nothing to envy from Java and I will easily conclude that anything that can be done in Java can be done better in Kotlin.

The only drawback the language has is that it is relatively new and as such, less known. So whenever your Kotlin team needs new members you will have a harder time having to find either a Kotlin developer or a brave Java developer.

So let’s go through all the “reasons” exposed in my previous article!

Syntax

This is a very reasonable point, especially when it comes from someone with more than 5 years of experience. These drastic syntax changes make the learning curve a bit more steep, but they are here for a reason.

Pascal notation for types

There are multiple reasons why it was done like that. The most important ones are type inference and being able to tell when a variable is final or not with the same amount of characters (val/ var). We can have type inference with C notation, but then we lack consistency along the code.

Then there’s also how well variable names align and the fact that variable names are actually more important than their types.

Notice how well all variable names line up in Kotlin, despite their differences in their types and whether they are final or not:

We don’t have to go all the way back to Pascal to see this notation; new languages have been adopting Pascal notation as well, and in good old SQL the names also go before the types.

Different lambda syntax

The rule-bending twist exposed in the previous article is the main reason why the lambda syntax was changed. This, together with the ability to have inline functions*, is a killer feature.

Let’s review a modified version of the previous article’s example:

As you can see, Kotlin allows different flavors for those who are still learning and getting used to it. This is again a feature that’s harder to get used to, but once you get used to it it makes complete sense.

*Inline functions: when a function is inline and it receives lambda parameters (not tagged with noinline), all the calls to that function will be inlined to each of the call sites and the lambda bodies will also be inlined there. Result: zero runtime lambda overhead.

For some people these reasons will still be not good enough as switching is very uncomfortable. My advice: focus on the next parts of this article, the language is very well worth the effort!

Null safety

In Kotlin we have a distinction between nullable and not nullable types. Nullable variables take more ceremony to be used, but that is something we should have always been doing in every application we write. If something can be null, perform the proper check! …or use the non-null assertion !! and face the possible consequences.

Nowadays, when writing/reading java, I see a !!. in every . because that’s what’s actually happening. Java is always ready and eager to throw a NPE when you try to use the . operator on something that’s null, and all we can do about it is to perform runtime checks and hope really hard that we didn’t miss anything.

Let’s look at the example that I raised up in the troll post:

The fact that the admin exists is asserted by some runtime startup logic (or even worse, manual setup logic). What will happen when this logic fails? A random NPE with that line number in its stack trace.

So I wouldn’t suggest neither of the “solutions” exposed (?. nor !!.). The best solution for such a case would be:

The elvis ?: operator is another very useful Kotlin feature related with null safety: whenever the left operand is null it will default to the right operand. In this case we are calling error (which is an alias of throw IllegalStateException). This allows the compiler to assert that by line 4 admin will not possibly be null.

To summarize: null safety is a feature all languages should adopt, because nullability will always be there and it’s better to be well geared for it; and because null is your friend, not a mistake.

Classes are final by default

Inheritance is a powerful tool, and you know what mr Parker says: with great power comes great responsibility. Every time we use such a powerful tool we should eagerly confirm we want to do so, and this is why a good software architect will never feel annoyed at having to type an extra keyword for stating that a class may have children.

Also, final classes and methods give the compiler the ability to perform extra assertions and even smart casts, isn’t this a much greater advantage?

Then about libraries such as Spring: the solution of having to use a gradle plugin for automatically opening all necessary classes and methods may be annoying, but it is available and very easy to install. The other solution would be having to do it all by hand, which is very valid too!

Operator overloading

The drawbacks of operator overloading are clear and we have overcome them long ago in the C++ times. We are very well aware that all operators must be self-explanatory. Otherwise all potential team members/collaborators/library users will not take us seriously, or even wish us diseases.

All language features can be misused (*ehem* Java’s inheritance and raw types *ehem*). This is never going to be a good enough reason not to have a feature.

Overloaded operators are very useful for containers or all kinds of math objects, and thanks to them our code can be much clearer and concise:

And if in doubt of what an operator does, a good IDE like IntelliJ IDEA will take us to its implementation after we ctrl/cmd+click on the symbol.

“Implicit” executable code

This is yet another misconception of what Kotlin does and why it works this way, so let’s start with getters and setters.

In Java we have always been told to never have public fields; they should be private and then accessed through their corresponding getter and setter methods. The reason behind this is that we are in full control of what happens when a variable is get/set, and we could also add breakpoints there (a few years ago we couldn’t add breakpoints to fields). It is very verbose but it has its point, so that’s been the norm since basically the beginning of Java.

In Kotlin, however, we no longer need to write getters and setters because they are generated and called automatically by the compiler, so we can use the straight forward foo.bar.baz syntax. And what happens if we want to change what happens on a getter/setter? We can always alter the behavior of our fields’ getters and setters like that:

Let’s continue with primary constructors. In Kotlin we have a special syntax for them, as they go between parentheses right after the class name. Furthermore, if you add val/var to any of its arguments it will become a field of that class and be automatically set to the value passed in the constructor!

“But what if I want extra logic in my constructor?” We have the init block for that, which is a piece of code that will execute right after the primary constructor. We also have the ability to create secondary constructors, which must call the primary one with an extra piece of special syntax. Let me illustrate it:

If you run this example, notice that the init block always runs right before the secondary constructor’s! With that knowledge you should be able to have full control of what happens in your constructors as well ;)

Finally we have data classes, which will generate a bunch of very useful methods for you. We have no control over the implementation of these methods, so I’d recommend this feature to be left for use only for POJOs (or POKOs?).

Type inference

It is true that with type inference we can’t immediately tell what type a variable is. One way to sort that out is by explicitly stating the type of each variable (e.g. val x: Int = calculateX()), which is basically what we are doing in Java. But let’s face it, we love type inference and this little drawback is very well worth it. Furthermore, if you’re using IntelliJ IDEA you can always enable type hints for Kotlin or use the CTRL+SHIFT+P shortcut with the caret on the desired variable. There’s still no way to directly navigate to the type, but hopefully we will have something like that in a future.

Also, Kotlin’s type system and inference cannot be compared to Java’s as Kotlin’s much more powerful. In Kotlin every expression has a type; if/else, try/catch/finally and when (Kotlin’s switch) included. It is designed so that when an expression has branching paths the compiler will infer its type by picking its common denominator. For example:

Kotlin’s entire type system is very well done and clean. I invite you to learn further about more advanced topics such as invariant/covariant generics and give a definitive goodbye to Java’s raw types.

Free global scope in all files

So in some places we want to have more options and then when a Java restriction is taken away we will complain? The argument is contradictory.

Global scope functions are extremelly useful. Instead of creating a Utils class with a bunch of static functions we can simply create a Utils file and create the functions in its root level. And don’t be afraid, there will not be chaos: all global scope functions and variables will belong to their package as well as classes have always done, and Kotlin will not compile if there are multiple functions/variables with the same name under the same package.

The most useful way to use this feature is when prototyping something. Most of the examples I’ve shown here did not need the class ceremony and the main function can reside in the global scope without needing to be contained in a class; public static void main(String[] args)? I’d rather fun main().

We are also allowed to define a set of small classes in a single file like this:

Pretty handy for HTTP APIs’ Data transfer Object Definitions, isn’t it?

Lack of static modifier

Let’s face it, Java developers: static is bad and recommended to be used only in very specific scenarios. In the case of fields they are a good option only for constant values or private objects (such as loggers). In the case of methods they should be purely unaffected by external data and have no effect on external data either (AKA utility function). Given these are the only ways we’ll use static in a (not horrible) Java program, let’s see how it’s done in Kotlin.

Constant values

They can either reside in the affected class’ companion object or be part of the global scope. I recommend the global scope for public constants.

Private objects

The same as with constant values: we can use private for root-scope variables and they will be accessible only from that file… Or we can use the companion object. Most of the time I’ve been using the latter, but the global scope is also a very good option.

By the way, for loggers there is a very interesting library called kotlin-logging, thanks to which the logger creation can be much less verbose:

It also allows us to interpolate values in our string messages thanks to inline lambdas, so that they are easier to read in the code:

Utility functions

The global scope is again very handy for these, as stated in the previous rebuttal, and this is how it is done everywhere except in languages with the restriction that functions can only reside in classes.

Extension functions

This is my favourite feature of Kotlin. It may be just syntactic sugar, but it allows us to create utility functions that make a lot of sense. Do we have a common String encryption/decryption mechanism? Let’s implement them as fun String.encrypt() and fun String.decrypt()! Whenever we are in front of a variable we want to cipher our IDE will have a much easier time suggesting these to us after we type a ..

Missing some trivial function in a library? No problem! Extend it yourself. For example, let’s see how we could try to make sure everyone sees our critical errors:

And there’s more: we can create extension functions inside a different class, so that they can be called only from its scope. This, together with inline lambdas, delivers us a language with full DSL capability; and strictly typed!

Take a look at libraries like Ktor or TornadoFX. They are so intuitive thanks to inline lambdas and extension functions.

Variable name shadowing

It is true that at first sight this feature looks inconsistent: why is it there if the same people who created it are disregarding it??

Well, because they were forced to allow name shadowing by the following 2 reasons:

  1. They decided to make parameter names always final.
  2. They implemented a Java to Kotlin conversion tool.

And of course they decided not to give up any of both features, so the only work around that was to allow name shadowing, so that when some dirty Java method alters one of its parameters the conversor tool is allowed to create a new variable with the same name and initial value than the altered parameter.

So yes, it’s there, it’s ugly, but the reason it’s there is closer to Java than you thought! Just avoid it as much as you can.

And that’s it!

Thanks for reading! I hope this article will help you convince your team mates or even your boss in favor of Kotlin.

And keep in mind that switching over should be done very slowly, making sure everyone gets used to it and comfortable. Your best opportunities will reside in new and/or small subprojects!

--

--