It feels like magic… But is it really? https://trends.google.com/trends/explore?q=%2Fm%2F0_lcrx4 What you’re looking at is Google Trends, where I’ve looked up “ ”. Can you see that sudden spike? That’s when , at its Google I/O conference that took place a couple of weeks ago.By now, either you were already using it in the past, or you have been diving your face into the language because everyone is suddenly talking about it. kotlin Google announced that the Kotlin programming language is now a first-class citizen in Android One of the most prominent features of the language is the with : this means that you can call Kotlin code from Java in the same way that you can call Java code from Kotlin. This is (and has been) probably the most important peculiarity that pushed the adoption forward. You do not need to migrate everything at once: you can simply take a piece of your existing code base and start adding Kotlin code, and just like that, it’ll work. You can with Kotlin, and if you don’t like what you see, you can always go back (even though I dare you to do so). interoperability Java experiment When I first started using Kotlin at , coming from 5 years of Java, some things felt just like magic. Clue “Wait, what? I can simply write to avoid boilerplate?” [data class](https://kotlinlang.org/docs/reference/data-classes.html) “Wait, so if I write then I no longer need to specify the object every time I want to invoke a method on it?” apply After the initial sigh of relief for finally having a language that doesn’t feel old and cumbersome, I started feeling a little uncomfortable. If interoperability with Java is a requirement, how exactly does Kotlin implement these nice language features? What’s the catch? This is what this article is about. I was super curious about knowing how the Kotlin compiler translates certain constructs so that they can interoperate with Java, and I chose to take a look at the four most used methods of the : Kotlin Standard Library apply with let run After reading this, you should not feel scared anymore. I feel way more confident now that I know how things work, and I know I can the language and the compiler. trust Apply is pretty simple: it is an extension function that executes the parameter on the instance of the extended type (called “receiver”) and returns the receiver itself. There are many use cases where this function comes in hand. You may bind the creation of an object to its initial configuration, like so: apply block val layout = LayoutStyle().apply { orientation = VERTICAL } As you can see, we are providing a configuration for our new right at the creation site, which contributes to a and to a . Has it ever happened to you to call methods on the wrong instance, just because of a similar name? Or worse, a refactoring gone horribly wrong? With this approach it is definitely harder to fall into these pits.Also, note how we do not have to specify the parameter: because we are , it’s as if we were extending that very class, thus is implicit. LayoutStyle cleaner code much less error prone implementation this in the same scope as the class itself this But how does that work? Let’s take a look at a brief example. Consider this simple snippet: Thanks to IntelliJ IDEA’s “Show Kotlin bytecode” tool ( ), we can inspect how the compiler translates our code into JVM bytecode: Tools > Kotlin > Show Kotlin Bytecode If you’re not too familiar with bytecode, I suggest you to read that will give you a much clearer idea (in this case, one important thing to keep in mind is that every method invocation pops the stack, so the compiler needs to load the object every time). these great articles Let’s break this down: Create a new instance of and duplicate it on the stack LayoutStyle Call the constructor with zero parameters Do a bunch of store/load (more on that later) Push the value to the stack Orientation.VERTICAL Invoke , which pops the object and the value from the stack setOrientation We notice a couple of things here. First of all, there is no magic behind the scene, it all happens as you would expect: the method is called on the instance that we have created. In addition, the function is nowhere to be seen, because the compiler was instructed to . setOrientation LayoutStyle apply it inline And most of all, the bytecode is almost identical to the one that is generated if you do the same thing in Java! Just see for yourself: Pro Tip: you may have noticed a lot of ASTORE/ALOAD operations. Those are inserted by the Kotlin compiler so that the debugger works also for lambdas! We’re going to elaborate this in the last section of the article. With may look similar to , but denotes some prominent differences. First of all, is not an extension function over a type: the receiver has to be explicitly passed as a parameter. Moreover, returns the result of the function, whereas returns the receiver itself. with apply with with block apply Since we have the freedom to return whatever we please, something like this is totally plausible: val layout = with(contextWrapper) {// `this` is the contextWrapperLayoutStyle(context, attrs).apply { orientation = VERTICAL }} In this example we can omit the prefix for and because is the receiver of the function. Even though the use cases are far less obvious than what you can think for , this function can become really useful in particular circumstances. contextWrapper. context attrs contextWrapper with apply With that in mind, let’s go back to our example and see what happens if we use : with The receiver for is a singleton called , which contains an orientation parameter that we would like our layout to have. Inside the function we create the instance and, thanks to , we are simply able to set the orientation with the one we read from the . with SharedState block LayoutStyle apply SharedState Now let’s look again at the generated bytecode: Again, there is really nothing special here. The singleton, which is implemented as a static field on the class, is retrieved; the instance is created just like before, there’s a call to the constructor, another invocation to get the value for inside and one last invocation to assign it to our instance. SharedState LayoutStyle previousOrientation SharedState LayoutStyle Pro Tip: when using “Show Kotlin Bytecode”, you can also press “Decompile” to see a Java representation of the bytecode produced by the Kotlin compiler. Spoiler alert: it’s exactly what you would expect! Let is very useful when you’re dealing with nullable objects. Instead of chaining endless if-else statements, you can simply combine the operator (called “safe call operator”) with : what you end up with is a lambda where the argument is a not-nullable version of the original object. let ? let it val layout = ()SharedState.previousOrientation?. layout.orientation = LayoutStyle let { it } Let’s see the entire example: Now that is nullable, if we tried to assign that directly to our layout, the compiler would complain because a nullable type cannot be assigned to a non-nullable type. Of course we could write an if statement, but that would mean referencing the value twice: by using instead, we get a not-nullable reference to the same parameter, which can safely be assigned to our layout. previousOrientation SharedState.previousOrientation let From a bytecode perspective, it’s very straightforward: It all resorts to a simple conditional jump , which is essentially what you would have done by hand, except this time the compiler does that efficiently for you and the language offers you a nice way of writing that code. I think this is awesome! IFNULL Run There are two versions of run, one is a simple function and the other is an extension function over a generic type. Since the former does nothing more than calling the block function that is passed as a parameter, we’re going to focus the analysis on the latter. is probably the simplest function among the ones that we have met so far. It is defined as an extension function over a type, whose instance is then passed as the receiver, and returns the result of executing the function. You might think that is somehow a hybrid between and , and you would be right, the only difference being the return value: in the case of we return the receiver itself, in the case of we return the result of the function (just like we do on ). run block run let apply apply run block let So the following example highlights the fact that returns the result of the function, so in this case an assignment ( ): run block Unit The bytecode equivalent is then: We can see that has been inlined, just like the other functions, and everything resolves to just plain method invocations. Nothing weird to see here as well! run We have noticed that there are a lot of similarities between the Standard Library functions: this is done on purpose, so to have you covered for as many use cases as possible. On the other hand, figuring out what functions suit you best for a particular task is not so immediate, given the slight variations between each one of them.To help you navigate the Standard Library, here’s a handy table that sums up the differences between the main functions that we’ve covered (with the exception of ): [also](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/also.html) Huge thanks to for sharing this with me! Eugenio Appendix: the extra operations store/load Before we close the curtains on this analysis, there was still something that I couldn’t really understand when comparing the “Java bytecode” and the “Kotlin bytecode”. More specifically, as I have already mentioned before, there were some extra couples of operations coming from Kotlin that Java was missing. I knew it had something to do with lambdas, but I couldn’t really figure out what their use was. astore/aload Turns out that those extra operations are necessary for the debugger to and, in turn, to allow us to step into them. In this way we can see what the local variables are, who the caller of the lambda is, who will be called from within the lambda, and so on. treat lambdas as stack frames However, when we ship an APK to production we don’t really care about debugger features, do we? So we can think of those instructions, even if small and negligible, as overhead that should and could be removed. , the tool we all know and “love”, could be the right tool for the job. It operates at bytecode level and, among other things like obfuscation and shrinking, it also does optimization passes to try and slim down the bytecode. So I tried to write the same piece of code in both Java and Kotlin, apply ProGuard with the same set of rules to both of them, and compare the results. Here’s what I found. ProGuard ProGuard configuration Source code Bytecode After comparing the two bytecode listings we can observe the following: The extra operations from the “Kotlin bytecode” are gone, because ProGuard figured they were superfluous and promptly removed them (interestingly enough, a single optimization pass didn’t do the trick, two were required) astore/aload The “Java bytecode” and the “Kotlin bytecode” are almost identical; the former has some interesting/weird of dealing with the enum value, whereas in Kotlin there is no such thing Conclusion It’s great to have a new language that offers so many more possibilities to developers, but it is also important to know that we can rely on the tools that we use and that we feel confident in using them. I’m glad that I can say “I can trust Kotlin”, in the sense that I know that the compiler is not doing anything exuberant or risky: it’s simply doing what we would have to do by hand in Java, saving us time and resources (and restoring some of the long lost fun in the activity of coding for the JVM). It also benefits the end users to some extent, because thanks to a much stronger type safety, hopefully we can ship less bugs in our apps. In addition, the Kotlin compiler is constantly being improved, therefore the code it outputs keeps getting better and more efficient. With that said, we should not try to optimize our Kotlin code based on the compiler, but rather stick to the best (as in, efficient and idiomatic) Kotlin code we can write and leave all the rest to the compiler itself.