DEV Community

Martin Häusler
Martin Häusler

Posted on

Kotlin - The Good, the Bad and the Ugly

Hi folks!

This is an article I've wanted to write for quite some time now. I've seen my fair share of Kotlin and also used it in production. Kotlin is a JVM-based language developed by Jetbrains, known for their IDEs and developer tools. Kotlin is very interesting from a language perspective, it has many merits, but also some pitfalls and awkward design choices. Let's get right to it!

The Good

I could fill entire books about the things I like about Kotlin. Here is a run-down of higlights.

Semicolons are optional

Not "optional" as in "well most of the time it works without them, just sometimes it makes a difference" but as in "use them or drop them, the program won't change". Less clutter in code, and my wrist is really happy about this.

NULL is a separate type

String (non-null String) and String? (nullable String) are two distinct types in Kotlin. If you stick to Kotlin methodology, your programs are unable to throw the dreaded NullPointerExceptions at you.

Extension functions

Did you ever want a null-safe variant of myStringVariable.isNullOrEmpty() in Java? Well, Kotlin has you covered! You can write static methods (with certain signatures) and call them as if they were regular member methods. This also marks the death of the dreaded StringUtils class full of static methods. All of these can be extension methods now!

Flow Typing & Type Inference

For all local variables, you just use var and the compiler will figure out the type on its own. Java 10 has that too, but with Kotlin's stronger type system it's even more valuable. Kotlin also does not require a cast operator. If you check that an object is instanceOf some class, then inside that if block, the variable will be treated as such, without you having to downcast it manually. Kotlin calls it "smart casts".

Final made easy

In Java, the final modifier is required to make a local-variable non-assignable. That's a very useful property, because it eliminates a large group of potential errors. However, final String myString is a mouth full. Hardly anybody wants to deal with this. In Kotlin, the character count for final and non-final variables is exactly the same: var myString for mutable and val myString for immutable variables.

Smart standard library

The Kotlin standard library is a treasure trove full of pleasant surprises. Did it ever bother you that there is no elegant way to use a lock in Java with try-with-resources? Well, in Kotlin you have lock.withLock{ doWork() }. It's a small thing, but it really helps. How often did you forget to free a lock in a finally clause? Or unlocked the read lock instead of the write lock? The Kotlin standard library is full of such little helpers.

Here's another one: mutableListOf<T>(T... elments). The Kotlin compiler is smart enough to figure out the T if you provide the elements. So the calls to this method will have one of two shapes:

// empty list, need to specify the type argument explicitly
val myList = mutableListOf<String>()

// pre-filled list, no need for type argument
val yourList = mutableListOf("Hello", "World"!)
Enter fullscreen mode Exit fullscreen mode

Can we stop here for a moment and appreciate how this code reads like prose in both cases? Also, Kotlin distinguishes between mutable and read-only collections, while still remaining compatible with Java. That's quite a feat in and on itself and makes Kotlin code a lot more robust against subtle bugs (hey, who modified this collection!?).

Streams, streamlined

If you want to filter a List in Java, you need to do this:

List<String> newList = oldList.stream()
    .filter( s -> s.startsWith("a") )
    .collect(Collectors.toList());
Enter fullscreen mode Exit fullscreen mode

That's quite a lot of text if all we really wanted to do was:

List<String> newList = oldList.filter(s -> s.startsWith("a"));
Enter fullscreen mode Exit fullscreen mode

Then where's the point of using stream()? By surrounding your filters (and map operations and...) with stream() and collect(...), Java streams can be lazy. However, you might argue that there is no point in lazyness if all you want to do is one filter. And you are correct, for a single filter operation, a stream is overkill, both syntactically as well as conceptionally. But in Java, List#filter(...) simply does not exist (you can use a static utility class, but come on, that's not the same thing).

Kotlin solves this problem in a very elegant way:

// single filter, quick and easy
val newList = oldList.filter { it.startsWith("a") }

// streams for multiple filters (same syntax!)
val newList = oldList.asSequence().filter { it.startsWith("a") }.toList()
Enter fullscreen mode Exit fullscreen mode

So, Kotlin gives you both stream-based lazy chains (called Sequences) as well as one-shot operations. The best thing about it is that the syntax for filter on a list is exactly the same as for a filter on a sequence. If you work with existing code and realize that you need another filter, simply insert asSequence() before and toList() after to make it lazy, and leave the existing filter in place.

Long live the Iterator

Iterators are a very basic and powerful concept. They exist in Java since version 1.0 (which says a lot). However, Iterators are treated in a rather stepmotherly fashion in Java. You can't do a for loop over an iterator in the same way as you can over a Collection. Collection#addAll accepts only other Collections as argument, not Iterables. Hell, you can't even do iterator.stream(). I never understood why.

Kotlin fixes all of the aforementioned problems. Dealing with iterators in Kotlin is just as pleasant as dealing with collections, without losing the benefits of iterators:

for(element in iterator) { ... } /* works! */

iterator.asSequence().filter { ... } /* works! */

myList.addAll(iterable) /* works! */

Enter fullscreen mode Exit fullscreen mode

Java Interop

... is really good. As in "Really REALLY good". You can mix Kotlin and Java sources (and binaries!) in the same project without issues. There are some pitfalls though; we will cover more on that later.

Many Platforms, one Language

Kotlin translates to JVM byte code, as well as JavaScript. Native implementations are also in the works. I think it goes without saying that this is a huge deal.

The Bad & The Ugly

For all its merits, Kotlin also have some downsides. They are not really breaking, but noteworthy nontheless.

No static modifier

