From Java to Kotlin and back again - May 2018

I thought this may be sharing here, my apologies if this has already been posted and discussed, if so, please link to the discussion.

Thank you.

Thanks for sharing, however I disagree with the article.

To list the difference between LocalDate::class.java and LocalDate.java is kind of ridiculous. It could be even better then the Java example with a little Kotlin Wrapper:

GsonBuilder().registerTypeAdapter<LocalDate>(LocalDateAdapter()).create()

This is only a little exentension function away.

To criticise reversed type declaration is questionable, too. I think it is mainly a matter of taste or habit. I find it much more readable to have the name first followed by the type. That is more important in parameter lists or variable declaration, and it fits much better with type inference. Even one of the Java leaders (I cannot remember, but I think James Gosling or Brian Goetz) said that he would probably use this syntax today.

Companion objects is one more thing you need little bit time to get used to, but then it is much more elegant and more consistent compared to static members. Why didn’t he mention all the strange happening with statics in Java? He is just used to the Java way.

To give this Java examaple

public int parseAndInc(String number) {
    return Optional.ofNullable(number)
                   .map(Integer::parseInt)
                   .map(it -> it + 1)
                   .orElse(0);
}

and then complain about this Kotlin counterpart is a bit strange:

fun parseAndInc(number: String?): Int {
    return number?.let { Integer.parseInt(it) }
                 ?.let { it -> it + 1 } ?: 0
}

The author writes that this wouldn’t be simple. It can actually be pretty simple:

fun parseAndInc(number: String?): Int = number?.let { Integer.parseInt(it) + 1 } ?: 0

And now, compare again with his Java example.

It’s really useful when implementing simple DTOs. But remember, Data classes come with the serious limitation — they are final. You cannot extend a Data class or make it abstract. So probably, you won’t use them in a core domain model.

Oh, what?! I use data classes in my domain models every day! There is a reason why data classes are final! It sounds a bit, like they tried to use data classes everywhere.

The he criticises that classes are not open by default. But may he should have read “Effective Java” … Josh Bloch suggests “Design and document for inheritance or else prohibit it.”. And, again, there are good reasons to do so. final classes are the default in C#, too, by the way.

I cannot confirm that Kotlin has a steep learning curve. Maybe I’m not the benchmark because I knew Scala when I was beginning with Kotlin, but I’ve seen colleagues adopt it in days to weeks.

All in all it looks like a Kotlin beginner has given up before really knowing the language and get used to some things that are not like in Java.

2 Likes

I actually agree with some points in this article. Companion objects are more complicated than they need to be for new users and IMO just having static methods would be fine. But then again, static methods are already part of kotlin as top level functions.
Yes, if you use APIs that need a java-class-reference the kotlin syntax for it looks worse then the java one.
Compile time null safety has some problems, but they are not the ones described in the article. The main problem is that you sometimes have “linked” properties, e.g if this “a is X then b and c must not be null”. This leads to sometimes ugly looking checks, without real meaning (!! is used a lot in this case, up to a point where it’s meaningless).

Yes, interop between a kotlin function which cares about null-safety and a java function which does not is not trivial. It requires the programmer to tell kotlin about the nullability. That said, I like kotlins approach there. It gives flexibility without trowing away the power of kotlins system.
The article complains, that null can still travel through the entire program, due to platform types (T!), which is not true. Sure, as long as you only use platform types, they can be null. But as soon as you try to assign a platform type to a non-null type kotlin adds a null-check and since it’s impossible to declare platform types in kotlin (they can only be inferred from java calls) this is not really an issue.
In the last 2 years of using kotlin I never had a problem with this, not even once.
The biggest offense of platform types is that they can be used in public/protected properties and functions. IMO this should at least be a warning and not just an IDE inspection.

This brings me to name shadowing. I agree with the article in that maybe kotlin goes a bit too far here. But the claim that this is only a warning in IDEA and not a compiler warning is just wrong. Ok I know that many people out there just ignore all compiler warnings, but that’s their fault and not kotlins.

