Professor of Mathematics and Computer Science

Why Kotlin? Eight features that could convince Java developers to switch

feature
Sep 18, 201928 mins

What would Java look like if someone designed it from scratch today? Probably a lot like Kotlin

silver infinity symbol
Credit: doguhakan / Getty Images

Officially released in 2016, Kotlin has attracted a lot of attention in recent years, especially since Google announced its support for Kotlin as an alternative to Java on Android platforms. With the recently announced decision to make Kotlin the preferred language for Android, you may be wondering if it’s time to start learning a new programming language. If that’s the case, this article could help you decide.

About Kotlin

Kotlin is a modern, statically-typed programming language that features both object-oriented and functional programming constructs. It targets several platforms, including the JVM, and is fully interoperable with Java. In many ways, Kotlin is what Java might look like if it were designed today. In this article I introduce eight features of Kotlin that I believe Java developers will be excited to discover.

Hello, World! Kotlin versus Java

Listing 1 shows the obligatory “Hello, world!” function written in Kotlin.

Listing 1. “Hello, world!” in Kotlin


fun main()
  {
    println("Hello, world!")
  }

As simple as it is, this example reveals key differences from Java.

  1. main is a top-level function; that is, Kotlin functions do not need to be nested within a class.
  2. There are no public static modifiers. While Kotlin has visibility modifiers, the default is public and can be omitted. Kotlin also doesn’t support the static modifier, but it isn’t needed in this case because main is a top-level function.
  3. Since Kotlin 1.3, the array-of-strings parameter for main is not required and may be omitted if not used. If needed, it would be declared as args : Array<String>.
  4. No return type is specified for the function. Where Java uses void, Kotlin uses Unit, and if the return type of a function is Unit, it may be omitted.
  5. There are no semicolons in this function. In Kotlin, semicolons are optional, and therefore line breaks are significant.

That’s an overview, but there’s a lot more to learn about how Kotlin differs from Java and, in many cases, improves on it.

1. Cleaner, more compact syntax

Java is often criticized for being too verbose, but some verbosity can be your friend, especially if it makes the source code more understandable. The challenge in language design is to reduce verbosity while retaining clarity, and I think Kotlin goes a long way toward meeting this challenge.

As you saw in Listing 1, Kotlin does not require semicolons, and it allows omitting the return type for Unit functions. Let’s consider a few other features that help make Kotlin a cleaner, more compact alternative to Java.

Type inference

In Kotlin you can declare a variable as var x : Int = 5, or you can use the shorter but just as clear version var x = 5. (While Java now supports var declarations, that feature did not appear until Java 10, long after the feature had appeared in Kotlin.)

Kotlin also has val declarations for read-only variables, which are analogous to Java variables that have been declared as final, meaning the variable cannot be reassigned. Listing 2 gives an example.

Listing 2. Read-only variables in Kotlin


val x = 5
...
x = 6   // ERROR: WILL NOT COMPILE

Properties versus fields

Where Java has fields, Kotlin has properties. Properties are declared and accessed in a manner similar to public fields in Java, but Kotlin provides default implementations of accessor/mutator functions for properties; that is, Kotlin provides get() functions for val properties and both get() and set() functions for var properties. Customized versions of get() and set() can be implemented when necessary.

Most properties in Kotlin will have backing fields, but it is possible to define a computed property, which is essentially a get() function without a backing field. For example, a class representing a person might have a property for dateOfBirth and a computed property for age.

Default versus explicit imports

Java implicitly imports classes defined in package java.lang, but all other classes must be explicitly imported. As a result, many Java source files start by importing collection classes from java.util, I/O classes from java.io, and so forth. By default, Kotlin implicitly imports kotlin.*, which is roughly analogous to Java importing java.lang.*, but Kotlin also imports kotlin.io.*, kotlin.collections.*, and classes from several other packages. Because of this, Kotlin source files normally require fewer explicit imports than Java source files, especially for classes that use collections and/or standard I/O.

No call to ‘new’ for constructors

In Kotlin, the keyword new is not needed to create a new object. To call a constructor, just use the class name with parentheses. The Java code


Student s = new Student(...);   // or var s = new Student(...);

could be written as follows in Kotlin:


var s = Student(...)

String templates

