Kenapa Kotlin? Lapan ciri yang dapat meyakinkan pemaju Java untuk beralih

Dirilis secara resmi pada tahun 2016, Kotlin telah menarik banyak perhatian dalam beberapa tahun terakhir, terutama sejak Google mengumumkan sokongannya terhadap Kotlin sebagai alternatif Java pada platform Android. Dengan keputusan yang diumumkan baru-baru ini untuk menjadikan Kotlin sebagai bahasa pilihan untuk Android, anda mungkin tertanya-tanya apakah sudah waktunya untuk mula belajar bahasa pengaturcaraan baru. Sekiranya demikian, artikel ini dapat membantu anda membuat keputusan.

Sejarah pembebasan Kotlin

Kotlin diumumkan pada tahun 2011, tetapi rilis stabil pertama, versi 1.0, tidak muncul hingga 2016. Bahasa ini bebas dan sumber terbuka, dikembangkan oleh JetBrains dengan Andrey Breslav berfungsi sebagai pereka bahasa utama. Kotlin 1.3.40 dikeluarkan pada bulan Jun 2019.

Mengenai Kotlin

Kotlin adalah bahasa pengaturcaraan moden yang ditaip secara statik yang menampilkan konstruk pengaturcaraan berorientasikan objek dan berfungsi. Ia mensasarkan beberapa platform, termasuk JVM, dan dapat beroperasi sepenuhnya dengan Java. Dalam banyak cara, Kotlin adalah seperti apa Java jika ia dirancang hari ini. Dalam artikel ini saya memperkenalkan lapan ciri Kotlin yang saya yakin pembangun Java akan teruja untuk mengetahui.

  1. Sintaks yang bersih dan padat
  2. Sistem jenis tunggal (hampir)
  3. Keselamatan batal
  4. Fungsi dan pengaturcaraan fungsional
  5. Kelas data
  6. Sambungan
  7. Pengendalian berlebihan
  8. Objek tingkat atas dan corak Singleton

Hai dunia! Kotlin berbanding Java

Penyenaraian 1 menunjukkan "Hello, world!" fungsi yang ditulis dalam Kotlin.

Penyenaraian 1. "Hai, dunia!" di Kotlin

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

Sesederhana itu, contoh ini menunjukkan perbezaan utama dari Java.

  1. mainadalah fungsi peringkat atasan; iaitu fungsi Kotlin tidak perlu bersarang di dalam kelas.
  2. Tidak ada public staticpengubah. Walaupun Kotlin mempunyai pengubah penglihatan, yang lalai adalah publicdan dapat dihilangkan. Kotlin juga tidak mendukung staticpengubah, tetapi tidak diperlukan dalam hal ini kerana mainmerupakan fungsi tingkat atas.
  3. Sejak Kotlin 1.3, parameter array-of-string maintidak diperlukan dan boleh dihilangkan jika tidak digunakan. Sekiranya diperlukan, ia akan dinyatakan sebagai args : Array.
  4. Tidak ada jenis pengembalian yang ditentukan untuk fungsi tersebut. Di mana Java menggunakan void, Kotlin menggunakan Unit, dan jika jenis fungsi kembali Unit, ia mungkin dihilangkan.
  5. Tidak ada titik koma dalam fungsi ini. Di Kotlin, titik koma adalah pilihan, dan oleh itu jeda garis adalah penting.

Itu gambaran keseluruhan, tetapi ada banyak lagi yang perlu dipelajari mengenai bagaimana Kotlin berbeza dari Java dan, dalam banyak kes, bertambah baik.

1. Sintaks yang lebih bersih dan padat

Java sering dikritik kerana terlalu verbose, tetapi beberapa verbosity dapat menjadi teman anda, terutama jika itu membuat kod sumber lebih mudah difahami. Tantangan dalam reka bentuk bahasa adalah untuk mengurangkan ketajaman sambil mengekalkan kejelasan, dan saya fikir Kotlin jauh untuk menghadapi cabaran ini.

Seperti yang anda lihat dalam Penyenaraian 1, Kotlin tidak memerlukan titik koma, dan ini memungkinkan untuk menghilangkan jenis pengembalian untuk Unitfungsi. Mari kita pertimbangkan beberapa ciri lain yang membantu menjadikan Kotlin sebagai alternatif yang lebih bersih dan ringkas daripada Java.

Taipkan inferens

Di Kotlin anda boleh menyatakan pemboleh ubah sebagai var x : Int = 5, atau anda boleh menggunakan versi yang lebih pendek tetapi sama jelasnya var x = 5. (Sementara Java sekarang mendukung vardeklarasi, fitur itu tidak muncul hingga Java 10, lama setelah fitur itu muncul di Kotlin.)