I’m not going to repeat the criticism @medium already listed. I totally agree with his/her points. Although I might add that I don’t think kotlin is hard to learn for a programmer that knows java. Yes, it’s a new language and has a different syntax. There are a few features (the way inline functions work, companion objects) and because of the null-safety people have to learn to think a bit differently when designing classes, but this is not different to learning any new language. I concede however that someone completely new to functional programming might take a bit longer (but then so was I).

In conclusion I don’t think this article gives any good reasons, why you should not use kotlin. It does however have a one or two good points about areas kotlin might still improve. But then, I could probably list 10 about java without thinking much about it.

3 Likes

The main reason why I don’t use Kotlin more is because it’s not as stable as Java. A lot of the stuff like error handling or overload resolution isn’t defined in the docs, and is either already different between different Kotlin variants (JVM/JS/Native) or is going to change between versions. IMO it’s not really acceptable if "Hello"[-1] suddenly returns an invisible 0-char instead of previously throwing an exception for example.

I also agree that companion objects don’t feel like they are the best design possible, to say it nicely, especially the requirement that you need a dummy companion object to create type/“static” extension methods is very limited IMO. Otherwise they just make Java interop more difficult and usually don’t add anything of value that regular Java-like static methods inside a class wouldn’t also give you. I’ve practically never seen the need to pass a companion object around as an object or let it implement an interface.

I’ve seen more than once strange things with statics going on. Something like this, where a static method is called via an instance:

class Main {
  public static void main(String[] args) {
    A b = new B();
    b.sayHello();
  }

  static class A {
    static void sayHello() {
      System.out.println("Hello from A");
    }
  }

  static class B extends A {
    static void sayHello() {
      System.out.println("Hello from B");
    }
  }
}

This prints “Hello from A”. This is obviously also a strange example, but I’ve seen similar things in projects. It is a sign that static members and OOP are not the best friends. The same in Kotlin wouldn’t even compile:

fun main(args: Array<String>) {
    val b: A = B()
    b.sayHello() // compiler error
}

open class A {
    companion object {
        fun sayHello() {
            println("Hello from A")
        }
    }
}

open class B : A() {
    companion object {
        fun sayHello() {
            println("Hello from B")
        }
    }
}

That code woud’ve given you big red warning in Intellij IDEA and other IDEs. Sure, it shouln’t have been allowed even in Java to access static members on an instance, but that’s IMO a relatively minor problem. Just don’t do that. The Kotlin problems aren’t that easily avoidable.

As someone that has the pleasure of teaching programming (in Java) to students I have to say that from that perspective static confuses the hell out of students. The notion that a field/method does not exist in the instance but does exist on a global level is rather confusing (they struggle with object lifetime/sharing/references/etc. anyway). A companion object is much clearer in that sense, and if you need a function unrelated to an object. That belongs at top-level.

7 Likes

In conclusion I don’t think this article gives any good reasons, why you should not use kotlin. It does however have a one or two good points about areas kotlin might still improve. But then, I could probably list 10 about java without thinking much about it.

The difference is that Java is the incumbent/default/natural choice for JVM, and Kotlin is an upstart. Anything that’s harder in Kotlin than Java is going to send people back to Java, even if overall Kotlin remains a better choice.

It seems inevitable that there will be hurdles in consuming Java code from Kotlin that don’t exist in pure Java, despite all the excellent work that’s been put into interoperability. If you need to pass around Java types a lot, then yes, appending .java to your classes does become a bit of a nuisance, perhaps enough to resort to Java.

1 Like

I agree with static being confusing. I don’t think companion objects are the right solution, though. Another related think I don’t really like in Kotlin relatively to Java is that in Java I can write small private helper methods right next to the normal (often public) methods. These are often static because they don’t need to access object data. In Kotlin I can either not make them static, which feels wrong, or I can put them outside of the class, which leads to much scrolling or file switching when the static helper method belongs to 1 or 2 normal methods and should be grouped with them. Or I can put it into the companion object which feels wrong and also leads to scrolling. (Or I think you can nest methods in Kotlin, but that creates it’s own problems and is rather unusual.)

