Calling Java from Kotlin – Interoperability

Things to consider when calling Java from Kotlin.

fun main() {
    val car = Car("silver", "Audi", "A3", "3.2 V6", "Leather", 2004)
    car.colour = "Akoya silver" // Can only refer to this like a property if getter and setters are set in Java. Will work without setter if property is public, but not usually desirable
    // Wildcard assignments in Java are converted to use-site variance in Kotlin. ? extends someClass -> out someClass, ? super someClass -> in someClass
    // If you call a Java method that throws an exception and you don't handle the exception in the code, you don't have to declare that the Kotlin function throws the exception - Kotlin doesn't care
    // varargs: If you want to call a Java method that wants multiple arguments:
    car.variableMethod(5, "hello", "goodbye") // This works
    val strings = arrayOf("hello", "goodbye")
//    car.variableMethod(10, strings) // But it doesn't allow us to pass in an array
    car.variableMethod(10, *strings) // So we need to use the spread operator to unpack the array
    // Kotlin doesn't have void, so it will return Unit
    // As previously discussed, when Java method expects primitive types we cannot use arrayOf. We need to use e.g. intArrayOf or call .toIntArray on the array:
//    car.wantsIntArray(arrayOf(1, 2, 3)) // Nope
    car.wantsIntArray(intArrayOf(1, 2, 3)) // Fine
    car.wantsIntArray(arrayOf(1, 2, 3).toIntArray()) // Fine
    // Any? is top of Kotlin hierarchy, Object is top of Java. What happens when Java calls Object explicitly? Will be treated like Any in Kotlin.
    // However, does not have methods such as wait(), notify(), clone() or finalize(). If wanting to call these cast the variable to java.lang.Object in your Kotlin code:
//    car.anObject.equals("bh") // Has equals() method and some others but missing the methods listed above
//    (car.anObject as java.lang.Object).notify() // All methods now available on casting
    // Static methods and fields are translated to companion objects. You can't pass the companion object around as a value but you can use its members
    println("x = ${Car.x}") // Accessing static variable
    println(Car.xString()) // The same as calling a companion object
    // Single Abstract Method (SAM) interface as of Java 8, can use lambda. E.g. Runnable interface is a SAM (see Car.java demoMethod and demoMethod2)
    // If a Java method wants an object that implements a SAM interface e.g. Runnable then you can pass it a Kotlin function:
    car.demoMethod3 ({ println("I'm in a Thread") }) // Function signature has to match parameter, in this case Runnable
}
package academy.learnprogamming.javainteroperability;


import org.jetbrains.annotations.NotNull; // N.B. Had to prompt for download from Maven
import org.jetbrains.annotations.Nullable;

public class Car {

    public static int x = 5;

    private String colour;
    private String manufacturer;
    private String model;
    private String variant;
    private String interior;
    private int year;
    private Object anObject;

    public Car(String colour, String manufacturer, String model, String variant, String interior, int year) {
        this.colour = colour;
        this.manufacturer = manufacturer;
        this.model = model;
        this.variant = variant;
        this.interior = interior;
        this.year = year;
    }

    public void demoMethod() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("I'm in a Thread");
            }
        }).start();
    }

    public void demoMethod2() {
        new Thread(() -> System.out.println("I'm in a Thread")).start();
    }

    public void demoMethod3(Runnable r) {
        new Thread(r).start();
    }

    public static String xString() {
        return "This is x: " + x++;
    }

    public String getColour() {
        return colour;
    }

    public void setColour(@NotNull String colour) { // @NotNull comes from JetBrains, not Java. Not enforced by Java.
        this.colour = colour;
    }

    public String getManufacturer() {
        return manufacturer;
    }

    public void setManufacturer(@Nullable String manufacturer) {
        this.manufacturer = manufacturer;
    }

    public String getModel() {
        return model;
    }

    public void setModel(String model) {
        this.model = model;
    }

    public String getVariant() {
        return variant;
    }

    public void setVariant(String variant) {
        this.variant = variant;
    }

    public @Nullable String getInterior() {
        return interior;
    }

    public void setInterior(String interior) {
        this.interior = interior;
    }

    public int getYear() {
        return year;
    }

    public void setYear(int year) {
        this.year = year;
    }

    public Object getAnObject() {
        return anObject;
    }

    public void setAnObject(Object anObject) {
        this.anObject = anObject;
    }

    public void variableMethod(int num, String... strings) {
        for (String string: strings) {
            System.out.println(string);
        }
    }

    public void wantsIntArray(int[] theArray) {
        for (int i: theArray) {
            System.out.println(i);
        }
    }

    @Override
    public String toString() {
        return "Car{" +
                "colour='" + colour + '\'' +
                ", manufacturer='" + manufacturer + '\'' +
                ", model='" + model + '\'' +
                ", variant='" + variant + '\'' +
                ", interior='" + interior + '\'' +
                ", year=" + year +
                '}';
    }
}

 

Imports in Kotlin

From within the same module:

package academy.learnprogramming.inheritance.inheritance // We can change this, but suggest sticking to directory structure

// Packages don't have to match the directory structure, but best to
// We import classes and interfaces as we would in Java,  but what about top-level functions?
fun topLevel(str: String) = println("Top-level function: $str") // This top-level function is called from a file in a different package. If auto-import is turned on it will detect this function and import for us
package academy.learnprogramming.inheritance.academy.learnprogramming.anotherpackage

import academy.learnprogramming.inheritance.inheritance.topLevel // Import statement produced by IDE

