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 +
                '}';
    }
}

 

Kotlin Collections – Useful Functions

Useful functions for Kotlin Collections.

fun main() {
    val setInts = setOf(10, 15, 19, 5, 3, -22)

    println(setInts.filter { it % 2 != 0 }) // Odd numbers only

    val immutableMap = mapOf(1 to Car3("green", "Toyota", 2015),
        2 to Car3("red", "Ford", 2016),
        3 to Car3("silver", "Honda", 2013),
        17 to Car3("red", "BMW", 2015),
        8 to Car3("green", "Ford", 2010)
    )

    println("2016 cars only: ${immutableMap.filter { it.value.year == 2016 }}") // 2016 cars only

    val mutableMap = mutableMapOf(1 to Car3("green", "Toyota", 2015), 2 to Car3("red", "Ford", 2016), 3 to Car3("silver", "Honda", 2013))

    mutableMap.filter { it.value.colour == "silver" }

    println(mutableMap) // Map hasn't changed by being filtered. Filter creates new instance of the map.

    // Add 10 to every element and create new
    val ints = arrayOf(1, 2, 3, 4, 5)
    val add10List: MutableList<Int> = mutableListOf()
    for (i in ints) {
        add10List.add(i + 10)
    }
    println("Add10List: ${add10List}")
    // The Kotlin way - using the map FUNCTION (not Collection)
    val add10List2 = ints.map { it + 10 }
    println("Add10List2: ${add10List2}")

    // Create a List that contains all the years of the Cars in our Map
    val carYearsList = immutableMap.map { it.value.year }
    println("Car years list: ${carYearsList}")

    // You can chain filter and map functions together:

    println("Ford cars only: ${immutableMap.filter { it.value.model == "Ford" }}") // Ford cars only

    println("Ford car colours: ${immutableMap.filter { it.value.model == "Ford" }.map { it.value.colour}}") // Ford car colours

    // If all the elements in a Collection match a condition:
    println(immutableMap.all { it.value.year > 2014 }) // Are all cars modern (post-2015)?
    println(immutableMap.any { it.value.year > 2014 }) // Are any cars modern (post-2015)?
    println(immutableMap.count { it.value.year > 2014 }) // How many cars are modern (post-2015)?
    // To find value that matches we can move them into a List and use the find function
    val cars = immutableMap.values
    println("First modern car: ${cars.find { it.year > 2014}}") // Finds first item in List that satisfies condition

    println("Cars grouped by colour: ${cars.groupBy { it.colour }}") // Group cars by colour
    println("Cars sorted by year: ${cars.sortedBy { it.year }}") // Sorted by year

    println("Sorted by key: ${immutableMap.toSortedMap()}") // Sorted by key

}

data class Car3(val colour: String, val model: String, val year: Int)

 

Datatypes

Datatype rules for Kotlin.

fun main() {
    val myInt = 10
    println("default datatype is ${myInt is Int}")
    var myLong = 22L
    // Every datatype has .toXxxx() functions to convert to another type. In Kotlin, you cannot declare myLong = myInt and expect it to expand the data type.
    myLong = myInt.toLong()

    val myByte: Byte = 111
    var myShort: Short
    myShort = myByte.toShort()

    var myDouble = 65.984 // Defaults to a Double
    println(myDouble is Double) // As proof

    val myFloat = 838.9823f // f declares it a Float
    println(myFloat is Float) // As proof

//    myDouble = myFloat // Will fail - no expansion
    myDouble = myFloat.toDouble()

    val char = 'b' // Defaults to Char
//    char myChar = 65 // In Java would call the char of value 65 = 'A'
    val myChar = 65 // In Kotlin, obviously produces a very different result!
//    val myChar2: Char = 65 // Fails, does not conform to data type
    val myCharInt = 65
    println(myCharInt.toChar()) // => A

    // When calling Java from Kotlin, how do we provide primitive types since they don't exist in Kotlin?
    val vacationTime = false
    val onVacation = DummyClass().isVacationTime(vacationTime)
    println(onVacation) // Works because the Kotlin datatype classes compile down to primitives. Also works if Java is expecting Boolean due to Java boxing.

    // Any, Unit, Nothing classes
    // Any is Kotlin root class
    val anything: Any
    // Unit class is equivalent to void. A Singleton instance of Unit is returned instead of nothing
    // Nothing class is subclass of Any class. Could use when e.g. you know something will never return
}