In Kotlin, there is no static keyword. This causes some annoyance in case you just want to define that one utility method. What you have to do instead is:

class MyClass {
   companion object {
       @JvmStatic
       fun myStaticMethod(/*...*/) { /*...*/ }
   }
}

Enter fullscreen mode Exit fullscreen mode

This is ugly. In Kotlin's defense, there's const for constants (which is roughly the same as public static final members in Java), and you can declare top-level functions outside any class (which effectively makes them static). But if you really want a static method, the way outlined above is the only option (as far as I know).

The companion object was originally introduced to ease the construction of singletons. A companion object is a singleton instance of the class it resides in. I feel that this was a bad decision. Firstly because static methods are useful and should not require that much syntax, and secondly because the Singleton pattern isn't so verbose as to warrant this shorthand, and also isn't that useful to deserve such a prominent langauge feature.

The open keyword

All classes in Kotlin by default are final. Let that sink in for a bit.

class MyClass {

}

class SubClass : MyClass { /* NOPE: compile error! MyClass is final! */

}
Enter fullscreen mode Exit fullscreen mode

This is only the tip of the iceberg. Imagine frameworks such as Spring or Hibernate, which generate bytecode at runtime that extends your class. Nope, can't do that - the class is final. Within the bink of an eye, all of your Kotlin classes have suddenly become incompatible with pretty much any commonly used framework. There are two ways to circumvent the issue:

  • Either declare your class as open class MyClass or
  • Add a compiler plug-in to the Kotlin compiler.

Neither option is very pretty. The reasoning of the Kotlin language developers is that extensibility should be an opt-in thing. Only if your class is intended to be extended, it should be able to be extended. In my opinion, this reasoning is flawed: defensive programming best-practices will tell you that you should prepare your code to work in the most unexpected of environments. If you write a class that potentially breaks when it is subclassed, you wrote a bad class. Kotlin gives the utterly wrong incentive here, by simply sealing off everything.

Constructor Galore

Kotlin as a langauge has a really odd fetish for constructors. Have a look:

// primary constructor
class PersonA(val firstName: String, val lastName: String) { }

// regular constructor
class PersonB {

    val firstName: String
    val lastName: String

    constructor(firstName: String, lastName: String){
        this.firstName = firstName
        this.lastName = lastName
    }

}
Enter fullscreen mode Exit fullscreen mode

Both examples above do the exactly same thing. You may argue that the variant with the "primary constructor" is much shorter, thus better. I disagree, for the following reasons:

  • The primary constructor must always list (and assign) all fields. This is super annoying when you require an additional transient field for local caching purposes.
  • The class declaration line gets exceedingly long.
  • If you extend from a base class which has a primary constructor, you...
    • ... must declare a primary constructor yourself
    • ... must call the primary constructor of the base class
    • ... must do all of this in the class declaration

In summary, once you are locked into the primary constructor workflow, you can't break free anymore. Your class declarations quickly grow to enormous sizes. Consider this:

class Student(val firstName: String, val lastName: String, val studentId: String) : Person(firstName,lastName) {

}

Enter fullscreen mode Exit fullscreen mode

Of course, you get a lot of mileage out of such a line. It declares a subclass, declares the fields, declares the constructor, states the base class and finally states how inherited fields should be filled. That's a very high information density right there. I still don't like it. It's too much information packed together, and can be really difficult to parse for a human. I do not recommend the usage of primary constructors (except for data classes where you have no choice), but sometimes you are forced to use them because of the classes in the standard library.

Data Classes - a brilliant concept with half baked execution

Data classes in and on themselves are a brilliant idea. Have a look:

data class Person(val firstName: String, val lastName: String)
Enter fullscreen mode Exit fullscreen mode

You specify the fields of a class and their types, and you get:

  • the specified fields
  • getter/setters
  • a constructor which does exactly what you would expect
  • hashCode
  • equals
  • toString
  • clone
  • various utilities

... all for free, without writing them. That means your hashCode() will never go out of sync with your equals(). Your toString() will never forget to print a field you just recently added to the class. All of this is possible because there is no text representation for these things; the compiler just generates the bytecode directly. It's a really cool concept and vastly superior to generating all of the aforementioned things via an IDE (because the generated code can get out of sync easily).

The problem is that it wasn't thought through until the end. The main issue is that you cannot use inheritance when writing data classes. Data classes cannot extend any base class, and are final themselves (nope, you can't use open here either). The reasoning behind the decision was that things might break (in particular in the clone utilities) when you try to introduce inheritance. In my opinion, this is total rubbish. UML and Ecore have shown how to correctly have data classes with multiple inheritance and cloning utilities.

The lack of inheritance options drastically limits the applicability of data classes. Even simple Data Transfer Objects (DTOs) or JPA entities (prime examples where data classes would be ever so useful!) often require inheritance.

Abundance of Keywords