Strings can contain template expressions, which are expressions that are evaluated with results inserted into the string. A template expression starts with a dollar sign ($) and consists of either a simple name or an arbitrary expression in curly braces. String templates can shorten string expressions by reducing the need for explicit string concatenation. As an example, the following Java code


println("Name: " + name + ", Department: " + dept);

could be replaced by the shorter but equivalent Kotlin code.


println("Name: $name, Department: $dept")

Extends and implements

Java programmers know that a class can extend another class and implement one or more interfaces. In Kotlin, there is no syntactic difference between these two similar concepts; Kotlin uses a colon for both. For example, the Java code


public class Student extends Person implements Comparable<Student>

would be written more simply in Kotlin as follows:


class Student : Person, Comparable<Student>

No checked exceptions

Kotlin supports exceptions in a manner similar to Java with one big difference–Kotlin does not have checked exceptions. While they were well intentioned, Java’s checked exceptions have been widely criticized. You can still throw and catch exceptions, but the Kotlin compiler does not force you to catch any of them.

Destructuring

Think of destructuring as a simple way of breaking up an object into its constituent parts. A destructuring declaration creates multiple variables at once. Listing 3 below provides a couple of examples. For the first example, assume that variable student is an instance of class Student, which is defined in Listing 12 below. The second example is taken directly from the Kotlin documentation.

Listing 3. Destructuring examples


val (_, lName, fName) = student
// extract first and last name from student object
// underscore means we don't need student.id

for ((key, value) in map)
  {
    // do something with the key and the value
  }

‘if’ statements and expressions

In Kotlin, if can be used for control flow as with Java, but it can also be used as an expression. Java’s cryptic ternary operator (?:) is replaced by the clearer but somewhat longer if expression. For example, the Java code


double max = x >= y ? x : y

would be written in Kotlin as follows:

val max = if (x >= y) then x else y

Kotlin is slightly more verbose than Java in this instance, but the syntax is arguably more readable.

‘when’ replaces ‘switch’

My least favorite control structure in C-like languages is the switch statement. Kotlin replaces the switch statement with a when statement. Listing 4 is taken straight from the Kotlin documentation. Notice that break statements are not required, and you can easily include ranges of values.

Listing 4. A ‘when’ statement in Kotlin


when (x)
  {
    in 1..10 -> print("x is in the range")
    in validNumbers -> print("x is valid")
    !in 10..20 -> print("x is outside the range")
    else -> print("none of the above")
  }

Try rewriting Listing 4 as a traditional C/Java switch statement, and you will get an idea of how much better off we are with Kotlin’s when statement. Also, similar to if, when can be used as an expression. In that case, the value of the satisfied branch becomes the value of the overall expression.

2. Single type system (almost)

Java has two separate type systems, primitive types and reference types (a.k.a., objects). There are many reasons why Java includes two separate type systems. Actually that’s not true. As outlined in my article A case for keeping primitives in Java, there is really only one reason for primitive types–performance. Similar to Scala, Kotlin has only one type system, in that there is essentially no distinction between primitive types and reference types in Kotlin. Kotlin uses primitive types when possible but will use objects if necessary.

So why the caveat of “almost”? Because Kotlin also has specialized classes to represent arrays of primitive types without the autoboxing overhead: IntArray, DoubleArray, and so forth. On the JVM, DoubleArray is implemented as double[]. Does using DoubleArray really make a difference? Let’s see.

Benchmark 1: Matrix multiplication

In making the case for Java primitives, I showed several benchmark results comparing Java primitives, Java wrapper classes, and similar code in other languages. One of the benchmarks was simple matrix multiplication. To compare Kotlin performance to Java, I created two matrix multiplication implementations for Kotlin, one using Array<DoubleArray> and one using Array<Array<Double>>. Listing 5 shows the Kotlin implementation using Array<DoubleArray>.

Listing 5. Matrix multiplication in Kotlin


fun multiply(a : Array<DoubleArray>, b : Array<DoubleArray>) : Array<DoubleArray>
  {
    if (!checkArgs(a, b))
        throw Exception("Matrices are not compatible for multiplication")

    val nRows = a.size
    val nCols = b[0].size

    val result = Array(nRows, {_ -> DoubleArray(nCols, {_ -> 0.0})})

    for (rowNum in 0 until nRows)
      {
        for (colNum in 0 until nCols)
          {
            var sum = 0.0

            for (i in 0 until a[0].size)
                sum += a[rowNum][i]*b[i][colNum]

            result[rowNum][colNum] = sum
          }
      }

    return result
  }

