Calling Kotlin from Java – Interoperability

Things to consider when calling Kotlin from Java.

import academy.learnprogramming.kotlincode.*;

import static academy.learnprogramming.kotlincode.CarKt.topLevel;
import static academy.learnprogramming.kotlincode.StaticCar.anotherTopLevel;

public class Main {

    public static void main(String[] args) {
  // So how do we call the top-level function from the Car.kt file?
        topLevel(); // If we type the function name the IDE suggests an import (see above). Compiler will quietly generate static class for all top-level items and will give the class the same name as the Kotlin file name.
        // So if we want to refer to a class we use the file name:
        CarKt.topLevel();
        //To demonstrate how we can deal with naming conflicts:
//        anotherTopLevel(); // Without using JvmName to rename file IDE cannot locate this function
        anotherTopLevel(); //Once JvmName has been implemented the IDE will suggest an import under the 'StaticCar' file name
        CarKt.print("Print this Java string"); // We cannot invoke extension function like we would in Kotlin. Need to use standard call
        Car car = new Car("blue", "BMW", 2011);
        System.out.println(car.getModel()); // We call getters and setters from Java as standard as compiler has produced these for Kotlin class properties by default (val only generates getters). Wouldn't work if properties private
        Car2 car2 = new Car2("red", "Ferrari", 2017, false);
        car2.setColour("purple"); // If custom sets and gets are created Java will access those
//        car2.setModel("A3"); // But not if setter is private
//        car2.setYear(1999); // Doesn't exist as setters only generated for var not val
        System.out.println(car2.getColour());
        System.out.println(car2.isAutomatic()); // When dealing with Booleans generates getter name beginning with 'is'
        // If we want Java to access the property directly without going through getter or setter, we can add an @JvmField annotation to the property:
        int year = car2.year; // Can't use @JvmField with private properties or one that overrides another property or can be overridden, or one with const keyword
        // How do we access functions within companion objects? Same for non-anonymous object instances (instances we create using the object keyword)?
        // Must use @JvmStatic annotation. Results in two version being created. One in instance as normal, but also one as a Java static function.
        Car2.Companion.carComp(); // Without @JvmStatic we're accessing the instance method generated by the compiler, not the static version. So we can't type Car2.carComp()
        Car2.carComp2(); // With the @JvmStatic annotation. Means we're accessing the static version of the method.
        // This could be necessary if carComp() was accessing static properties
        // For named object instances i.e. Singletons:
        SingletonObj.INSTANCE.doSomething(); // We can go through the INSTANCE object to access the functions within. Compiler creates an instance and assigns INSTANCE to it
        SingletonObj2.doSomething2(); // If we annotate the function with @JvmStatic we can access it directly
        // Fields, if they're not private:
        System.out.println("is 4WD = " + Car2.Companion.is4WD()); // Without @JvmField
        System.out.println("is pimped out = " + Car2.isPimpedOut); // With @JvmField
        // If the field is a const you don't need to annotate it - automatically converted to static field:
        System.out.println("a constant = " + Car2.constant);
        // Also true for late initialised properties (whatever they are)

        // Null safety
        // Nothing stopping Java passing a null object to a nonNullable function parameter - Kotlin will generate an exception:
//        car2.printMe(null);
        // Kotlin doesn't require functions to declare what exceptions they can throw, whereas Java does.  What happens when we call an exception-able Kotlin function from Java?
//        CarKt.doIO(); // Throws IOException. Can we catch it?
//        try {
//            CarKt.doIO();
//        } catch (IOException e) { // Error - 'IOException is never thrown' - Java can't see the potential exception
//
//        }
        try {
            CarKt.doIO2();
        } catch (IOException e) { // @Throws annotation solves the problem
            System.out.println("IOException!");
        }
        // What happens when we've defined default parameters in a Kotlin function?
        CarKt.defaultArgs("The number is: ", 40);
//        CarKt.defaultArgs("The number is: "); // Error even though second argument should be optional
        // When a Kotlin function with default values is called, only one version is generated for Java (requires all parameters). Annotate with @JvmOverloads:
        CarKt.defaultArgs2("The number is: "); // All better
    }
}

 

fun topLevel() = println("I'm in the car file")

fun main() {
    "Print this".print() // Calling the extension function from within the Kotlin class, as standard
}

object SingletonObj {
    fun doSomething() = println("I'm doing something in the Singleton object")
}

object SingletonObj2 {
    @JvmStatic fun doSomething2() = println("I'm doing something in the second Singleton object")
}

fun doIO() {
    throw IOException()
}

@Throws(IOException::class)
fun doIO2() {
    throw IOException()
}

fun defaultArgs(str: String, num: Int = 25) {}

@JvmOverloads fun defaultArgs2(str: String, num: Int = 25) {}

class Car(val colour: String, var model: String, val year: Int) {

}

class Car2(colour: String, model: String, @JvmField val year: Int, val isAutomatic: Boolean) { // year property has been designated for direct access through Java

    companion object {
        const val constant = 25
        val is4WD = false
        @JvmField val isPimpedOut = true
        fun carComp() = println("I'm in car's companion object")
        @JvmStatic fun carComp2() = println("I'm also in car's companion object")
    }

    fun printMe(text: String) {
        println("I don't expect a null value: $text")
    }

    var colour: String = colour
    set(value) { // example custom setter which always produces same output
        field = "always green"
    }
    var model: String = model
    private set(value) {
        field = "Focus"
    }
}

fun String.print() { // Extension function
    println(this)
}

 

Kotlin Try Catch

Using the Try/Catch statement in Kotlin.

fun main() {
    println(getNumber("22")) // returns 22
    println(getNumber("22.5")) // returns 0
    println(getNumber2("33") ?: "I can't print the result") // Use null return to determine output
    println(getNumber2("33.5") ?: IllegalArgumentException("Number isn't an Int")) // Use null return to throw a different Exception

    notImplementedYet("Yep")
}
// Don't have to declare the exceptions that Kotlin throws as it does not distinguish between checked and unchecked exceptions. Try/catch can be used as an expression.
fun getNumber(string: String): Int {
    return try {
        Integer.parseInt(string)
    }
    catch (e: NumberFormatException) {
        0
    }
    finally {
        println("I'm in the finally block") // Will print regardless of outcome. Prints before the result as it gets executed and then the function returns the result.
        1 // Does not feature as the finally block is not involved in the execution of the function
    }
}
//If you want to return null instead of throwing an exception:
fun getNumber2(string: String): Int? {
    return try {
        Integer.parseInt(string)
    }
    catch (e: NumberFormatException) {
        null
    }
    finally {
        println("I'm in the finally block")
    }
}
// A developer might implement a function that will always throw an Exception since it has not been completed yet - as a reminder to themselves and other developers that code is incomplete:
fun notImplementedYet (something: String): Nothing { // Makes it clear to developers that this code will never return a value
    throw IllegalArgumentException("Implement me!")
}