Kotlin has a very large set of Keywords and Operators. Kotlin (like C#) has the notion of "Soft Keywords", meaning that a certain word is only considered a keyword in certain contexts, but can be used as an identifier (e.g. a variable name) in all other places in the code. Some of them are quite... arcane and awfully specific, such as crossinline. Regardless, this makes the language quite difficult to learn, and even more difficult to master, as you have to know all of these keywords. In regular usage, you don't need more than you would need in Java. But if you want to read and understand the library functions, you will encounter them quite often. I personally think that a lot of these keywords would have been better expressed by annotations (e.g. @Actual instead of the modifier actual).

Verdict

I absolutely recommend any Java programmer to at least have a closer look at Kotlin. In very many areas it is a far superior language than Java. It carries over all of the Java infrastructure and libraries, and adds a lot of smart design choices on top. It has a better type system for enhanced safety and compile-time error checking. The fact alone that it is null-safe by default should be enough to justify the use of Kotlin over Java. However, it isn't perfect. Kotlin makes it easier to write code that is short and concise, but it can also be used to produce a mess of seemingly random character combinations that nobody can decipher anymore after two days. However, in spite of its shortcomings, weaknesses and the occasional strange design choice, it is an excellent language.

Do you have any experiences to share with this language? Do you use it in production, and are you happy with it? Leave a comment below.

Top comments (46)

Collapse
 
lovis profile image
Lovis

Nice post, thanks for that!

I somehow understand how you got to your "bad/ugly" points, but it feels like it's written from a Java devs point of view.

But Kotlin is a programming language on its own and not a simple java substitute!

Here's my take:

Static functions

"This causes some annoyance in case you just want to define that one utility method"

I disagree. You can have top level functions (like you mentioned) or nicely scoped extension functions. Imho, you never really want a static function to begin with. It leads to procedural, rather than object-oriented, thinking.

open vs final

"If you write a class that potentially breaks when it is subclassed, you wrote a bad class."

I really think that this is impossible. Not even the original Java Developers were able to achieve that. Consider e.g java.util.Stack, which should most definitely have been final.
There's a reason that Effective Java Item 17. is "Design and document for inheritance or else prohibit it." and is not written the other way round. Also "Favor composition over inheritance" has been advice since forever for the same reasons.

By the way, I rarely run into problems with final vs open because I usually use interfaces and rarely even consider extending non-abstract classes.
The fact that the Spring Framework chose to work with subclassing is the Framework's problem, not the language's. That's why spring boot takes care of that for you by automatically applying the all-open compiler plugin.

constructors
I don't understand what your problem with constructors is. If the problem is that it's on the same line, then put it on a different line? Otherwise Java's got the same problem - the reason is too many parameters.

Data classes
I kinda agree with your data class opinion, but want to add that it might be a bad idea to use data classes for JPA since, JPA is... special. (vladmihalcea.com/how-to-implement-...)

Additionally, the keyword vs annotation thing is discussed here (blog.jetbrains.com/kotlin/2015/08/...)

Thanks for giving you thoughts! :-)

Collapse
 
cjbrooks12 profile image
Casey Brooks • Edited

But Kotlin is a programming language on its own and not a simple java substitute!

I could not agree more. When I picked up kotlin for the first time, I saw it as simply a replacement for Java, and did a lot of the things Lombok did. The more I use it, the more I've come to realize that I really do approach solving problems in Kotlin differently (and better) than I would in Java. In fact, new libraries I write in Kotlin, I'm trying to make sure they are multiplatform, so that I can't just treat it like a cleaner Java.

Collapse
 
martinhaeusler profile image
Martin Häusler

But Kotlin is a programming language on its own and not a simple java substitute!

I disagree on this one. The Kotlin language has been advertised by its creators from the very beginning to have excellent interoperability with Java. Therefore it should also play nicely with existing libraries and frameworks from the Java universe. My point is: the Kotlin devs must have seen those issues coming from miles away, and chose to deliberately ignore them, for the sake of forcing a design choice down people's throats (closed keyword) which isn't even that good in my opinion. Feel free to disagree, but to me this a big disadvantage of Kotlin.

JPA is... special.

Indeed it is. A lot more special than I would like it to be, honestly. There's so many things you can do wrong with JPA without even realizing that you made a mistake, until it is way too late to rectify it properly. It would have been nice to have a framework where you can model your data easily and have dedicated support to ease the mapping of those data models. Data classes would have been a good fit.

Additionally, the keyword vs annotation thing is discussed here

I've yet to read the entire article (thanks for linking it), but @inline doesn't seem so bad to me. A lot better (and less scary) than a magical "sometimes I'm a keyword" inline, in fact. The ability to use annotations without a leading @ that distinguishes them from keywords makes my neck hair stand up in terror. Good thing that didn't happen.

I don't understand what your problem with constructors is.

I like my code to be structured in blocks: declare the class name, declare the superclass, declare the fields, declare the constructor. This isn't possible with primary constructors. It's all mixed up into one Gordian Knot. Also, there's the lock-in effect when using primary constructors. You can't rely on a secondary constructor in a subclass. Overall, data classes aside, I simply don't see the need for primary constructors. They just increase the mental efforts required for parsing the source code of a class. Where are my fields? They are not listed... oh, right, they might be listed in the constructor... argh I like to have things organized, and find them always in the same place. Now I have to look at least in two places until I find all the fields of a class. It's mixing what doesn't belong together.

I rarely run into problems with final vs open because I usually use interfaces

Then you are not using frameworks such as Spring or JPA. I don't say that I like dynamic bytecode generation at runtime (quite the opposite...) but it is a hard fact that those are the primary frameworks we work with on the server side. Apparently, Spring 5 got dedicated support for Kotlin, I've yet to look into that, maybe they fixed this particular issue somehow. As stated above, the Kotlin devs must have seen this coming.

Imho, you never really want a static function to begin with.

I often do, because I like to have named constructors that take different arguments, so I create factory methods (e.g. User.createFromDTO(userDTO) instead of new UserImpl(userDTO)). The nice thing about these is that they can be placed directly into the interface (Java 8+), the code which uses it doesn't even have to be aware of UserImpl. If I wanted to do the same thing in Kotlin, I need a companion object that holds the factory method. The companion object construction as such (as stated in the blog post) is something that I feel wasn't necessary to begin with, and it has become a syntactic hurdle for cases that actually matter.

