tech-lessons.in
Moved from dzone
April 20, 2018

Kotlin Wishlist for Java

Posted on April 20, 2018  •  7 minutes  • 1467 words
Table of contents

There is no doubt that Java has enjoyed a superior position when it comes to programming languages and is considered as one of the most important languages for development. However, there have been a number of languages developed on top of the JVM, like Kotlin Kotlin is a statically typed programming language for modern multi-platform applications. While I have been a Java developer for quite a long while, working on the data-anonymization project made me feel that there are things that Java should consider importing from Kotlin. These are some Kotlin features that I would love to see making a place in Java.

Promote Immutability

Java 9 promotes immutability by introducing factory methods to create collections. It would be great to see immutable collections embedded in the language, rather than relying on wrappers to generate immutable collections. existingDepartments() is a function that returns an immutable list of Strings in Kotlin.

fun existingDepartments(): List =
    listOf("Human Assets", "Learning & Development", "Research")

Java 9 comes closest to returning an immutable list by throwing an UnsupportedOperationException when an attempt is made to add or remove an element from the list. It would be great to have a separate hierarchy of mutable and immutable collections and avoid exposing add/remove or any other mutating methods from immutable collections.

//pre Java 8
public List existingDepartments() {
    return new ArrayList(){{
        add("Human Assets");
        add("Learning & Development");
        add("Research");
    }};
}
//Java 8
public List existingDepartments() {
    return Stream.of("Human Assets", "Learning & Development", "Research")
                 .collect(Collectors.toList());
}
//Java 9
public List existingDepartments() {
    return List.of("Human Assets", 
                   "Learning & Development",
                   "Research");
}

Being more explicit about immutable collections and letting immutable collections speak out loud for themselves should be given preference over exposing methods and throwing UnsupportedOperationExceptions.

Method Parameters Should Be Final by Default

With an intent to promote immutability and avoid errors because of mutation, it might be worth to at least giving a thought to making method parameters final by default.

fun add (augend: Int, addend: Int) = augend + addend

Parameters for the add() function are val by default cannot be changed, which means as a client of any function, I can rest assured that the function is not changing the arguments (not to be confused with object mutation) that are passed to it.

Making method parameters final by default might and will most likely break existing code bases on Java upgrades but is worth giving a thought.

Handle NULL at Compile Time

Every Java developer is bound to know the infamous NullPointerException. Kotlin took a major step by handling NULLs at compile time. Everything is non-null be default unless it is explicitly stated. Did Java 8 not introduce Optional for the very same reason ? Let’s see with an example:

class Employee(private val id: Int, private val department: Department?) {
    fun departmentName() = department?.name ?: "Unassigned"
}
class Department(val name: String)
/**
Employee needs a non-nullable "id" and an optional department to be constructed.
val employee = Employee(null, null); //Compile Time Error
 **/

The Employee class has a primary constructor with a non-nullable id and an optional (nullable) department. Passing null for the id will result in a compilation error. The departmentName function accesses the name property of Department using the optional operator ? on the nullable field. If department is null, name will not be accessed and the expression on the left-hand side department?.name will return null. The Elvis operator ?: will return the right-hand side (“Unassigned”) if the left-hand side of the expression is null.

//Java8
class Employee {
    private Integer id;
    private Optional department;

    Employee(Integer id, Optional department){
       this.id = id;
       this.department = department;
    }
    public String departmentName() {
       return department.orElse("Unassigned");
    }
}
/**
Employee needs a non-nullable "id" and an optional department to be constructed.
Employee employee = new Employee(null, null); //NPE;
**/

Optional will not protect the code from NPE, but Optional has its advantages:

Handling NULLs at compile time should result in cleaner code by removing unnecessary NULL checks in the form of an if statement, Objects.requireNonNull, Preconditions.checkNotNull, any other form.

Improve Lambdas

Java 8 introduced lambdas, which are built on top of a functional interface and a functional descriptor, meaning every lambda expression will map to the signature of an abstract method defined in that functional interface. This means it is a mandate to have an interface (Functional Interface) with only one abstract method (Functional Descriptor) to create a lambda expression.

