Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Teach Kotlin To Play Well With Java - Tips for ...

Teach Kotlin To Play Well With Java - Tips for Interoperability

Ever since Google made the announcement, Kotlin has seen a great reception from the developer community. Many of the apps have moved to Kotlin already, some have forgotten Java but some are still stuck in the Java land!

Kotlin is here to stay and those who are stuck with Java do not mean to ignore Kotlin. It is just that they do not have the time and support to experiment yet another language because of deadlines.

When they work with a Kotlin enthusiast or use a library developed in Kotlin, sometimes they hit roadblocks. Because of how Kotlin compiler helps with interoperability, Java developers get to consume the code in Kotlin but in an inefficient manner.

This talk aims to provide some small tips and tricks to the Kotlin developers which will help them work well with Java developers and make their lives easier until their transition to Kotlin is complete.

Talk given at Droidcon Vienna and Droidcon SF 2018

Avatar for Vivek Chanddru

Vivek Chanddru

November 20, 2018
Tweet

More Decks by Vivek Chanddru

Other Decks in Technology

Transcript

  1. Why is there so much hype? • Unveiled on July,

    2011 by JetBrains and released Feb, 2016 • Received First Class support at Google I/O 2017 • A much better Java • Provides support similar to other modern languages • A boon for Java programmers writing verbose code
  2. Kotlin Adoption • Very soon, Kotlin is going to overtake

    Java (For Android) • Libraries are popping up with Kotlin support • New libraries are developed using Kotlin • Sample apps and code are in Kotlin • Medium is filled with wonders of Kotlin
  3. Problems in Adopting Kotlin • Have to learn an all

    new language • Team members not willing to learn a new language • Company is not willing to spend time and money on R&D • Fear of breaking what is already working fine • Hard to find Kotlin experts to get the work done
  4. Most of it is not their fault! A lot of

    factors come into play that stops their adoption
  5. What goes on in a Java developer's mind? • The

    world is moving on leaving them behind • See a new blog? It is in Kotlin • An awesome library that they want? It is developed and optimized for Kotlin • Left with the default implementation of Kotlin interoperability support
  6. How Kotlin helps with interoperability • Kotlin works out of

    box with Java (mostly) • Java classes and methods can be used directly from Kotlin • Kotlin classes and functions can be used from Java • With very little effort, you can make Java developers’ life much easier and write better looking code
  7. Visibility Modifiers - Kotlin • Private fields in Kotlin stay

    private in Java • Private top-level declarations turns out to be package-local declarations • Protected members in Java are accessible to other classes in same package • Internal members become public after some name-mangling • Public stay public
  8. fun calculateAge(dob: Date): Int { //some magic happens here return

    42 } void runAgeDemo(Date date) { AmazingUtilsKt
 .calculateAge(date); } Kotlin - AmazingUtils.kt Java
  9. Utils.calculateAge(date); void runAgeDemo(Date date) { } @file:JvmName("Utils") fun calculateAge(dob: Date):

    Int { //some magic happens here return 42 } Kotlin - AmazingUtils.kt Java
  10. @file:JvmName(“Utils") @file:JvmMultifileClass fun calculateAge(dob: Date): Int { //some magic happens

    here return 42 } @file:JvmName("Utils")
 @file:JvmMultifileClass fun isEligibleToVote(age: Int): Boolean { //some magic happens here return age >= 18 }
 A.kt B.kt
  11. Does not show the receiver type. Might want function name

    to give more context fun String?.isNullorEmpty(): Boolean { return this != null && this.isEmpty() } Kotlin void nullOrEmptyDemo(String s) { boolean nullorEmpty = Utils.isNullorEmpty(s); } Java
  12. @file:JvmName("Utils")
 @JvmName("isStringNullorEmpty") fun String?.isNullorEmpty(): Boolean { return this != null

    && this.isEmpty() } Gives more context on what the method is and what it does. Kotlin void nullOrEmptyDemo(String s) { boolean nullorEmpty = Utils.isStringNullorEmpty(s); } Java
  13. Kotlin Does not compile - both methods have same signature

    
 filterValid(Ljava/util/List;)Ljava/util/List; fun List<String>.filterValid(isValid: (String) -> Boolean): List<String> { ... } fun List<Int>.filterValid(isValid: (Int) -> Boolean): List<Int> { ... }
  14. fun List<Int>.filterValid(isValid: (Int) -> Boolean): List<Int> { ... } Kotlin

    fun List<String>.filterValid(isValid: (String) -> Boolean): List<String> { ... } @JvmName(“filterValidString") Compiles fine! Works great with Kotlin as well as Java
  15. var name: String? = null @JvmName("getPersonName") get() { return field?.toLowerCase()

    } set(value) { field = value?.toUpperCase() } var age: Int? = null class Person { } Kotlin
  16. var name: String? = null @JvmName("getPersonName") get() { return field?.toLowerCase()

    } set(value) { field = value?.toUpperCase() } var age: Int? = null class Person { } Kotlin
  17. class Person { var name: String? = null @JvmName("getPersonName") get()

    { return field?.toLowerCase() } set(value) { field = value?.toUpperCase() } @get:JvmName("getpersonAge") @set:JvmName("setPersonAge") var age: Int? = null
 } Kotlin
  18. void personDemo(Person person) { person.getPersonName(); person.getpersonAge(); person.setName("Vivek"); } class Person

    { var name: String? = null @JvmName("getPersonName") get() { return field?.toLowerCase() } set(value) { field = value?.toUpperCase() } @get:JvmName("getpersonAge") @set:JvmName("setPersonAge") var age: Int? = null
 } Java Kotlin
  19. void companionDemo() { AmazingClass
 .Companion
 .simpleUtilFunction(); } class AmazingClass {

    companion object { fun simpleUtilFunction() { //Do some magic here } } } Kotlin Java
  20. void companionDemo() { AmazingClass.simpleUtilFunction(); } class AmazingClass { companion object

    { fun simpleUtilFunction() { //Do some magic here } } } @JvmStatic Kotlin Java
  21. Accessing Properties by Name • There are three scenarios where

    the properties of a class can be accessed directly without getters and setters from Java • When the property is a `const` • When the property is a `lateinit` • When the property is annotated with `@JvmField`
  22. class AmazingClass { companion object { const val BASE_URL_GOOGLE 


    = "www.google.com" val BASE_URL = “sf.droidcon.com" } } Kotlin
  23. void companionDemo() { system.out.println(AmazingClass.BASE_URL_GOOGLE);
 system.out.println(AmazingClass.Companion.getBASE_URL() );
 } class AmazingClass {

    companion object { const val BASE_URL_GOOGLE 
 = "www.google.com" val BASE_URL = “sf.droidcon.com" } } Kotlin Java
  24. void companionDemo() { system.out.println(AmazingClass.BASE_URL_GOOGLE);
 system.out.println(AmazingClass.Companion.getBASE_URL() );
 } class AmazingClass {

    companion object { const val BASE_URL_GOOGLE 
 = "www.google.com" val BASE_URL = “sf.droidcon.com" } } Kotlin Java
  25. class AmazingClass { companion object { const val BASE_URL_GOOGLE 


    = "www.google.com" 
 val BASE_URL = “sf.droidcon.com" } } @JvmField Kotlin
  26. void companionDemo() { system.out.println(AmazingClass.BASE_URL_GOOGLE);
 system.out.println(AmazingClass.BASE_URL); );
 } class AmazingClass {

    companion object { const val BASE_URL_GOOGLE 
 = "www.google.com" 
 val BASE_URL = “sf.droidcon.com" } } @JvmField Kotlin Java
  27. Java
 void propertyAccessDemo() { LazyClass lazyClass = new LazyClass(); lazyClass.mandatoryVariable

    = "abcd"; lazyClass.setMandatoryVariable("vivek"); } 
 class LazyClass {
 lateinit var mandatoryVariable: String 
 var optionalVariable: String? = null
 } Kotlin
  28. Java
 void propertyAccessDemo() { LazyClass lazyClass = new LazyClass(); lazyClass.optionalVariable

    = "hello"; //not available } 
 class LazyClass {
 lateinit var mandatoryVariable: String 
 var optionalVariable: String? = null
 } Kotlin
  29. fun() with defaults • Default parameters in Kotlin is great

    • They do not work directly when called from Java • @JvmOverloads annotation is required to make it work • Can be used with constructors, static methods etc • Not applicable to abstracts and methods in interfaces
  30. fun increaseValue( givenValue: Int,
 increaseBy: Int = 1): Int {

    return givenValue + increaseBy } Kotlin void increaseValueDemo() { DemoKt.increaseValue(60,1); } Java
  31. fun increaseValue( givenValue: Int,
 increaseBy: Int = 1): Int {

    return givenValue + increaseBy } Kotlin void increaseValueDemo() { DemoKt.increaseValue(60,1); } Java DemoKt.increaseValue(60); This is an error. No methods match such signature
  32. fun increaseValue( givenValue: Int,
 increaseBy: Int = 1): Int {

    return givenValue + increaseBy } Kotlin @JvmOverloads
  33. fun increaseValue( givenValue: Int,
 increaseBy: Int = 1): Int {

    return givenValue + increaseBy } Kotlin @JvmOverloads
  34. fun increaseValue( givenValue: Int,
 increaseBy: Int = 1): Int {

    return givenValue + increaseBy } Kotlin @JvmOverloads void intelligentMethodDemo() { DemoKt.increaseValue(60,5); DemoKt.increaseValue(60); } Java
  35. Handle with care • Kotlin does not have checked exceptions

    • Their JVM Signature does not contain the `throws` statement • Hence if the kotlin method throws some exception, and if we try to catch it as checked exception, Java gets bitter and grumbles • Use `@Throws` to calm Java down and ask it to handle the checked exception • Beware of API methods that might get called from Kotlin as they might throw some checked exception that Kotlin silently ignores
  36. fun exceptionalMethod() { throw IOException(“You ought to be careful, mortal”)

    } Kotlin void exceptionalDemo() { try { DemoKt.exceptionalMethod(); } catch(IOException e) { e.printStackTrace(); } } Java Shows a compile time error that IOException is never thrown in the try block
  37. fun exceptionalMethod() { throw IOException(“You ought to be careful, mortal”)

    } Kotlin @Throws(IOException::class) Now, Java would accept that IOException is indeed thrown by the method and accepts the catch block
  38. fun writeBytes(bytes: ByteArray) { val file = File(“file_path") val fos

    = FileOutputStream(file) fos.write(bytes) fos.close() } Kotlin This line is going to throw 
 an exception
  39. fun writeBytes(bytes: ByteArray) { val file = File(“file_path") val fos

    = FileOutputStream(file) fos.write(bytes) fos.close() } Kotlin void exceptionDemo() { byte[] bytes = new byte[]; DemoKt.writeBytes(bytes); } Java Does not throw any compile time errors. Silently crashes at Runtime
  40. fun writeBytes(bytes: ByteArray) { val file = File(“file_path") val fos

    = FileOutputStream(file) fos.write(bytes) fos.close() } Kotlin @Throws(IOException::class) Document the exceptions that might be thrown
  41. fun writeBytes(bytes: ByteArray) { val file = File(“file_path") val fos

    = FileOutputStream(file) fos.write(bytes) fos.close() } Kotlin @Throws(IOException::class) void exceptionDemo() { byte[] bytes = new byte[]; DemoKt.writeBytes(bytes); } Java Shows a compile time error and provides an option to surround with try catch block
  42. The pitfalls of Function Types • Fan of higher order

    functions and lambdas? • Kotlin higher-order functions are a very nice feature to use but should be used carefully. • If your function type returns `Unit`, then, when used from Java, it has to return `Unit.INSTANCE` which is unidiomatic • Alternatives are to use a SAM (Single Abstract Method) interface in Java or Kotlin • This issue will soon be fixed in one of the Kotlin releases and until then, you can use Function Types bearing in mind the inconveniences caused to Java consumers
  43. fun lambdaExample(block: (Int) -> Unit) { //do some magic block(3)

    } Kotlin DemoKt.lambdaExample(integer -> { system.out.println(integer); return Unit.INSTANCE; }); DemoKt.lambdaExample(integer -> { system.out.println(integer); return null; }); Java
  44. interface Callback { fun doSomething(value: Int) } fun lambdaExampleInterface( callback:

    Callback) { callback.doSomething(3) } Kotlin We have a SAM interface written in Kotlin
  45. Java
 void lambdaDemo() { DemoKt.lambdaExampleInterface( integer -> { system.out.println(integer); });

    } interface Callback { fun doSomething(value: Int) } fun lambdaExampleInterface( callback: Callback) { callback.doSomething(3) } Kotlin
  46. interface Callback { fun doSomething(value: Int) } fun lambdaExampleInterface( callback:

    Callback) { callback.doSomething(3) } Kotlin Kotlin
 lambdaExampleInterface(object : Callback { override fun doSomething(value: Int) { //do something here } })
  47. Java
 interface Callback { void doSomething(int value) } Kotlin
 fun

    lambdaExampleInterface( callback: Callback) { callback.doSomething(3) } We have a SAM interface written in Java
  48. Java
 interface Callback { void doSomething(int value) } Kotlin
 fun

    lambdaExampleInterface( callback: Callback) { callback.doSomething(3) } Java
 void lambdaDemo() { DemoKt.lambdaExampleInterface( integer -> { system.out.println(integer); }); }
  49. Java
 interface Callback { void doSomething(int value) } Kotlin
 fun

    lambdaExampleInterface( callback: Callback) { callback.doSomething(3) } Kotlin
 lamdaExampleInterface(Callback { it -> //do something here })
  50. Collections - Handle with Care • In Kotlin, collections are

    by default immutable (list, set, map etc) • Meaning, you cannot add or modify values in the collection • If a mutable version is required, the request should be explicit to create a mutable version of the collection • When exposing immutable collections through public APIs, Java consumers can receive them and make changes to it, causing inconsistency
  51. fun getPersonList(): List<Person> { val personList: List<Person> = fetchPersonList() return

    personList } Kotlin void collectionDemo() { DemoKt.getPersonList().add( new Person("Vivek",30)); } Java
  52. fun getCurrentPersonList(): List<Person> { val personList = getPersonList() val currentPersonList

    = mutableListOf<Person>() Collections.copy( currentPersonList ,personList) return currentPersonList } Kotlin
  53. fun getCurrentPersonList(): List<Person> { val personList = getPersonList() val currentPersonList

    = mutableListOf<Person>() Collections.copy( currentPersonList ,personList) return currentPersonList } Kotlin void collectionDemo() { DemoKt.getCurrentPersonList().add( new Person("Vivek",30)); } Java
  54. Working with coroutines • async { … } blocks are

    used in Kotlin to run something in a coroutine asynchronously • await() can be used on Deferred<T> to wait for async block to return a response • async { … } can be replaced with future { … } to make the method return CompletableFuture used in Java8 • Extension function is also available to convert Deferred to CompletableFuture
  55. fun callApiMethodAsync(delay: Int = 1000): Deferred<String> { return async {

    delay(delay) println("Returning response from Async with $delay") return@async "Response $delay" } } Kotlin launch(UI) { println("Loading - Through Async”) combine(callApiMethodAsync(1000).await(),
 callApiMethodAsync(5000).await()) } Kotlin - Calling function
  56. fun callApiMethodFuture(delay: Int = 1000): CompletableFuture<String> { return future {

    delay(delay) println("Returning response from Future with $delay") return@future "Response $delay" } } Kotlin launch(UI) { println("Loading - Through CompletableFuture") combine(callApiMethodFuture(1000).await(),
 callApiMethodFuture(5000).await()) } Kotlin - Calling function
  57. fun callApiMethod(delay: Int = 1000): CompletableFuture<String> { return future {

    delay(delay) println("Returning response from Future with $delay") return@future "Response $delay" } } Kotlin Java - Calling function CompletableFuture<String> stringCompletableFuture1 = NetworkUtil.callApiMethod(1000); CompletableFuture<String> stringCompletableFuture = NetworkUtil.callApiMethod(5000); stringCompletableFuture1.thenAccept(res1 -> { stringCompletableFuture.thenAccept(res2 -> { NetworkUtil.combine(res1, res2); System.out.println(res1 + " " + res2); }); });