Which is better, Kotlin or Java in the future?

Introduction

This article is the 12th day article of Kotlin Advent Calendar 2019.

I think Kotlin has been said to be modern compared to Java. However, the release cycle of Java is once every six months, and it is becoming a better language than before, so Java is better when considering future maintainability. There is also an opinion that you may be wondering which one to choose when selecting a language. So, this time, I would like to compare the latest Java trends with Kotlin and compare which one is more modern.

The comparison targets are the latest version of Kotlin and Java 12 or later features. Regarding Java, version 13 was released on September 18, 2019, but this time I would like to compare some features that are likely to enter JDK 14 and later under development. For the features before JDK11, I think there are many other good articles, so please refer to them. Or it may be added when there is a margin. </ font>

Regarding the functions added in each version of the JDK, @ nowokay's article is pretty neat and easy to understand, so I would like to refer to it and compare it with Kotlin.

By the way, Kotlin has just begun to touch and I think there are some parts that I do not understand well, so please do Masakari.

Premise

  • Kotlin 1.3.61
  • Java OpenJDK 14 Early-Access Build 25
    (I used sdkman this time)

Compare with Java 12

Reference: Summary of new Java12 features

The Preview version of Switch Expressions is also included in 12, but since it is still being fixed in JDK 14, I will explain it in JDK 14.

CompactNumberFormat CompactNumberFormat was introduced in Java 12, but Kotlin doesn't have a similar feature.

Java


     NumberFormat cnf = NumberFormat.getCompactNumberInstance();
     System.out.println(cnf.format(10000));               //10 000
     System.out.println(cnf.format(10000_0000));          //One hundred million
     System.out.println(cnf.format(10000_0000_0000L));    //1 trillion
}

Kotlin


    fun format(number: Long): String {
        return when {
            (number >= 10F.pow(12)) -> {
                floor(number / 10F.pow(12)).toLong().toString() + "Trillion"
            }
            (number >= 10F.pow(8)) -> {
                floor(number / 10F.pow(8)).toLong().toString() + "Billion"
            }
            (number >= 10F.pow(4)) -> {
                floor(number / 10F.pow(4)).toLong().toString() + "Ten thousand"
            }
            else -> {
                number.toString()
            }
        }
    }
    println(format(10000))               //10 000
    println(format(10000_0000))          //One hundred million
    println(format(10000_0000_0000L))    //1 trillion

Since there are only 3 units, I tried to make it a little forcibly. Java is better here, isn't it? It seems that there are few opportunities to use it.

String.indent

If String.indent is used, the specified argument will be indented and displayed.

Java


    String s = "Kotlin";
    System.out.println(s);
    System.out.println(s.indent(2));
    // Kotlin
    //  Kotlin

In the case of Kotlin, use String.prependIndent and indent with the character specified in it. This time, I put 2 blank characters, so 2 characters will be indented.

Kotlin


    val s = "Kotlin"
    println(s)
    println(s.prependIndent("  "))
    // Kotlin
    //  Kotlin

String.transform

Java


    var addresses = Map.of("Mike", "Fukuoka", "John", "Tokyo");
    var population = Map.of("Tokyo", 30000000, "Fukuoka", 2000000);
    var name = "Mike";
    System.out.println(name.transform(addresses::get).transform(population::get));  // 2000000

Kotlin


fun main() {
    val addresses = mapOf("Mike" to "Fukuoka", "John" to "Tokyo")
    val population = mapOf("Tokyo" to 30000000, "Fukuoka" to 2000000)
    val name = "Mike"

    println(name.let(addresses::get)?.let(population::get))  // 2000000
}

In Kotlin, you can write it in the same way as String.transform by using the let function.

@rmakiyama and @ anatawa12 taught me how to write a String.transform in Kotlin! Thank you very much. </ font>

Collectors.teeing

This also seems difficult to achieve in one line with Kotlin. I prepared a function instead and realized it.

Java


    Map.Entry<String, Long> map = Stream.of("aaa", "", "bbb", "ccc").
                filter(Predicate.not(String::isEmpty)).
                collect(Collectors.teeing(
                    Collectors.joining(","),
                    Collectors.counting(),
                    Map::entry));
    System.out.println(map);  // aaa,bbb,ccc=3

Kotlin


    fun listToMap(list: List<String>): Map<String, Int> {
        return mutableMapOf(list.joinToString(",") to list.count())
    }
    val list = mutableListOf("aaa", "", "bbb", "ccc")
                    .filter { !it.isBlank() }
                    .toList()
    println(listToMap(list))  // {aaa,bbb,ccc=3}

Files.mismatch

It is an API to check if the contents of File are different.