Kotlin juga memiliki valpernyataan untuk pemboleh ubah hanya-baca, yang serupa dengan pemboleh ubah Java yang telah dinyatakan sebagai final, yang bermaksud pemboleh ubah tidak dapat ditugaskan kembali. Penyenaraian 2 memberikan contoh.

Penyenaraian 2. Pemboleh ubah baca sahaja di Kotlin

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

Hartanah berbanding medan

Di mana Java mempunyai ladang, Kotlin mempunyai sifat. Properti diisytiharkan dan diakses dengan cara yang serupa dengan bidang umum di Jawa, tetapi Kotlin menyediakan pelaksanaan default fungsi aksesor / mutator untuk sifat; iaitu, Kotlin menyediakan get()fungsi untuk valsifat dan kedua get()- duanya dan set()fungsi untuk varsifat. Versi disesuaikan get()dan set()dapat dilaksanakan apabila perlu.

Sebilangan besar hartanah di Kotlin akan mempunyai medan sokongan, tetapi adalah mungkin untuk menentukan harta yang dihitung , yang pada dasarnya adalah get()fungsi tanpa bidang sokongan. Contohnya, kelas yang mewakili seseorang mungkin mempunyai harta dateOfBirthdan harta yang dikira untuk age.

Lalai berbanding import eksplisit

Java secara implisit mengimport kelas yang ditentukan dalam pakej java.lang, tetapi semua kelas lain mesti diimport secara eksplisit. Akibatnya, banyak fail sumber Java dimulakan dengan mengimport kelas koleksi dari java.util, kelas I / O dari java.io, dan sebagainya. Secara lalai, Kotlin tersirat import kotlin.*, yang kira-kira sama dengan Java mengimport java.lang.*, tetapi Kotlin juga import kotlin.io.*, kotlin.collections.*dan kelas dari beberapa pakej lain. Oleh kerana itu, fail sumber Kotlin biasanya memerlukan import eksplisit yang lebih sedikit daripada fail sumber Java, terutama untuk kelas yang menggunakan koleksi dan / atau I / O standard.

Tiada panggilan untuk 'baru' untuk pembina

Di Kotlin, kata kunci newtidak diperlukan untuk membuat objek baru. Untuk memanggil konstruktor, gunakan nama kelas dengan tanda kurung. Kod Java

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

boleh ditulis seperti berikut di Kotlin:

 var s = Student(...) 

Templat rentetan

String boleh mengandungi ekspresi templat , yang merupakan ekspresi yang dinilai dengan hasil yang dimasukkan ke dalam string. Ungkapan templat dimulakan dengan tanda dolar ($) dan terdiri daripada nama mudah atau ungkapan sewenang-wenang dalam kurungan kurung. Templat rentetan dapat memendekkan ungkapan rentetan dengan mengurangkan keperluan penggabungan rentetan eksplisit. Sebagai contoh, kod Java berikut

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

boleh diganti dengan kod Kotlin yang lebih pendek tetapi setara.

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

Memanjangkan dan melaksanakan

Pengaturcara Java tahu bahawa kelas dapat extendkelas lain dan implementsatu atau lebih antara muka. Di Kotlin, tidak ada perbezaan sintaksis antara kedua konsep yang serupa ini; Kotlin menggunakan titik dua untuk kedua-duanya. Contohnya, kod Java

 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 } 

Seterusnya, saya membandingkan prestasi dua versi Kotlin dengan Java dengan doubledan Java dengan Double, menjalankan keempat-empat penanda aras pada komputer riba saya sekarang. Oleh kerana terdapat sedikit "kebisingan" dalam menjalankan setiap penanda aras, saya menjalankan semua versi tiga kali dan rata-rata hasilnya, yang diringkaskan dalam Jadual 1.

Jadual 1. Prestasi jangka masa penanda aras pendaraban matriks

Hasil masa (dalam beberapa saat)
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 menukar versi Java penanda aras SciMark menjadi Kotlin. IntelliJ IDEA secara automatik ditukar double[]dan int[]di Java menjadi DoubleArraydan IntArraydi Kotlin. Saya kemudian membandingkan versi Java menggunakan primitif dengan versi Kotlin menggunakan DoubleArraydan IntArray. Seperti sebelumnya, saya menjalankan kedua-dua versi tiga kali dan rata-rata hasilnya, yang diringkaskan dalam Jadual 2. Sekali lagi jadual menunjukkan hasil yang hampir sama.

Jadual 2. Prestasi jangka masa penanda aras SciMark

Persembahan (dalam Mflops)
Jawa Kotlin
1818.22 1815.78