Next, I compared the performance of the two Kotlin versions to that of Java with double and Java with Double, running all four benchmarks on my current laptop. Since there is a small amount of “noise” in running each benchmark, I ran all versions three times and averaged the results, which are summarized in Table 1.

I was somewhat surprised by these results, and I draw two takeaways. First, Kotlin performance using DoubleArray is clearly superior to Kotlin performance using Array<Double>, which is clearly superior to that of Java using the wrapper class Double. And second, Kotlin performance using DoubleArray is comparable to–and in this example slightly better than–Java performance using the primitive type double.

Clearly Kotlin has done a great job of optimizing away the need for separate type systems–with the exception of the need to use classes like DoubleArray instead of Array<Double>.

Benchmark 2: SciMark 2.0

My article on primitives also included a second, more scientific benchmark known as SciMark 2.0, which is a Java benchmark for scientific and numerical computing available from the National Institute of Standards and Technology (NIST). The SciMark benchmark measures performance of several computational routines and reports a composite score in approximate Mflops (millions of floating point operations per second). Thus, larger numbers are better for this benchmark.

With the help of IntelliJ IDEA, I converted the Java version of the SciMark benchmark to Kotlin. IntelliJ IDEA automatically converted double[] and int[] in Java to DoubleArray and IntArray in Kotlin. I then compared the Java version using primitives to the Kotlin version using DoubleArray and IntArray. As before, I ran both versions three times and averaged the results, which are summarized in Table 2. Once again the table shows roughly comparable results.

3. Null safety

One of the major design goals of Kotlin was to eliminate, or at least greatly reduce problems associated with null references. More than any other language I have used, Kotlin greatly reduces occurrences of the dreaded NullPointerException. The Kotlin type system distinguishes between references that can hold null values (nullable references) and those that can’t, and the compiler will verify consistent usage of variables of each type. So, for example, in Kotlin, a variable of type String can never be null, but a variable of type String? (note ? suffix) can. This idea is illustrated in Listing 6.

Listing 6. Null safety examples


var s1: String  = "abc"   // s1 is not nullable
var s2: String? = "abc"   // s2 is nullable
s1 = null    // compilation error
s1 = s2      // compilation error
s2 = s1      // o.k.
s2 = null    // o.k.

println(s1.length)   // will never throw NPE
println(s2.length)   // compilation error

...

fun printLength(s : String?)
  {
    if (s != null)
        println(s.length)   // o.k.
  }

Safe calls

If s is nullable, and if the compiler can’t verify that s is not null at the point where s is dereferenced, then a call to s.length will be flagged as a compile-time error. However, a call to s?.length will return s.length if s is not null; otherwise, it will return null. The type of s?.length is Int?, so after calling s?.length, the compiler checks that Int? is used safely. Since the function println can handle null values, a call to println(s?.length) is safe and will print either the length of the string or the word “null”.

The Elvis operator

Frequently in programming we want to use a specific property of a nullable variable, so we check first to ensure that it isn’t null. If it isn’t, we use that property, but if it is, we want to use a different value in our code. Listing 7 illustrates this use case.

Listing 7. Example without the Elvis operator


val s : String?
...
val len : Int = if (s != null) s.length else -1

Kotlin has a “null coalescing” operator (?:), which is more popularly known as the “Elvis” operator, because if you turn your head slightly it resembles Elvis Presley’s pompadour. The left side of the operator is an expression that can be null. If the value on the left of the operator is not null, then that value is used as the result of applying the operator. If the value on the left is null, then the value on the right of the operator is used. With the Elvis operator, the code in Listing 7 can be written more simply as shown in Listing 8.

Listing 8. Example with the Elvis operator


val s : String?
...
val len = s?.length ?: -1

The ‘!!’ operator

In Kotlin, you can use the !! operator to tell the compiler to ignore checks for null safety. Consider the following code:


val len = s!!.length

Here you are saying to the compiler, “Look, I am confident that s is not null at this point, and I assume responsibility if it is. Do not flag this as a compile error.” Of course execution of this code could still throw a NullPointerException at runtime if s is null.

4. Functions and functional programming