Just my 5 cents, as I said: feel free to disagree. If you use Kotlin in a different environment (e.g. Android), your experiences with Kotlin may vary drastically from mine.

Collapse
 
vishnuharidas profile image
Vishnu Haridas • Edited

Data classes are very useful when declaring POJOs for processing JSON, with some extensions. For example, I can have the below setup to fetch a JSON that contains a user's data and convert it into the App's user model to remember locally in my app:

data class UserResponse(id: String, email: String, name: String){
fun asAppUser() = AppUser(name, id, email) // Convert to App's model.
val isVerified: Boolean
get() = id.endsWith("-unverified") // for example, id="37823-unverified"
}

// Somewhere in code,
val jsonString = fetchUserDataFromServer()
val user = Gson().fromJson(jsonString, UserResponse::class)

if(user.isVerified){
rememberUser(user.asAppUser())
}

Collapse
 
kayis profile image
K

"Imagine frameworks such as Spring or Hibernate, which generate bytecode at runtime that extends your class."

Heavy use of code-generation is always a red flag to me.

Collapse
 
martinhaeusler profile image
Martin Häusler

I think we have to differentiate between source code generation and byte code generation here. Both are entirely different beasts with individual pros and cons. In this case we talk only about byte code generation. At the very least, this is code you will never see, which will never go out-of-sync with your source code (because it is generated at application startup).

Overall, I would strongly prefer to work entirely without code generation in any shape or form, byte or source. But in the Java server world, the hard truth is: pretty much any notable server-side framework (JPA, Spring, Spring Data...) has to generate bytecode to achieve its goals in one way or another. You can't avoid it. And usually it doesn't matter, because you never see it happening. As long as you don't declare your classes explicitly as final (which I hardly ever see people doing), you'll be fine. With Kotlin, it is just the other way around: declare your class as open, or face inexplicable exceptions at runtime. Like I said in another comment, the Kotlin language developers must have seen this coming, I can't imagine that this came as a surprise to them.

Collapse
 
defiance profile image
Defiance Black • Edited

I have no experiences with Kotlin but it interests me as a gateway into Android apps. Is there a short story to tell regarding Kotlin + Android specifically (and would you tell it)?

It seems understandable if the integration is butter smooth with very little story at all.

Collapse
 
cjbrooks12 profile image
Casey Brooks

I've worked with Kotlin/Android a good bit, and would highly, highly recommend it. It is especially useful considering that Android has Java 8 support for syntax only, but standard libraries (like the Streaming API) are only available on much newer versions of Android and so cannot be used because of backward compatibility reasons. Kotlin lets you do all the functional .map { }, .filter { }, etc. that you want, and it still compiles down to Java 6 bytecode to work perfectly on all Android versions.

They also have the Anko, Kotlin Android Extensions, and Android KTX libraries, which are all a treasure trove of useful extensions to make the Android APIs much nicer to work with.

Collapse
 
smartym profile image
Sergey V. • Edited

I agree with you. What's important, JetBrains is constantly improving Kotlin and working on extending its capabilities. New versions are being often released, and that's great.

As a mobile app engineer, I'm happy about the appearance of such an advanced language as Kotlin. In our company, we use it more than 50% of our Android development.

Null-safety, extension-functions, coroutines, JVM backend, JavaScript official support, and many other cool features make it an effective tool for writing apps. By the way, TechBeacon included Kotlin in the list of 5 emerging languages with a bright future.

Thread Thread
 
defiance profile image
Defiance Black

Is it a fair assumption that the rest of the 50% is essentially HTML/CSS/JS? While I do webdev, the appdev side of things is still cloudy; I'm unsure if there'd be anything else required.

Your comment makes me better appreciate my PyCharm subscription. When the time comes, it's all a few clicks away.

Collapse
 
defiance profile image
Defiance Black

Those links are all new to me; butter smooth Android integration indeed. Thank you, drive-by story-teller.

Collapse
 
bill1550 profile image
Bill Hart

I've been developing for Android now for the last 18 months exclusively with Kotlin. It is much more productive and safer than Java and is fully supported by Android. When working on older projects I find it is usually better to convert whatever classes I'm working to Kotlin. In particular the explicit and concise handling of nullable types is a major quality improvement.

Collapse
 
martinhaeusler profile image
Martin Häusler

I'm experiencing similar things on the server side. As I wrote in the article: null-safety alone should justify looking into Kotlin for every Java developer out there. It is a game-changer.

Collapse
 
vishnuharidas profile image
Vishnu Haridas

I strongly recommend Kotlin+Android. I am amazed with the better, functional style syntax, Extension Functions, Coroutines, Null-safety, and many other features. Threading and background processing is much easy with Coroutines.

Kotlinx - Kotlin Extensions is a collection of functions. For example, if you have a button in a layout XML named btnAccept, you don't need to do findViewById(R.id.btnAccept). Instead, you can directly use the name btnAccept inside your activity.

// example
btnAccept.setOnClickListener { callAcceptApi() }

Here's a nice list, #31DaysofKotlin, a full month of tips and tricks: twitter.com/i/moments/980488782406...

Collapse
 
martinhaeusler profile image
Martin Häusler

Sorry, I've got no experiences with android so far. But considering that Kotlin is an officially supported language on Android, you shouldn't face too many issues. I'm using kotlin on the server side, and it works like a charm alongside our existing Java code.

Collapse
 
defiance profile image
Defiance Black

Heard that.