val isPositive: (Int) -> Boolean = { it > 0 }
OR,
val isPositive: (Int) -> Boolean = { num > 0 }
OR,
val isPositive: (Int) -> Boolean = { num: Int > 0 }

//Usage
isPositive(10) returns true
isPositive(-1) returns false

In the above code, the variable isPositive a function that takes an Int as an argument and returns a Boolean. The value of this variable is a function definition or a lambda defined in braces, which checks that the passed argument is greater than zero. Whereas, as seen in Java below, Predicate is a functional interface containing an abstract method test() — which takes an argument of type T and returns a boolean. So, isPositive takes an argument of type Integer and checks that it is greater than zero. In order to use it, we need to invoke the test() method on the isPositive method.

//Java 8
private Predicate<Integer> isPositive = (Integer arg) -> arg > 0;

//Usage
isPositive.test(10) returns true
isPositive.test(-1) returns false

@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
}

Lambdas should be independent of functional interfaces and their functional descriptors.

Support Extension Functions

Kotlin supports extension functions, which provide the ability to extend a class with new functionality without having to inherit from the class or use any type of design pattern, such as Decorator. Let’s write an extension function to return the last character of a String, meaning "Kotin".lastChar() will return ’n'.

fun String.lastChar() = this.toCharArray()[this.length - 1]
/**
    Extension functions are of the form -
    fun <ReceiverObject>.function_name() = body
    OR,
    fun <ReceiverObject>.function_name(arg1: Type1, ... argN: TypeN) = body
**/

Here, lastChar() is an extension function defined on the String type, which is called a receiver object. This function can now be invoked as "Kotlin".lastChar().

Extension functions provide an ability to extend a class with new functionalities without inheritance or any other design pattern.

Tail Recursion

Kotlin provides support for Tail-recursion. Tail-recursion is a form of recursion in which the recursive calls are the last instructions in the function (tail). In this way, we don’t care about previous values, and one stack frame suffices for all the recursive calls; tail-recursion is one way of optimizing recursive algorithms. The other advantage/optimization is that there is an easy way to transform a tail-recursive algorithm to an equivalent one that uses iteration instead of recursion.

fun factorialTco(val: Int): Int {
    tailrec fun factorial(n: Int, acc: Int): Int = if ( n == 1 ) acc else factorial(n - 1, acc * n)
  return  factorial(val, acc = 1)
}

When a function is marked with the tailrec modifier and meets the required form, the compiler optimizes out the recursion, leaving behind a fast and efficient loop-based version instead.

Effectively, a tail-recursive function can execute in constant stack space, so it’s really just another formulation of an iterative process

Java does not directly support tail-call optimization at the compiler level, but one can use lambda expressions to implement it. It would be nice to see TCO at the compiler level.

Miscellaneous

Remove inherent duplication [new, return, semicolon]: Kotlin does not require new to create an instance. It still needs a return if a function is treated as a statement instead of an expression.

class Employee(private val id: Int, private val department: Department?) {
    //no return
    fun departmentNameWithoutReturn() = department?.name ?: "Unassigned"

    //return is needed if a function is treated as a statement rather than an expression
    fun departmentNameWithoutReturn() {
        val departmentName = department?.name ?: "Unassigned"
        return departmentName
    }
}

Singleton Classes: It would be great to see an easier way to create singleton classes in Java. An equivalent syntax in Kotlin is seen below.

object DataProviderManager {
    fun registerDataProvider(provider: DataProvider) {
        // ...
    }
}

Immutable Classes: It would be good to see something like a readonly or immutable modifier to create an immutable class. The below-mentioned code snippet is simply a thought (not available in Kotlin or Java).

//Hypothetical
immutable class User(private val name: String, private val id: Int)

Conclusion

As developers, we will always make mistakes (skipping NULL checks, mutating a collection, etc.), but providing features at the language level that can stop these mistakes will make our lives easier and prevent mistakes.