Functions in Kotlin can be top-level objects, meaning they do not need to be nested within a class. Moreover, Kotlin has other function-related features that support a cleaner, more compact syntax without loss of clarity. As an example, it’s possible to omit the return type, curly braces, and return statement in single-expression functions. Therefore


fun double(x : Int) : Int
  {
    return 2*x
  }

could be replaced with the much shorter version below:


fun double(x : Int) = 2*x

Kotlin also allows default values for parameters, which can be used to eliminate some Java functions or constructors that simply call other functions or constructors with specific values for parameters. I often write constructors that call other constructors in the same class. For example, if I were to write my own ArrayList class in Java, I might provide a couple of constructors as follows:


public ArrayList(int initialCapacity)
  {
    ...
  }

public ArrayList()
  {
    this(10);
  }

In Kotlin this would be reduced to only one constructor:


constructor(initialCapacity : Int = 10)
  {
    ...
  }

Higher-order functions

Kotlin also supports higher-order functions, which means that functions can be passed as parameters to functions, returned as values from functions, or both. And, similar to Java, Kotlin supports lambda expressions and closures to avoid having to write complete function definitions. Moreover, if a lambda expression has only one parameter, then the parameter need not be declared, and the parameter will be implicitly declared using the name it. Additional features that support functional programming include optimized tail recursion and many common functional programming functions for lists. Listings 9 and 10 illustrate some of these features.

Listing 9. Functional programming in Kotlin (Example 1)


// assumes that students is a list of Student objects
val adultStudents = students.filter   { it.age >= 21}
                            .sortedBy { it.lastName }

Listing 10 below is from the article “My favorite examples of functional programming in Kotlin” by Marcin Moskala.

Listing 10. Functional programming in Kotlin (Example 2)


fun <T : Comparable<T>> List<T>.quickSort(): List<T> =
    if (size < 2) this
    else
      {
        val pivot = first()
        val (smaller, greater) = drop(1).partition {it <= pivot}
        smaller.quickSort() + pivot + greater.quickSort()
      }

In certain cases, Kotlin also supports inline functions, meaning that the compiler will replace a function invocation with the function body. Inlining a function can improve performance by eliminating call/return overhead, but it can also increase the size of your code, so use this technique cautiously. The place where inlining is most applicable is with lambda expressions, where inlining means that the compiler does not need to create a new function every time a lambda expression is created.

5. Data classes

Classes designed primarily to hold data are known as entity classes, business classes, or data classes. These types of classes are typically persisted to a database. In Java, these classes tend to have very few methods beyond constructors, get()/set() methods for fields, and standard methods toString(), hashCode(), equals() (and possibly clone()). While modern IDEs can save time by generating these standard methods for you, Kotlin provides a special kind of class, a data class, that doesn’t require implementing these methods within the class.

You can grasp the benefits of data classes by comparing the Java code outlined in Listing 11 to the equivalent Kotlin code in Listing 12. Using methods generated by Eclipse, the full Java implementation consists of approximately 180 lines of uncommented source code while the Kotlin implementation consists of fewer than 10 lines of uncommented source code.

Listing 11. Data class example in Java


public class Student implements Cloneable
  {
    private String    studentId;
    private String    lastName;
    private String    firstName;
    private String    midInitial;
    private LocalDate dateOfBirth;
    private String    gender;
    private String    ethnicity;
    ...   // constructor with every field
    ...   // get()/set() methods for each field
    ...   // method toString()
    ...   // methods hashCode() and equals()
  }

Listing 12. Data class example in Kotlin


data class Student(var studentId   : String,
                   var lastName    : String,
                   var firstName   : String,
                   var midInitial  : String?,
                   var dateOfBirth : LocalDate,
                   var gender      : String,
                   var ethnicity   : String)

Records in Java

Java language architect Brian Goetz is working on a draft JDK Enhancement Proposal (JEP) that would add functionality similar to data classes to Java. The code example below (from the JEP) shows that the proposed new data classes, called records, are similar to Kotlin’s data classes, with most of the standard methods derived automatically. Records have a name, a state description, and a body.


record Point(int x, int y) { }

See JEP Draft: Records and Sealed Types for more about the proposal to add records to Java.

6. Extensions

