Mengapa Kotlin? Delapan fitur yang bisa meyakinkan developer Java untuk beralih

Dirilis resmi pada tahun 2016, Kotlin telah menarik banyak perhatian dalam beberapa tahun terakhir, terutama sejak Google mengumumkan dukungannya terhadap Kotlin sebagai alternatif Java pada platform Android. Dengan keputusan yang baru-baru ini diumumkan untuk menjadikan Kotlin sebagai bahasa pilihan untuk Android, Anda mungkin bertanya-tanya apakah sudah waktunya untuk mulai mempelajari bahasa pemrograman baru. Jika demikian, artikel ini dapat membantu Anda memutuskan.

Sejarah rilis Kotlin

Kotlin diumumkan pada tahun 2011, tetapi rilis stabil pertama, versi 1.0, tidak muncul hingga 2016. Bahasa ini gratis dan bersumber terbuka, dikembangkan oleh JetBrains dengan Andrey Breslav sebagai perancang bahasa utamanya. Kotlin 1.3.40 dirilis pada Juni 2019.

Tentang Kotlin

Kotlin adalah bahasa pemrograman modern dengan tipe statis yang menampilkan konstruksi pemrograman berorientasi objek dan fungsional. Ini menargetkan beberapa platform, termasuk JVM, dan sepenuhnya dapat dioperasikan dengan Java. Dalam banyak hal, Kotlin akan terlihat seperti Java jika dirancang saat ini. Pada artikel ini saya memperkenalkan delapan fitur Kotlin yang saya yakin pengembang Java akan senang untuk menemukannya.

  1. Bersih, sintaks yang kompak
  2. Sistem tipe tunggal (hampir)
  3. Keamanan nol
  4. Fungsi dan pemrograman fungsional
  5. Kelas data
  6. Ekstensi
  7. Operator kelebihan beban
  8. Objek tingkat atas dan pola Singleton

Halo Dunia! Kotlin versus Java

Kode 1 menunjukkan pesan "Halo, dunia!" fungsi yang ditulis di Kotlin.

Daftar 1. "Halo, dunia!" di Kotlin

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

Sesederhana itu, contoh ini mengungkapkan perbedaan utama dari Java.

  1. mainadalah fungsi tingkat atas; artinya, fungsi Kotlin tidak perlu disarangkan di dalam kelas.
  2. Tidak ada public staticpengubah. Meskipun Kotlin memiliki pengubah visibilitas, defaultnya adalah publicdan dapat dihilangkan. Kotlin juga tidak mendukung staticpengubah, tetapi tidak diperlukan dalam kasus ini karena mainmerupakan fungsi tingkat atas.
  3. Sejak Kotlin 1.3, parameter array-of-strings untuk maintidak diperlukan dan dapat dihilangkan jika tidak digunakan. Jika perlu, itu akan dideklarasikan sebagai args : Array.
  4. Tidak ada tipe pengembalian yang ditentukan untuk fungsi tersebut. Jika Java menggunakan void, Kotlin menggunakan Unit, dan jika tipe kembalian dari suatu fungsi adalah Unit, ia dapat dihilangkan.
  5. Tidak ada titik koma dalam fungsi ini. Di Kotlin, titik koma bersifat opsional, dan oleh karena itu jeda baris bersifat signifikan.

Itu adalah ringkasannya, tapi masih banyak yang harus dipelajari tentang perbedaan Kotlin dari Java dan, dalam banyak kasus, memperbaikinya.

1. Sintaks yang lebih rapi dan lebih ringkas

Java sering dikritik karena terlalu bertele-tele, tetapi beberapa verbositas dapat menjadi teman Anda, terutama jika itu membuat kode sumber lebih mudah dipahami. Tantangan dalam desain bahasa adalah mengurangi verbositas sambil tetap mempertahankan kejelasan, dan menurut saya Kotlin dapat mengatasi tantangan ini.

Seperti yang Anda lihat di Listing 1, Kotlin tidak memerlukan titik koma, dan memungkinkan penghilangan tipe kembalian untuk Unitfungsi. Mari pertimbangkan beberapa fitur lain yang membantu membuat Kotlin menjadi alternatif yang lebih rapi dan ringkas untuk Java.

Ketik inferensi

Di Kotlin, Anda dapat mendeklarasikan variabel sebagai var x : Int = 5, atau Anda dapat menggunakan versi yang lebih pendek tetapi sama jelasnya var x = 5. (Meskipun Java sekarang mendukung vardeklarasi, fitur tersebut tidak muncul hingga Java 10, lama setelah fitur tersebut muncul di Kotlin.)

Kotlin juga memiliki valdeklarasi untuk variabel hanya-baca, yang serupa dengan variabel Java yang telah dideklarasikan sebagai final, artinya variabel tersebut tidak dapat ditetapkan kembali. Daftar 2 memberi contoh.

Kode 2. Variabel hanya-baca di Kotlin

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

Properti versus bidang

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 

would be written more simply in Kotlin as follows:

 class Student : Person, Comparable 

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.

Switch expressions in Java

Java 12 introduced switch expressions. Similar to Kotlin's when, Java's switch expressions do not require break statements, and they can be used as statements or expressions. See "Loop, switch, or take a break? Deciding and iterating with statements" for more about switch expressions in Java.

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 and one using Array . Listing 5 shows the Kotlin implementation using Array.

Listing 5. Matrix multiplication in Kotlin

 fun multiply(a : Array, b : Array) : Array { 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 } 

Selanjutnya, saya membandingkan kinerja dua versi Kotlin dengan Java doubledan Java dengan Double, menjalankan keempat tolok ukur pada laptop saya saat ini. Karena ada sejumlah kecil "gangguan" dalam menjalankan setiap benchmark, saya menjalankan semua versi tiga kali dan rata-rata hasilnya, yang dirangkum dalam Tabel 1.

Tabel 1. Kinerja runtime benchmark perkalian matriks

Hasil berwaktu (dalam detik)
Jawa

( double)

Jawa

( Double)

Kotlin

( DoubleArray)

Kotlin

( Array)

7.30 29.83 6.81 15.82

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, 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.

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.

Dengan bantuan IntelliJ IDEA, saya mengonversi benchmark SciMark versi Java ke Kotlin. IntelliJ IDEA secara otomatis dikonversi double[]dan int[]di Java ke DoubleArraydan IntArraydi Kotlin. Saya kemudian membandingkan versi Java yang menggunakan primitif dengan versi Kotlin yang menggunakan DoubleArraydan IntArray. Seperti sebelumnya, saya menjalankan kedua versi tiga kali dan rata-rata hasilnya, yang dirangkum dalam Tabel 2. Sekali lagi tabel menunjukkan hasil yang kira-kira sebanding.

Tabel 2. Kinerja runtime benchmark SciMark

Performa (dalam Mflops)
Jawa Kotlin
1818.22 1815,78