I'll come back to this top-down view of Kotlin to refresh my mind on its concepts. You should have seen my jaw when I read about inheritance (Python has spoiled me). It's also a great mini-syntax tutorial.

Thank you for writing this thing!

Collapse
 
jagdeepsfo profile image
Jagdeep Sandhu

The kotlin drawbacks are not drawbacks if you want to use the functional programming paradigm. OO doesn't make sense in all domains, so classes and inheritance sometimes add to noise of the program. Kotlin data class is replacement for Python tuple. Since the JVM is not getting the investment that it used to get 20 years ago, I would really like to compile Kotlin to Native. In the age of docker container running a JVM makes little sense, it just adds another unnecessary dependency.

Collapse
 
martinhaeusler profile image
Martin Häusler • Edited

The kotlin drawbacks are not drawbacks if you want to use the functional programming paradigm. OO doesn't make sense in all domains, so classes and inheritance sometimes add to noise of the program.

Sure, you can do that. But then I would argue that you should go for Haskell or F#, because even if Kotlin has a lot of functional aspects to it, if you really want to go for it, then you might as well go all the way. The merit of Kotlin IMHO is that it supports both paradigms. It just falls short a little on both. For example, on the functional side Kotlin doesn't support Currying or partial application (without additional syntactical tricks). On the OO side, it doesn't support inheritance on data classes.

Kotlin data class is replacement for Python tuple.

I don't think of them as replacements for tuples, and honestly I've never heard th is argument before. They were called data classes by the Kotlin devs for a reason. They can have methods. They basically do everything a regular class does and more, except that they, for some weird reason which I really fail to see, can't use inheritance. Data classes would be a lot more useful if they could inherit at least from one other data class.

In the age of docker container running a JVM makes little sense, it just adds another unnecessary dependency.

I think that this point of view is a little bit on th extreme side. The JVM still gives you a lot of benefits, such as a garbage collector, all the profiling tools (JVisualVM etc.) and in general just a great infrastructure. Languages like Go are catching up, but slowly (different discussion entirely). I would welcome the possibility to compile my kotlin code to a native executable, or even to Go code.

Collapse
 
yuriykulikov profile image
Yuriy Kulikov

I would disagree about data classes and inheritance. There is absolutely no need in inheritance, especially with data classes.

Collapse
 
martinhaeusler profile image
Martin Häusler

This may depend on your domain. If you want to use data classes for Data Transfer Objects (which would make perfect sense because they are just data containers, or "structs" if you prefer this term), then inheritance is ordinary day-to-day business (at least in the domains I work with). The only alternative to the use of inheritance would be to duplicate fields into every leaf class, and remove the base classes. This makes the design brittle, because not only are you duplicating information (DRY principle), you also run into the issue that you might forget to add a field in one of your leaf classes in the future. I strongly believe that inheritance is necessary, and this is the reason why I don't use data classes for DTOs, as much as I would like to do so.

Collapse
 
cjbrooks12 profile image
Casey Brooks

Kotlin allows you to define properties in interfaces, and you can still implement interfaces in a data class. This frees you from the brittle design of duplicated properties (at least, in remembering to put them all in), and also solves the issue of wanting to use a data class as multiple types. I've tried this pattern out in my Dokka-to-JSON formatter and it works quite well, and is very robust.

I do not think the choice of data classes being final was an oversight. I think it was made very intentionally, in order to get you truly using data classes as pure data composed of other data. They are not in the same "class" of objects as normal objects, they are useful primarily for serialization, and its generally a good design to not have your code interact directly with a data class any way. You should abstract your code away from the structure of the serialized data into a format that works better with the business logic.

Thread Thread
 
martinhaeusler profile image
Martin Häusler

You should abstract your code away from the structure of the serialized data into a format that works better with the business logic.

Totally in agreement here. That's why a quick way for building serialization formats is a great benefit.

Kotlin allows you to define properties in interfaces, and you can still implement interfaces in a data class.

I must admit that I didn't think of this option yet. It would allow you to do all the inheritance you want on the interface level, and instantiate only the leaf classes. It also enforces the correct properties and types. Okay, granted, that seems reasonable. It does entail though that you need to write the interfaces and the data classes, and in a lot of cases there will be only one implementation for each interface (DTOs for example). Still, thanks for the heads-up. I'll consider this in the future.

Thread Thread
 
yuriykulikov profile image
Yuriy Kulikov

You can, of course, implement interfaces in data classes, but, in my opinion, compisition will always be a better way to do it. Want to avoid property duplication in your DTOs/value objects/whatever? Move the duplicated part to a new data class and use it as a property in another class. I have yet to see a use of inheritence which can be justified. Normally it is just a mess. Steer clear of it :-)

Collapse
 
cjbrooks12 profile image
Casey Brooks

About primary constructors, you definitely don't have to list all the properties in the class. Just the ones you want initialized in a constructor. I will agree that the syntax is a bit quirky, but overall I've found them to be quite helpful. I had adopted a pattern in java of always chaining overloaded constructors to a single, "root" constructor, passing the default values along the way, to ensure all objects are initialized the same way. Kotlin just enforces this pattern.

I also prefer a different syntax for declaring primary constructors, which makes it easier to read.

@ClassAnnotation
class ClassName
@PrimaryConstructorAnnotation
constructor(
        val1: String,
        val val2: Int,
        private val val3: Double
) : SuperClass(val1) {
...
}

This format makes it very clear which annotations go where (and I use @Inject on nearly all of my classes, so is quite necessary for me to have the constructor keyword even in the primary one), and also keeps properties each on their own lines, just like when declared in the class body.

Collapse
 