Java


	Path filePath1 = Path.of("./com/example/jdk12/FilesMismatchFile1.txt");
	Path filePath2 = Path.of("./com/example/jdk12/FilesMismatchFile2.txt");

	long mismatchRow = Files.mismatch(filePath1, filePath2);
	if (mismatchRow == -1) {
	    System.out.println("file is same");
	} else {
	    System.out.println("file is diffarent¥nrow:" + String.valueOf(mismatchRow));
	}
        //If the files are the same
        // file is same

        //If the files are different
        // file is diffarent
        // row:24

Kotlin doesn't seem to have a similar API. Since it is difficult to write the process, I will omit it here.

String.isEmpty

Java


    String s = "";
    String n = null;
    if (s.isEmpty()) {
        System.out.println("s is Empty");
    }
    if (n.isEmpty()) {
        System.out.println("n is Empty");
    }

    // s is Empty
    // Exception in thread "main" java.lang.NullPointerException
    //   at com.example.jdk12.StringIsEmpty.main(StringIsEmpty.java:10)

Kotlin


    val s = "";
    val n: String? = null;
    if (s.isEmpty()) {
        println("s is Empty")
    }
    if (n.isNullOrEmpty()) {
        println("n is Null")
    }

    // s is Empty
    // n is Null

Kotlin gets a compile error when using n.isEmpty (). After all Null safety is good.

In Java, if you use ʻisEmpty () for a Null variable, NPE will output it. You can also compile using ʻOptional <String> n = null; and NPE will occur. I wonder if Java will be able to do Null and Empty checks at the same time without using a library.

Compare with Java 13

Reference: https://openjdk.java.net/projects/jdk/13/

Switch Expressions are likely to change a little more from JDK 13, so I'll compare them later.

Text Blocks

https://openjdk.java.net/jeps/368 It's likely to change a little more, but I don't think it will change much.

Java


    String s = """
	       ultra soul
	       OCEAN
	       """;
    System.out.println(s);
    // ultra soul
    // OCEAN

Kotlin


    val s = """
            ultra soul
            OCEAN
            """
    print(s)
    // ultra soul
    // OCEAN

It's almost the same in Java and Kotlin.

Compare with Java 14 or later

Well, it's not officially released from here yet, but let's compare Kotlin with what is under development.

Reference: Java syntax changes being considered by Amber

Records(JEP 359)