Extensions allow the addition of new functionality to a class without subclassing and without directly modifying the class’s definition. Both extension functions and extension properties can be defined. An extension function is declared by prefixing its name with the name of the class being extended. Suppose, for example, we wanted to add a function to the String class. Listing 13 shows a simple example where we add a boolean function isLong().

Listing 13. Extension function


fun String.isLong() = this.length > 30
...   // create string str
if (str.isLong()) ...

Similarly we can add an extension property to an existing class. For example, using the data class Student from above and without modifying the source code for the class, we can add extension properties fullName and age as illustrated in Listing 14. Note the required check for nullable property middleInitial in the implementation of the extension property fullName.

Listing 14. Extension properties


val Student.fullName : String
    get()
      {
        val buffer = StringBuffer(35)
        buffer.append(lastName)
              .append(", ")
              .append(firstName)
              .append(if (midInitial != null) " $midInitial." else "")

        return buffer.toString()
      }

val Student.age : Int
    get() = dateOfBirth.until(LocalDate.now()).getYears()

...   // create Student variable student

println(student.fullName)
println(student.age)

While these two properties are implemented as extension properties, they could also be implemented as computed properties within the definition of class Student, assuming access to the source code for Student is available. A computed property is essentially a get() method in Java without a backing field.

Note that calls to extension functions and references to extension properties are resolved statically by the compiler. This means the extension function being called is determined by the declared type of a variable and not by the type of the object the variable actually references at runtime. Polymorphism does not apply for extensions.

7. Operator overloading

In Kotlin, some functions can be called using predefined operators. Because I was a mathematician in my former life, this feature has strong personal appeal for me. Many mathematically related classes can be used in a more natural way when operators such as + and * are overloaded; examples include Fraction and Matrix. But even non-mathematical classes can benefit since the operator == can be used to call the equals() function, and brackets ([]) can replace calls to get() functions, as in myList[n] or myMap["key"]. C++ also allows operators to be overloaded, but personally I prefer the simpler approach used by Kotlin. Here is a partial list of common functions that can be called using operators.

Operator Function name
+ plus()
* times()
+= plusAssign()
== equals()
> compareTo()
[] get()
.. rangeTo()
in contains()
++ inc()

The rules of operator overloading in Kotlin

It’s important to understand a few points about overloading operators in Kotlin:

  • First, functions that overload operators must be marked with the operator modifier. This is illustrated more clearly in the example shown in Listing 15 below. Note that the matrix multiplication function shown in Listing 5 can easily be converted to an operator function by adding the operator modifier and changing the name of the function from multiply to times (the name required by Kotlin for overloading the “*” operator).
  • Second, when combined with Kotlin’s single type system, you can use == almost exclusively without having to worry about when to use == and when to call the equals(). (Raise your hand if you have ever used == in a Java program when you should have called equals() method. That would be almost everyone, including me, and I strongly suspect that those with your hands down have not programmed extensively in Java.) Along these lines note that Kotlin uses the operator === for identity (a.k.a., referential equality). Also note that if variable a is null, then using a == b is safe since it translates to comparison for null using identity, as in null === b.
  • Third, Kotlin does the right thing when the operator ++ calls inc(); this means the expression ++x is compiled differently from x++, even though both expressions eventually call inc(). Similarly, Kotlin knows how to interpret the results from calling compareTo() when using relational operators such as <= and >.

Listing 15 provides an outline of a class that overloads operator functions, and Listing 16 illustrates the use of operators to call those functions. Note the use of braces {} in Listing 16 to include expressions within string templates.

Listing 15. Operator overloading for class fraction


class Fraction(numerator : Long, denominator : Long = 1) : Comparable<Fraction>
  {
    val numerator : Long
    val denominator : Long

    init
      {
        ...  // code to "normalize a fraction; e.g., 2/4 is normalized to 1/2
      }

    fun toDouble() = numerator.toDouble()/denominator.toDouble()

    operator fun plus(f: Fraction): Fraction
      {
        val numer = numerator*f.denominator + denominator*f.numerator
        val denom = denominator*f.denominator
        return Fraction(numer, denom)
      }

    operator fun minus(f: Fraction): Fraction
      {
        val numer = numerator*f.denominator - denominator*f.numerator
        val denom = denominator*f.denominator
        return Fraction(numer, denom)
      }

    operator fun times(f: Fraction)
        = Fraction(numerator*f.numerator, denominator*f.denominator)

    operator fun div(f: Fraction): Fraction
      {
        if (f.numerator == 0L)
            throw IllegalArgumentException("Divide by zero.")
        return Fraction(numerator*f.denominator, denominator*f.numerator)
      }

    operator fun inc() = Fraction(numerator + denominator, denominator)

    operator fun unaryMinus() = Fraction(-numerator, denominator)

    override fun toString() = "$numerator/$denominator"

    ...  // other functions such as compareTo(), hashCode(), and equals()
  }