martinhaeusler profile image
Martin Häusler

Thanks for sharing your experience. This way of formatting improves the situation a little. However, for me it's still counter-intuitive. When I open up the source code of a class file, there are three things that I am primarily interested in:

  1. what kind of class is it? (regular class, interface, enum...)
  2. what's the name of the class?
  3. what is the base class, and what are the interfaces?

While the "Kotlin way" satisfies 1 and 2, it fails on 3. Look where the base class is located. Somewhere way down there. The very concept of declaring fields (which belong to the class) within a constructor is appalling to me. A constructor is a member of a class. Why should it contribute to the structure of the class (in terms of fields in this case)? I realize that this is not really the case, but the syntax makes it look as if that was happening. Maybe it's just because I've written so much Java and C#.

Collapse
 
mgroovy profile image
mgroovy

From what you say Groovy might be a language you should have a look at: Kotlin takes many of its Java improvements from Groovy, but Groovy avoids some imho strange syntax and design decisions, staying as close as possible to Java syntax and never unecessarily restricting the programmer.

Many mistake Groovy for a "script language", but as someone who was written a large framework (Vaadin/Ebean/Oracle/Tomcat technology stack) in it over the last years, I can safely vouch this not to be true: Groovy can be used as a dynamic as well as statically typed language (@CompileStatic/@CompileDynamic annotation on class or method), and comes with a multitude of powerful annotations (@Immutable/@Canonical/@ToString/@AutoFinal/etc) that make for very concise but readable programming. It has flow typing, closures (in addition to Java lambdas), a large extension library that enhances existing Java classes, a huge ecosystem - and is probably the most Java compatible language besides Java itself G-)

(For more details see arscreat.com/wprs/goltrta/ and on newer features groovy-lang.org/releasenotes/groov...)

Collapse
 
martinhaeusler profile image
Martin Häusler

We use groovy at my company too - but not for application development, but as an embedded scripting environment that allows the user to extend the application at runtime. It does a very good job at that.

If you remove all the meta-programming from Groovy and restrict yourself to the @CompileStatic subset, Groovy is a fairly decent language. My pet peeve with Groovy is that the language itself is far larger than the "safe" subset. I imagine it would be difficult to enforce the usage of this subset across the entire project and across all developers. Also, people are tempted to bring in external libraries, and those might make use of language features which do not conform to @CompileStatic. Basically, @CompileStatic divides the groovy world in two halves; and the 50% that does not use @CompileStatic I'd rather not deal with, at all, ever.

I've seen groovy both at its best (scripting with @CompileStatic) and at its worst (ever seen the mess that is gradle?). From all JVM aliens, I would personally rank Groovy second (behind Kotlin) for general purpose use, but ranked first for runtime scripting.

Collapse
 
mgroovy profile image
mgroovy

Hi Martin, could you elaborate on the "the language itself is far larger than the safe subset" and "remove all the meta-programming from Groovy" ? I don't quite get what you mean: Do you write your own AST transformations in Groovy in your project, or do you mean the built in AST transformations that come with the language (e.g. @Immutable) ?

Collapse
 
mgroovy profile image
mgroovy

In what areas is Kotlin better than Groovy in your opinion (apart from always being @CompileStatic ;-) ) ?

I have not used much Gradle myself, so I cannot really comment on it, I only know it is in widespread use. But so is make, of course ;-)

You might have noticed that our technology stack (which I as lead picked) is very @CompileStatic (Vaadin/Ebean), and does not e.g. use Grails or GORM. Having said that, I started using Groovy when @CompileStatic did not exist (and someone had just released a static Groovy compiler (groovypp), and approach that was rejected by the rest of the Groovy devs in favor of the more flexible @CompileStatic approach, which led to same guy later designing Kotlin), but due to the excellent IntelliJ* Intellisense support for Groovy I never really missed static compilation (probably a different story if you use Groovy as an embedded scripting language without Intellisense support, though).

(Only in the last 2 years have I started to shift larger portions of our code to use @CompileStatic , since IntelliJ sometimes makes refactoring errors on dynamic Groovy code. Also static compilation is helpful for less experienced developers and/or in areas where there is no time to have 100% test coverage.)

*Ironically the company behind IntelliJ, Jetbrains, of course also makes Kotlin. Alas it looks like the conflict of interests has started to show recently, when Groovy features were only supported in IntelliJ a year after they had been introduced. Besides Java, Groovy as the #2 JVM language is the main competitor of Kotlin. I would rather see the two sides join forces tbh, but that's not likely to happen...

Thread Thread
 
martinhaeusler profile image
Martin Häusler

Sorry for the delayed answer - busy times.

Kotlin enforces its type system everywhere. Every kotlin library out there adheres to it. The same cannot be said about Groovys @CompileStatic. Things like the JSON Slurper simply won't work under static compilation. Basically all the metaprogramming features contradict @CompileStatic.

I know, there are valid use cases for metaprogramming. But for general application development, I'd rather forfeit metaprogramming and get meaningful compilation results. In a way, it's nice that Groovy offers the choice, but it backfires when you need to include libraries which made a different choice than you did for your code. I don't want to deep-dive into a feature comparison of Kotlin vs. Groovy, but this fact alone (at least for me) would justify choosing Kotlin over Groovy for general application development. If you want an example of the extensive problems an optional type system can cause, you don't have to look any further than the current JavaScript/TypeScript mess. It's metaprogramming (JavaScript with monkeypatching) vs. well-defined strict type system (TypeScript) all over again. I'd rather stick to a language which doesn't even ask that question.

Collapse
 