Records automatically prepares code such as hashCode ʻequals`` toString`, which is redundant in Java beans. It's very simple and nice.

Java


    record Point(int x, int y) {}

    Point point1 = new Point(5, 10);
    Point point2 = new Point(5, 10);
    System.out.println(point1.x());                   // 5
    System.out.println(point1);                       // Point[x=5, y=10]
    System.out.println(point1.equals(point2));        // true

And Kotlin has a data class.

Kotlin


    data class Point(val x: Int, val y: Int)
    val point1 = Point(5, 10)
    val point2 = Point(5, 10)
    println(point1.x)                 // 5
    println(point1)                   // Point(x=5, y=10)
    println(point1.equals(point2))    // true

Record seems to refer to Scala's case class, Kotlin's data class, C #'s record types, etc.

Sealed Types(JEP 360)

Sealed Types are used to limit class inheritance This is convenient to use as an enum.

Java


    sealed interface HondaCar {};

    public class Demio implements HondaCar {
        public String getName() {
            return "Demio";
        }
    }

    public class Vezel implements HondaCar {
        public String getName() {
            return "Vezel";
        }
    }

Kotlin


sealed class HondaCar
class Demio: HondaCar() {
    fun getName():String { return "Demio" }
}
class Vezel: HondaCar() {
    fun getName():String { return "Vezel" }
}

Switch in Record

I don't understand Java code very much here, but I will write an image. (I'm wondering if I saw an example of using sealed / record with switch somewhere, but I haven't found any issues yet, so if I find one, I'll paste the detailed URL)

Java


    // sealed
    sealed interface HondaCar permits Demio, Vezel {}
    record Demio() implements HondaCar {}
    record Vezel() implements HondaCar {}

    // use
    int price = switch(hondaCar) {
                    case Demio(int price) -> "Demio";
                    case Vezel(int price) -> "Vezel";
                    //No need for default statement as sealed knows that Demio and Vezel are the only choices
                    // default -> throw new IllegalStateException("Error");
                };

Kotlin is already available in when expressions.

Kotlin


    // sealed
    sealed class HondaCar
    class Demio: HondaCar()
    class Vezel: HondaCar()

    // use
   val hondaName = when(hondaCar) {
        is Demio -> "Demio"
        is Vezel -> "Vezel"
        //No default required
    }
    println(hondaName)

By the way, if you don't use sealed in Kotlin, default (else) is required.

Kotlin error


    // interface
    interface NissanCar
    class Leaf: NissanCar
    class Juke: NissanCar

    // use
    val nissanCar: NissanCar = Leaf()
    val nissanName = when(nissanCar) {
        is Leaf -> "Leaf"
        is Juke -> "Juke"
        //Since there is no else, the following error is output
        // 'when' expression must be exhaustingstive, add necssary 'else' branch
    }
    println(nissanName)

Switch Expressions (JEP 361)

https://openjdk.java.net/jeps/361 Originally I thought that Java switch was difficult to use, but it has been considered since JDK12 (JEP 325), and it seems that various improvements have been made. I just want to compare it with Kotlin, so I'll just list the parts that change a lot.

Multiple cases that fit in the same block can be described at the same time

Java


    //originally
    switch (day) {
        case MONDAY:
        case FRIDAY:
        case SUNDAY:
            System.out.println(6);
            break;
        case TUESDAY:
            System.out.println(7);
            break;
    }

    //After improvement
    switch (day) {
        case MONDAY, FRIDAY, SUNDAY -> System.out.println(6);
        case TUESDAY                -> System.out.println(7);
        case THURSDAY, SATURDAY     -> System.out.println(8);
        case WEDNESDAY              -> System.out.println(9);
    }

Multiple Kotlin can be listed at the same time.

Kotlin


    when (day) {
        Day.MONDAY, Day.FRIDAY, Day.SUNDAY -> println(6)
        Day.TUESDAY -> println(7)
        Day.THURSDAY,  Day.SATURDAY -> println(8)
        Day.WEDNESDAY -> println(9)
    }

switch can be used as an expression

As mentioned in Sealed Types, it seems that the result of switch can be used as an expression and stored in a variable. Currently, when using switch in a loop such as a for statement, it seems that the movement of break and continue is being adjusted.

Java


    int j = switch (day) {
        case MONDAY  -> 0;
        case TUESDAY -> 1;
        default      -> {
            int k = day.toString().length();
            int result = f(k);
            yield result;
        }
    };

Kotlin


    val j = when (day) {
        is Monday -> 0;
        is Tuesday -> 1;
        else -> {
            val k = day.toString().length
            k
        }
    }

    //Kotlin also supports break and continue in the switch(It's useless logic)
    loop@ for (i in 1..100) {
        when (day) {
            is Monday -> j = 0;
            is Tuesday -> j = 1;
            else -> {
                j = day.toString().length
                break@loop
            }
        }
    }

Pattern Matching for instanceof (JEP 305)

After checking the type with instanceof, the type is fixed, so Pattern Matching for instanceof can be used by storing it in a variable as it is.

Java


    //until now
    if (o instanceof String) {
        //You cannot use o directly as a String type
        // System.out.println(o.length());

        //It is necessary to cast it to String type once and then use it.
        String s = (String)o;
        System.out.println(s.length());     // 27
    }

    //from now on
    Object o = "Pattern Match of instanceof";
    //Can be stored in a variable at the same time as instanceof
    if (o instanceof String s) {
        System.out.println(s.length());    // 27
    }

    //It seems that switch will also be available.(OpenJDK 14 Early-Still in Access Build 25)
    // https://cr.openjdk.java.net/~briangoetz/amber/pattern-match.html
    switch (o) {
        case Integer i -> System.out.println(i);
        case String s -> System.out.println(s.length());
    }

Kotlin can be used as it is without storing it in a variable.

Kotlin


    val o: Any = "Pattern Match of instanceof"
    if (o is String) {
        println(o.length)    // 27
    }
    when(o) {
        is Int -> println("Int")
        is String -> {
            val s:String = o
            println(s)
        }
    }
    // 27

Summary

First of all, I thought it was amazing that Java has evolved so much.

Previously Kotlin was the best. I think Java was a disappointing image, but I have the impression that Java has also adopted modern languages such as Kotlin toward JDK17, and the difference is disappearing. And I was surprised at Kotlin, which has already adopted such a new Java syntax.

From the perspective of comparing Java and Kotlin languages, Kotlin still has the advantages of being null-safe and easy to write functional types, and as of September 2021, when the next LTS, JDK17, is released, which one I wondered if I could choose it.

I will write in Kotlin for the new project I will make from now on, so I would like to write a separate article about what I was interested in while actually dealing with it in the future.

Finally

It is a reflection that the color of Java has become stronger even though it is an ad-care of Kotlin.

Also, I'm sorry that it has become an expression that other people reuse the content already in the article. For the comparison between Kotlin and the new Java, there was a lot of content that I inevitably suffered even though there was little information, so please forgive me.

I thought while writing, but it seems better to compare JDK9 to JDK11 as well. We will respond when you can afford it.

Kotlin hasn't touched it in business at the moment, so I think there are some parts that I don't understand, so I'd like to gradually correct it as my understanding deepens.

Reference information

Java 12 new feature summary Java syntax changes being considered by Amber

https://openjdk.java.net/projects/jdk/13/ https://openjdk.java.net/projects/amber/

Recommended Posts