Listing 16. Using class fraction


val f1 = Fraction(4, 10)   // normalized to 2/5
val f2 = Fraction(5)
val f3 = Fraction(2, 5)

println("f1 = $f1    f2 = $f2")   // f1 = 2/5    f2 = 5/1
println("f1.toDouble() = ${f1.toDouble()}")   // f1.toDouble() = 0.4
println("f2.toDouble() = ${f2.toDouble()}")   // f2.toDouble() = 5.0
println("f1 === f3 is ${f1 === f3}")   // f1 === f3 is false
println("f1 == f3 is ${f1 == f3}")     // f1 == f3 is true
println("f1 < f2 is ${f1 < f2}")       // f1 < f2 is true
println("f1.compareTo(f2) is ${f1.compareTo(f2)}")   // f1.compareTo(f2) is -1
println("-f1 = ${-f1}")           // -f1 = -2/5
println("f1 + f2 = ${f1 + f2}")   // f1 + f2 = 27/5
println("f1 - f2 = ${f1 - f2}")   // f1 - f2 = -23/5
println("f1 * f2 = ${f1*f2}")     // f1 * f2 = 2/1
println("f1 / f2 = ${f1/f2}")     // f1 / f2 = 2/25

var x : Fraction
val y = Fraction(1,2)
var z = Fraction(1)
x = y + z++
println("x = $x, z = $z")   // x = 3/2, z = 2/1

z = Fraction(1)
x = y + ++z
println("x = $x, z = $z")   // x = 5/2, z = 2/1

8. Top-level objects and the Singleton pattern

Some classes should have exactly one instance. These classes usually involve the central management of a resource such as a print spooler, a logger, a factory for a family of objects, an object that manages database connections, or an object that interacts with a physical device. Classes that can have exactly one instance with a global point of access are said to implement the Singleton pattern.

There are several ways to implement the Singleton pattern in Java, but if we’re not concerned with thread safety, one common approach is to use a static field and a static getInstance() method, as shown in Listing 17 below. Note the use of lazy initialization in Listing 17, where the instance is not actually created until it is needed. One simple but often sufficient way to achieve thread safety is to make the getInstance() method synchronized.

Listing 17. Singleton pattern in Java


public class Singleton
  {
    // the one and only instance
    private static Singleton instance = null;

    ...  // other fields


    protected Singleton()
      {
        ...  // initialization code
      }

    public static Singleton getInstance()
      {
        if (instance == null)
            instance = new Singleton();
        return instance;
      }

    ...  // other methods
  }

Code using the Java singleton would look similar to the following:


Singleton.getInstance().someMethod();

In Kotlin, the Singleton pattern is implemented using a top-level object declaration. Note the use of “object” instead of “class” in the declaration in Listing 18.

Listing 18. Singleton pattern in Kotlin


object Singleton
  {
    ...  // properties and methods
  }

Code using the Kotlin singleton would look similar to the following:


Singleton.someMethod()

While Kotlin’s approach is simple and straightforward, it is somewhat more complicated to implement a singleton in Kotlin if the constructor has parameters. See the end of this article for guidance on implementing parameterized singletons in Kotlin.

Additional features

Kotlin has more cool features than I could cover in one article. I will briefly mention two more that I think Java developers should know about.

Delegation

Think of delegation as an alternative to implementation inheritance. A class can implement an interface by delegating all of its public members to a specific object. Overriding a member of an interface implemented by delegation works as you might expect.

Coroutines

Think of coroutines as lightweight threads. Coroutines were considered experimental until Kotlin version 1.3. Like Java threads, Kotlin coroutines are not simple and can require considerable effort to master and to use effectively. It would take another whole article this size just to provide a complete overview of coroutines.

What’s missing from Kotlin?