newcolours profile image
NewColours

I'm in a minority here, but for me Kotlin use is a huge red flag.

The weakest developers use it to work around understand basic SOLID code and without understanding what it's going to generate.

The advantages you list like NPE safety are only truly advantageous in the hands of seasoned engineers. The rest of the time they are the reason weak devs recommend Kotlin to eachother in stackoverflow comments - to avoid learning to produce safe, efficient, quality code and to avoid learning concepts like nullability, inheritance and encapsulation. (The way they would previously have recommended static everything as a terrible solution)

Collapse
 
edwardvanraak profile image
Edward van Raak

So if a bridge would collapse you would blame the newly invented materials instead of the faulty design conceived by inexperienced engineers, who did not fully understand said materials?

Slavish devotion to a few general principles and the worship of acronyms is pretty stupid as well.

Collapse
 
rmirabelle profile image
Robert Mirabelle

Nicely written. I totally agree with your assessments.

I'm frustrated almost daily by Kotlin's EXTREMELY awkward constructor logic, and it gets worse when you factor in usage of the init block, which will be required if you actually need to do any work in the constructor. Primary, secondary, init...it's all a bit ridiculous. For the life of me, I cannot fathom what problem the Kotlin devs thought needed to be solved, but I feel confident stating this wasn't the right way to solve it. It feels like someone was showing off: "Look how much logic we can stuff into a single undecipherable line!" Brevity should follow clarity, not the other way around.

Data classes can't participate in inheritance, yet no one can ever infer that restriction. Instead, every Kotlin dev will be forced to discover it once their code fails to compile. Perhaps naming the class FinalSealedDataClass would have been helpful, but allowing inheritance would be better.

And possibly my least favorite language construct of all time (second only to Android's AsyncTask) is this: inline fun<reified T>. Dear lord, I had to google what the word reified even means. It basically means to 'make an abstract thing real', and while technically accurate, is a mightily poor cue for understanding that you're instructing the compiler to translate the generic T into a concrete type at every call site at runtime. And while we're at it, I think Kotlin should reify its own damn T's and inline generic functions as needed without me having to tell it to do so ;-)

And finally, coroutines - designed to make working with threads easier - and all that's required to use them is a complete understanding of absolutely everything about concurrency. Here again, I think the Kotlin devs have conflated simplicity with brevity.

These not-insignificant complaints aside, Kotlin is actually rather fabulous.

Collapse
 
leoat12 profile image
Leonardo Teteo

When I heard about Kotlin for the first time I heard that it was a language full of features that solved many of Java problems: verbosity, NPE, etc. People also said that it was a smooth learning curve for any Java developer, I thought it was nice and since the learning curve was smooth, maybe I could do what I liked to do more: jump into a language with examples and all that. It was not a very good experience...
It was some months ago, so some things may have changed and my memory can fail, but the first example I jump into was Spring Boot, since I use it every day in Java and it is already concise and easy to use, so even more concise would just make me love it more, right?
I created the project and started to write, reading the documentation when needed, but when I finished writing the Hello World example... Errors appeared...
More recent examples don't have this anymore, but at that time it was necessary to use a companion object to start the Spring Boot application, it was hard to find out that, and then there is lateinit for autowired properties, compatibility between Java libraries and Kotlin which made the use of my favorite libraries very troublesome. I remember a had a lot of trouble trying to figure out how to use Jackson. lol
Yeah, it was not smooth at all... I'm returning now since there are examples of projects in Kotlin in the book I'm reading. I think I will give it another chance.

Collapse
 
tbroyer profile image
Thomas Broyer

Wrt data classes, having inheritance would break the Liskov Substitution Principle wrt equals(), making things break as soon as you use such objects in sets or as map keys.

There's some discussion about this in errorprone.info/bugpattern/EqualsG...

If all you want is a DTO and don't really care about equals et al (or all the things you get for free with a data class, i.e. destructuring assignment and copy()), then just use a class, not a data class:

class Person(
  val firstName: String,
  val lastName: String
)

I'd say the biggest problem of data classes is that they're too often abused.

See also jakewharton.com/public-api-challen...

Collapse
 
androiddeveloperlb profile image
AndroidDeveloperLB • Edited

Some comments:
"Semicolons are optional" - in some cases they actually made it a must. Not sure why.

"NULL is a separate type" - this is good, but it doesn't protect you from a very nasty bug, that when you need to convert from Java to Kotlin, and the tool of the IDE doesn't really use the correct type. It causes a different exception...
The conversion tool is very dangerous. Need to handle it carefully.

"Flow Typing & Type Inference" - actually, this isn't always true. Many times, Kotlin insists that I tell it the type of the variable, even though it's clear what it is. For example, I can't use this:

data class Foo(val tt = 123) {
}

"Smart standard library" - About the sample code, this actually made me confused in the past, as "List" isn't quite the same on Java and Kotlin. And I don't think we really need MutableList.

"Streams, streamlined" - it is shorter, but it can be much less readable than normal code sometimes. I think it depends on the case. I wonder if it has advantage in terms of performance.