// enums can be imported as a class or individual enum:
import academy.learnprogramming.inheritance.inheritance.Department.* // class, or
import academy.learnprogramming.inheritance.inheritance.Department.IT // enum

// If importing from multiple sources and two have the same name you can import 'as':
import academy.learnprogramming.inheritance.inheritance.someImport as anotherImportName // Use this reference when calling. Does not work with the * wildcard

fun main() {
    topLevel("A string") // IDE suggests this function and imports it as auto-import is turned on
    println(IT.getDeptInfo())
}
// Object declarations can accessed similarly
// If trying to access class functions you will need to import the entire class
// typealiases and extension functions are imported the same way, by name

To add another module you must use the IDE. Right-click on the module and select Open Module Settings (F4). Make sure the correct module is selected and use the ‘+’ to add Module Dependency:

You can then use the Alt-Enter/Cmd-Enter prompt to auto-import the function you have typed in, or the IDE will suggest the function name if you start typing it.

Kotlin object keyword – Singletons, companion objects and object expressions

Use of the object keyword in Kotlin.

fun main() {
    println(CompanyCommunications.getTagLine()) // Call the class name as there is only one instance
    println(CompanyCommunications.getCopyRightLine())

    SomeOtherClass().printPrivateVar() // Will allow us access to privateVar despite being private, thanks to Companion object
    SomeClass2.SomeCompanion.accessPrivateVar() // Using companion name, but again redundant

    val someClass = SomeClass4.justAssign("this is the string as is")
    val someClass2 = SomeClass4.upperOrLowerCase("this is the string as is", false)
    println(someClass.someString)
    println(someClass2.someString)
//    val someClass3 = SomeClass4("This will not work") // Constructor is private so cannot instantiate directly

    var thisIsMutable = 45

    wantsSomeInterface(object: SomeInterface { // Uses object to create an instance of the someInterface interface - NOT a Singleton. Is generated and then destroyed. Can use multiple interfaces, unlike Java
        override fun mustImplement(num: Int): String {
            return "This is from mustImplement: ${num * 100 + thisIsMutable}" // This expression can access variables from outside it (val or var, doesn't need to be final), unlike Java
        }
    })
}

// 'object' keyword's 3 uses: Singletons, companion objects, object expressions

// Singletons
// //In Java, you would create a private constructor that allows only one instance which other classes can access
// In Kotlin, there can only be one instance of an 'object' class, so we use that:
object CompanyCommunications { // No class keyword needed. Also no constructors as instance is created instantaneously
    val currentYear = Year.now().value
    fun getTagLine() = "Our company rocks!"
    fun getCopyRightLine() = "Copyright \u00A9 $currentYear Our Company. All rights reserved."
}

// Companion Objects
// No static keyword in Kotlin - instead we can use top-level functions and properties, and Object declarations (above)
// But what if you want an equivalent to Java static (see Java objects below), where you can access functions and properties without creating an instance of the class?
// We create companion objects inside the class which can be accessed without having to generate an instance of the class
class SomeClass {

    companion object {
        private var privateVar = 6 // Because is private no getter/setter generated

        fun accessPrivateVar() {
            println("I'm accessing privateVar: $privateVar")
        }
    }

}

class SomeClass2 {

    companion object SomeCompanion { // You can also name your companion object, but little use
        private var privateVar = 6

        fun accessPrivateVar() {
            println("I'm accessing privateVar: $privateVar")
        }
    }

}

class SomeOtherClass() {
    fun printPrivateVar () {
        println(SomeClass.accessPrivateVar()) // Could use SomeClass.Companion.accessPrivateVar() but redundant
    }
}

class SomeClass3 {

    val someString: String

    constructor(str: String) { // First secondary constructor
        someString = str
    }

    constructor(str: String, lowerCase: Boolean) { // Second secondary constructor
        if (lowerCase) {
            someString = str.toLowerCase()
        } else {
            someString = str.toUpperCase()
        }
    }
// But instead of doing it this way we can create a companion class that returns an instance of the class (see below)
}

class SomeClass4 private constructor(val someString: String){ // We want to implement the Factory pattern here. This class should generate instances of the class - nobody should be able to do it directly as in SomeClass3, so primary is made private

    companion object{
        private var privateVar = 6

        fun accessPrivateVar() {
            println("I'm accessing privateVar in SomeClass4: $privateVar")
        }

        fun justAssign(str: String) = SomeClass4(str) // Can only call the private constructor from inside class therefore controlling how class is instantiated...
        fun upperOrLowerCase(str: String, lowerCase: Boolean): SomeClass4 { // so to get an instance of this class you have to go through either the justAssign or upperOrLowerCase function
            if (lowerCase) {
                return SomeClass4(str.toLowerCase())
            } else {
                return SomeClass4(str.toUpperCase())
            }
        }

    }

}

// Object expressions - equivalent to an anonymous object in Java
interface SomeInterface {
    fun mustImplement(num: Int): String
}

fun wantsSomeInterface(si: SomeInterface) {
    println("Printing from wantsSomeInterface - ${si.mustImplement(22)}")
}

Companion object equivalent in Java

public class SomeClass {

    private static int privateVar = 6;

    public static void main(String[] args) {
        new SomeOtherClass().someOtherMethod();
    }

    public static void accessPrivateVar() {
        System.out.println("I'm accessing privateVar: " + privateVar);
    }
}
public class SomeOtherClass {

    public void someOtherMethod() {
        SomeClass.accessPrivateVar();
    }
}