The answer to this question is … not much. Still, there are a few Java features that I miss when working with Kotlin. They are not essential, and there are workarounds, but I find the Kotlin workarounds to be somewhat more awkward than their Java counterparts.

Coercion for numeric types

The first Java feature that I miss when I’m programming in Kotlin is type coercion (also known as implicit type conversion) for numeric types. In Java, if n has type int and x has type double, we can write x = n to assign the integer value to a double. The coercion from an integer type to a floating point type is well understood and has been present in most programming languages since Fortran. But in Kotlin, if n has type Int and x has type Double, we have to write x = n.toDouble().

There is one exception to this restriction in that integer literals can be assigned to variables of the type Byte or Short provided that the literals are within the range of that type. This exception is permitted since the compiler can verify that the value is allowed for that type.

Kotlin’s lack of type coercions for numeric types is a minor inconvenience, and you can read Kotlin’s documentation to find out why the language requires explicit type conversion. Still, I occasionally miss simple numeric coercions when programming in Kotlin.

Static fields

The second Java feature I miss is the ability to declare static fields for a class. Evidently, Kotlin’s language designers consider static fields to be less “object-oriented,” but personally I was never bothered by the fact that some data values were stored with the class object rather than with each instance.

As an example, consider the Fraction class that I introduced in Listing 15. Let’s say I want to include two constants representing the common values 0 and 1. Listing 19 shows how these might be declared in Java.

Listing 19. Static fields in Java


public static final Fraction ZERO = new Fraction(0);
public static final Fraction ONE  = new Fraction(1);

Kotlin’s solution is to create what it calls a companion object, which is defined within the Fraction class. Listing 20 illustrates how this would look in Kotlin. Again, this is only a minor inconvenience, but personally I like the Java version better.

Listing 20. A companion object in Kotlin


companion object   // defined within the Fraction class
  {
    val ZERO = Fraction(0)
    val ONE  = Fraction(1)
  }

It is frequently the case that static methods can be implemented as top-level functions in Java, but if you really need something roughly equivalent to a static method, it too can be implemented in a Kotlin companion object.

Is adopting Kotlin worth the learning curve?

Many programming languages were originally developed to overcome the limitations or pain points of existing programming languages. Bjarne Stroustrup created C++ because he wanted a language with object-oriented capabilities and the speed of C, and James Gosling developed Java as a response to general dissatisfaction with C++. Newer languages like Kotlin, Scala, and Clojure have likewise emerged as alternatives to Java.

Will Google’s promotion of Kotlin as the preferred language for Android be enough to push Kotlin to the forefront of developers’ minds? I recently exchanged emails with noted author and Java expert Cay Horstmann, who had this to say about the adoption of programming languages:

Major language shifts occur when the new language offers something that is greater than the pain of switching. With C, the benefit was efficiency, portability, and a usable set of libraries. With Java, the benefit was garbage collection and an even more usable set of libraries.

There are successful incremental changes, in particular from C to C++. But it was special because there was no pain. You could take your (ANSI) C code and it compiled as C++ code.

I don’t know if the incremental change from Java to Kotlin gives enough benefits to be worth the pain. Look at JavaScript, where there are saner alternatives, and the pain of switching prevents the switch from happening on a large scale.

Java has been my preferred programming language for more than 20 years, and while it is a great language, it also has its share of detractors. Like all widely adopted programming languages, Java has evolved over time to address shortcomings and limitations and to improve programmer efficiency. But evolving an existing language requires strong consideration of compatibility with previous versions. Sometimes creating a new programming language is a better alternative. Is Kotlin better than Java? In many respects, the answer is “yes.” Are the advantages of switching to Kotlin worth the pain? The jury is still out on that question, but clearly Kotlin deserves serious consideration for future software development projects, especially Android applications.

Professor of Mathematics and Computer Science

John I. Moore, Jr., Professor of Mathematics and Computer Science at The Citadel, has a wide range of experience in both industry and academia, with specific expertise in the areas of object-oriented technology, software engineering, compilers, mobile applications, and applied mathematics. For more than three decades he has designed and developed software using relational databases and several high-order languages, and he has worked extensively in Java since version 1.1. In addition, he has developed and taught numerous academic courses and industrial seminars on advanced topics in mathematics and computer science, training hundreds of mathematicians and software professionals throughout the United States and Canada.

More from this author