"No static modifier" - not only that, but only one "companion object" allowed, and if you call from Java, you might need to call ".Instance" unless you add the @JvmStatic annotation for each static function (because the converter doesn't add it for you). But, good news is that if you have a class that all of it is static stuff, you use "object".

"The open keyword" - So true. Really hate to have it this way. Ruins the entire openness of Android development. I've heard though that it's possible to overcome it somehow (without modifying the code of the final-class). Do you know how?

"Constructor Galore" - I actually like it. Your choice though. And when you need more, you change to a different way of writing it.

"Data Classes" - didn't know they are final. Didn't use them much though. Good to know. Made a request about this:
youtrack.jetbrains.com/issue/KT-27063

Collapse
 
martinhaeusler profile image
Martin Häusler

First of all, thanks for all the input!

"Semicolons are optional" - in some cases they actually made it a must. Not sure why.

The only case that I have ever encountered in Kotlin where a semicolon was actually required is when you have multiple statements on the same line of source code. Consider this:

// semicolon is actually required here
val x = 3; val y = 5

... however, you can always reformat your code to be one per statement:

// semicolon be gone!
val x = 3
val y = 5

I never encountered any other scenarios where the compiler wanted me to insert a semicolon. And honestly, having more than one statement per line is rather ugly and unnecessary.

The conversion tool is very dangerous. Need to handle it carefully.

I didn't want to open this pandora's box in the article, it's an entirely different discussion. But I agree with you: the tool, while conceptually nice, is indeed not without flaws. The tool is nice when you are learning Kotlin: you can write some Java code, throw the tool at it, and discover how it would look like in Kotlin. However, I would never trust it when it comes to production code.

Many times, Kotlin insists that I tell it the type of the variable, even though it's clear what it is

What you are trying to do in your code is to declare a field with a default value, and expect the compiler to infer the field type from that. While I do agree that this is fine for local variables, I would argue that the same logic does not necessarily apply to fields, because they have a much broader scope (and a longer lifetime). So if you say (in a constructor argument) val x = 3, then do you really want x to be an Int? Or maybe a Double? The compiler can't tell. Again, for local variables this is fine: if you discover that the type was too narrow (i.e. you actually wanted Double, not Int) then you simply go back and adapt your initial value assignment accordingly.

By the way, likewhise the Kotlin compiler doesn't provide type inference for function return values, even though there might be only one path through the control flow of the function which always returns a value of a certain type. But hey, let's be real here - it is still a big step upwards from what Java can do at the moment (Java is slowly catching up in this area though).

And I don't think we really need MutableList.

This is debatable of course. I personally really like it, because it saves me both the trouble and the performance loss for returning Collections.unmodifiablelist(myList) instead of just myList. It makes it clear to everybody which collections are safe to be modified, and which ones should be treated as read-only. I can see though that it is annoying to write Mutable all over the place. Then again, immutable data structures do have a lot of appeal, so maybe we should not use too many mutable collections to begin with.

But, good news is that if you have a class that all of it is static stuff, you use "object"

Oh okay, thanks for the heads up, didn't know that. I agree with you that the companion object was not the best idea they've ever had.

I've heard though that it's possible to overcome it somehow (without modifying the code of the final-class). Do you know how?

As it happens, I do :-) Fair warning: dangerous terrain ahead. You can actually write plug-ins to the Kotlin compiler (whether or not you should do that in practice is another discussion entirely). One such plugin is the All Open plugin, which is primarily required for using Kotlin together with the Spring framework (here's an article that highlights why this is so very much necessary), but the plugin works independently from any library or framework. What it does is that it basically implicitly declares all Kotlin classes as open. This takes us back to the way Java did it. However, note that it does not work on all classes. Data Classes, Sealed Classes etc. will still be final. Also note that with this plugin activated you have no way of declaring your classes explicitly as final anymore. Furthermore, I can't confirm how well the tooling (IDE) plays with such compiler plug-ins.

"Data Classes" - didn't know they are final. Didn't use them much though. Good to know. Made a request about this.

I appreciate the valiant effort, but I'm afraid you will not be greeted with a warm welcome for this request. I've seen more than one discussion thread online about this topic, and the Kotlin devs are quite stubborn about this decision. They claim that it is not possible to have inheritance for data classes because of technical reasons within the generated clone(...) utilities. As I stated in the article, I don't buy that argument. I sense plain old lazyness here.

Collapse
 
androiddeveloperlb profile image
AndroidDeveloperLB

About semicolons, incorrect. It is also required in enum classes (which I don't get why can't we just use "enum" instead of "enum class", BTW) that have extra code and not just values :

enum class SomeEnum{
    VALUE1,VALUE2;

    companion object {
        fun foo(valueInt: Int): Int = 123
    }
}

"...do you really want x to be an Int? Or maybe a Double? The compiler can't tell. "
Of course it can tell. It does it to fields (properties) and variables. What difference does it make if I put it directly in the CTOR...

About MutableList, I mostly just hate that they made it confusing. "List" should have been the same on Kotlin as on Java. Now it means a different thing. On Java you can do whatever you wish with it. On Kotlin you can't.

About making Kotlin open of classes, I mean something else. It was asked somewhere in Google (probably some Google IO lecture), and I think they said that if you insist, you can use Java to override Kotlin, but I didn't understand how.

Really on many (and probably most) things Kotlin did nice things, but on some I just ask myself "why did they do this? This is way worse than on Java...".

Collapse
 
kgtgit profile image
KgTgIT • Edited

You can use @JvmOverloads for constructors not to overload them by urself. But this is useful only for Spock and nullable fields.

As for data classes, you can use interface with a field. Then simply add override on field in data class. This may be useful for some code generalisation.

Collapse
 
krishisangaran profile image
krishi sangaran

Thanks for sharing this information. Nicely written. I totally agree with your assessments. I really like your blog post very much. Here I have drafted step by step tutorial on How to Develop Android Chat App Using Kotlin. It should be helpful for further process.
blog.mirrorfly.com/build-android-c...

Collapse
 
smartym profile image
Sergey V.

A great post about Kotlin!