Published on

Using Kotlin in Your Library

Authors

Today I was working on a library that is used by other apps in our environment. I intentionally avoided using Kotlin in this JAR to avoid forcing apps to interact with Kotlin if they did not want to.

When discussing this with a colleague he did raise a very good point which is that although the source code is Kotlin the compiled JAR will consist of the compiled Java versions of the Kotlin code.

The main thing to watch out for is ensuring you use the all open plugin if you want to use frameworks like Spring to add functionality to beans or explicitly declare the class as open. The reason this plugin is needed in these situations is because Kotlin classes are final by default which means the Java code it compiles to is also final.

Another thing that may not be immediately obvious is how Kotlin primitives like Int can be interoperable with Java. The answer is that they compile down to Java primitives where it makes sense. JetBrains has made interoperability between Kotlin and Java a key part of their design philosophy. A way to confirm this is to write you Kotlin code as in the below example:

package scratch


data class Person(val name: String, val surname: String, val age: Int, val height: Number)

Then if you are using IntelliJ/Android Studio to get the Java code associated with the Kotlin code you would:

  1. Recompile the Kotlin code (Ctrl/Cmd-Shift-F9)
  2. Then push shift 3 times and type byte
  3. Choose the option that says Show Kotlin Byte Code
  4. This will pop up the JVM byte code for your Kotlin class
  5. Finally click Decompile which will output the equivalent Java code
package scratch;

import kotlin.Metadata;
import kotlin.jvm.internal.Intrinsics;
import org.jetbrains.annotations.NotNull;

@Metadata(
   mv = {1, 1, 9},
   bv = {1, 0, 2},
   k = 1,
   d1 = {"\u0000(\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0000\n\u0002\u0010\u000e\n\u0002\b\u0002\n\u0002\u0010\b\n\u0000\n\u0002\u0010\u0004\n\u0002\b\u000e\n\u0002\u0010\u000b\n\u0002\b\u0004\b\u0086\b\u0018\u00002\u00020\u0001B%\u0012\u0006\u0010\u0002\u001a\u00020\u0003\u0012\u0006\u0010\u0004\u001a\u00020\u0003\u0012\u0006\u0010\u0005\u001a\u00020\u0006\u0012\u0006\u0010\u0007\u001a\u00020\b¢\u0006\u0002\u0010\tJ\t\u0010\u0011\u001a\u00020\u0003HÆ\u0003J\t\u0010\u0012\u001a\u00020\u0003HÆ\u0003J\t\u0010\u0013\u001a\u00020\u0006HÆ\u0003J\t\u0010\u0014\u001a\u00020\bHÆ\u0003J1\u0010\u0015\u001a\u00020\u00002\b\b\u0002\u0010\u0002\u001a\u00020\u00032\b\b\u0002\u0010\u0004\u001a\u00020\u00032\b\b\u0002\u0010\u0005\u001a\u00020\u00062\b\b\u0002\u0010\u0007\u001a\u00020\bHÆ\u0001J\u0013\u0010\u0016\u001a\u00020\u00172\b\u0010\u0018\u001a\u0004\u0018\u00010\u0001HÖ\u0003J\t\u0010\u0019\u001a\u00020\u0006HÖ\u0001J\t\u0010\u001a\u001a\u00020\u0003HÖ\u0001R\u0011\u0010\u0005\u001a\u00020\u0006¢\u0006\b\n\u0000\u001a\u0004\b\n\u0010\u000bR\u0011\u0010\u0007\u001a\u00020\b¢\u0006\b\n\u0000\u001a\u0004\b\f\u0010\rR\u0011\u0010\u0002\u001a\u00020\u0003¢\u0006\b\n\u0000\u001a\u0004\b\u000e\u0010\u000fR\u0011\u0010\u0004\u001a\u00020\u0003¢\u0006\b\n\u0000\u001a\u0004\b\u0010\u0010\u000f¨\u0006\u001b"},
   d2 = {"Lscratch/Person;", "", "name", "", "surname", "age", "", "height", "", "(Ljava/lang/String;Ljava/lang/String;ILjava/lang/Number;)V", "getAge", "()I", "getHeight", "()Ljava/lang/Number;", "getName", "()Ljava/lang/String;", "getSurname", "component1", "component2", "component3", "component4", "copy", "equals", "", "other", "hashCode", "toString", "production sources for module kotlin-koans_main"}
)
public final class Person {
   @NotNull
   private final String name;
   @NotNull
   private final String surname;
   private final int age;
   @NotNull
   private final Number height;

   @NotNull
   public final String getName() {
      return this.name;
   }

   @NotNull
   public final String getSurname() {
      return this.surname;
   }

   public final int getAge() {
      return this.age;
   }

   @NotNull
   public final Number getHeight() {
      return this.height;
   }

   public Person(@NotNull String name, @NotNull String surname, int age, @NotNull Number height) {
      Intrinsics.checkParameterIsNotNull(name, "name");
      Intrinsics.checkParameterIsNotNull(surname, "surname");
      Intrinsics.checkParameterIsNotNull(height, "height");
      super();
      this.name = name;
      this.surname = surname;
      this.age = age;
      this.height = height;
   }

   @NotNull
   public final String component1() {
      return this.name;
   }

   @NotNull
   public final String component2() {
      return this.surname;
   }

   public final int component3() {
      return this.age;
   }

   @NotNull
   public final Number component4() {
      return this.height;
   }

   @NotNull
   public final Person copy(@NotNull String name, @NotNull String surname, int age, @NotNull Number height) {
      Intrinsics.checkParameterIsNotNull(name, "name");
      Intrinsics.checkParameterIsNotNull(surname, "surname");
      Intrinsics.checkParameterIsNotNull(height, "height");
      return new Person(name, surname, age, height);
   }

   // $FF: synthetic method
   // $FF: bridge method
   @NotNull
   public static Person copy$default(Person var0, String var1, String var2, int var3, Number var4, int var5, Object var6) {
      if ((var5 & 1) != 0) {
         var1 = var0.name;
      }

      if ((var5 & 2) != 0) {
         var2 = var0.surname;
      }

      if ((var5 & 4) != 0) {
         var3 = var0.age;
      }

      if ((var5 & 8) != 0) {
         var4 = var0.height;
      }

      return var0.copy(var1, var2, var3, var4);
   }

   public String toString() {
      return "Person(name=" + this.name + ", surname=" + this.surname + ", age=" + this.age + ", height=" + this.height + ")";
   }

   public int hashCode() {
      return (((this.name != null ? this.name.hashCode() : 0) * 31 + (this.surname != null ? this.surname.hashCode() : 0)) * 31 + this.age) * 31 + (this.height != null ? this.height.hashCode() : 0);
   }

   public boolean equals(Object var1) {
      if (this != var1) {
         if (var1 instanceof Person) {
            Person var2 = (Person)var1;
            if (Intrinsics.areEqual(this.name, var2.name) && Intrinsics.areEqual(this.surname, var2.surname) && this.age == var2.age && Intrinsics.areEqual(this.height, var2.height)) {
               return true;
            }
         }

         return false;
      } else {
         return true;
      }
   }
}