I’m new to Kotlin and the best way to get over the “name is separated from type” problem for me is it to write “name :type”.
No space between “type” and “:”. This also helps me with the wrong order (lets face it, it’s wrong :smiley: ) problem because if it starts with a “:” it’s a type no matter of the order, problem solved. Why don’t I use “name: type” instead? Well first in this case I would see “name:” and “name” in the code even if both are the same thing, but they shouldn’t look different. And second, because a class can “extend” multiple things so if Im not wrong the syntax is:
“class Something :Foo :Bar{}”
You see? “Something:” wouldn’t work because of the Bar, you shouldn’t put the “:” of “Bar” to the end of “Foo”. But I’m not even sure if this syntax is correct because I didn’t wrote that many classes till now.

Edit:
The name shadowing thing shocked me, I gave Kotlin a try because I expected it to prevent me more from writing buged code.

2 Likes

Java is awesome - it has pretty simple syntax. It’s readable, it can be written compact, it predictable, it builds very fast compared to kotlin…

Kotlin is awesome - it has tons of sugar to minify your routine, you can use it like script language instead of python for your personal everyday analitic needs, it’s more functional oriented, it can be used for dsl, it can be readable just like java and even better, it has “light threads” form-the-box…

Both come with powerful jdk tools, giant comunity and billions of open source libs.

Anytime i listen stories like “we moved from kotlin back to java because…” or “we moved from java to kotlin because…” i do not find them convincing enough. Especially with following description of crutches and bicycles intended to make new language do things the same way as old language did.

In my team i prevented initiatives of my colleagues to move to kotlin during a year. It’s not because i didn’t like kotlin. I just wasn’t sure i could write such simple code as i did in java. One day i feel myself prepared and i gave up. As a result now i’m pretty busy with refactoring tons of very confusing code from most initiative of my colleagues. But it’s not language design issue. It’s brain design issue. Brain has large inertness. For example one guy in my team inspired of inheritance and just can’t write code in a composition way. He read tons of literature about architecture, he better than me in some aspects, but… when he just need to write something reusable - his code is turning in inheritance hell. That’s it. Brain inertness.

And what about language design issues? We work with tools - languages, sdk’s, frameworks. Any tools has issues. Our code has issues. We have issues. Finally the result must contain as little issues as it’s possible. We do work for the money btw. Even if i don’t like some feature implementation it can be the best in current situation for many other reasons. If i say “i’m engineer” it means i must to offer bunch of solutions and voice their costs (time, maintainability, scalability). Than i sit down and just write a code, not cry about it’s ugliness or complexity, just try to do my best. So thanks cthulhu i’m not a php programmer.

2 Likes

He’s recommending Groovy, which I think is really all you need to know about that blog post.

8 Likes

I use local (“nested”) functions a lot. I never experienced any problems with them. They are unusual only to those who haven’t used them so far, for example by working in a language that doesn’t support them.

They are in fact much more convenient, and relate the intent much better, than private static in Java. You get the additional benefit of local variable capture, which means you don’t have to redundantly pass around parameters.

1 Like
fun parseAndInc(number: String?): Int {
    return number.let { Integer.parseInt(it) }
                 .let { it -> it + 1 } ?: 0
}

This example is a strawman. It uses a null-unsafe Java call Integer.parseInt instead of the idiomatic it.toInt(), which would immediately flag the inappropriate usage of .let instead of ?.let.

Unfortunately, Kotlin’s let doesn’t work that way. It’s just called on everything from the left, including nulls.

It may seem unfortunate when demonstrated on platform calls, but otherwise it’s nothing but an advantage.

3 Likes

It’s quite unfair to say Kotlin is bad just because some frameworks are designed to exploit the bad sides of Java. This is essentially

1 Like

Some of the concerns in the article are valid, but most of them aren’t problems that cannot be fixed by backward compatible changes or IDE warnings, so I think it’s insufficient to argue about moving away.

1 Like