Zašto Kotlin? Osam značajki koje bi mogle uvjeriti programere Java da se prebace

Službeno objavljen 2016. godine, Kotlin je privukao veliku pozornost posljednjih godina, pogotovo jer je Google najavio podršku Kotlinu kao alternativi Javi na Android platformama. S nedavno najavljenom odlukom da Kotlin postane preferirani jezik za Android, možda se pitate je li vrijeme da počnete učiti novi programski jezik. Ako je to slučaj, ovaj članak može vam pomoći u odluci.

Kotlinova povijest izdanja

Kotlin je najavljen 2011., ali prvo stabilno izdanje, verzija 1.0, pojavilo se tek 2016. Jezik je besplatan i otvoren izvor, razvio ga je JetBrains, a Andrey Breslav bio je vodeći dizajner jezika. Kotlin 1.3.40 objavljen je u lipnju 2019.

O Kotlinu

Kotlin je moderan, statički upisan programski jezik koji sadrži objektno orijentirane i funkcionalne programske konstrukcije. Cilja nekoliko platformi, uključujući JVM, i potpuno je interoperabilan s Javom. Na mnogo načina, Kotlin je ono što bi Java mogla izgledati da je dizajnirana danas. U ovom članku predstavljam osam značajki Kotlina za koje vjerujem da će ih programeri Java rado otkriti.

  1. Čista, kompaktna sintaksa
  2. Jednotipski sustav (gotovo)
  3. Nulta sigurnost
  4. Funkcije i funkcionalno programiranje
  5. Klase podataka
  6. Proširenja
  7. Preopterećenje operatera
  8. Objekti najviše razine i uzorak Singleton

Pozdrav svijete! Kotlin naspram Jave

Popis 1 prikazuje obavezno "Zdravo, svijete!" funkcija napisana u Kotlinu.

Popis 1. "Zdravo, svijete!" u Kotlinu

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

Koliko god bio jednostavan, ovaj primjer otkriva ključne razlike od Jave.

  1. mainje funkcija najviše razine; odnosno funkcije Kotlina ne trebaju biti ugniježđene unutar klase.
  2. Nema public staticmodifikatora. Iako Kotlin ima modifikatore vidljivosti, zadana vrijednost je publici može se izostaviti. Kotlin također ne podržava staticmodifikator, ali u ovom slučaju nije potreban jer mainje funkcija najviše razine.
  3. Od Kotlina 1.3, parametar niza nizova za mainnije potreban i može se izostaviti ako se ne koristi. Ako je potrebno, deklarirat će se kao args : Array.
  4. Za funkciju nije naveden tip povrata. Gdje Java koristi void, koristi se Kotlin Unit, a ako je vrsta povrata funkcije Unit, ona se može izostaviti.
  5. U ovoj funkciji nema zareza. U Kotlinu točke sa zarezom nisu obvezne, pa su stoga prekidi reda značajni.

To je pregled, ali još se puno toga može naučiti o tome kako se Kotlin razlikuje od Jave i, u mnogim slučajevima, poboljšava je.

1. Čistija, kompaktnija sintaksa

Java se često kritizira zbog previše opširnosti, ali neki opširnost može vam biti prijatelj, pogotovo ako izvorni kod učini razumljivijim. Izazov u jezičnom dizajnu je smanjiti opširnost uz zadržavanje jasnoće i mislim da Kotlin ide daleko u susret tom izazovu.

Kao što ste vidjeli u Popisu 1, Kotlin ne zahtijeva zarez i dopušta izostavljanje vrste return za Unitfunkcije. Razmotrimo nekoliko drugih značajki koje pomažu da Kotlin postane čišća i kompaktnija alternativa Javi.

Tip zaključivanje

U Kotlinu možete proglasiti varijablu kao var x : Int = 5ili možete upotrijebiti kraću, ali jednako jasnu verziju var x = 5. (Iako Java sada podržava vardeklaracije, ta se značajka pojavila tek Java 10, dugo nakon što se značajka pojavila u Kotlinu.)

Kotlin također ima valdeklaracije za varijable samo za čitanje, koje su analogne Java varijablama koje su deklarirane kao final, što znači da se varijabla ne može ponovno dodijeliti. Popis 2 daje primjer.

Popis 2. Varijable samo za čitanje u Kotlinu

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

Svojstva nasuprot poljima

Gdje Java ima polja, Kotlin ima svojstva. Svojstva se deklariraju i pristupa im se na način sličan javnim poljima u Javi, ali Kotlin pruža zadane implementacije funkcija pristupa / mutatora za svojstva; odnosno Kotlin pruža get()funkcije za valsvojstva i oboje get()i set()funkcije za varsvojstva. Prilagođene verzije get()i set()mogu se implementirati po potrebi.

Većina svojstava u Kotlinu imat će sigurnosna polja, ali moguće je definirati izračunato svojstvo , koje je u osnovi get()funkcija bez sigurnosnog polja. Na primjer, klasa koja predstavlja osobu može imati svojstvo za dateOfBirthi izračunato svojstvo za age.

Zadani nasuprot eksplicitnom uvozu

Java implicitno uvozi klase definirane u paketu java.lang, ali sve ostale klase moraju biti eksplicitno uvezene. Kao rezultat toga, mnoge Java izvorne datoteke počinju uvozom klasa kolekcije iz java.util, I / O klasa iz java.io, i tako dalje. Prema zadanim postavkama, Kotlin implicitno uvoza kotlin.*, što je otprilike analogno Java uvoza java.lang.*, ali Kotlin i uvoz kotlin.io.*, kotlin.collections.*, i klase iz nekoliko drugih paketa. Zbog toga Kotlin izvorne datoteke obično zahtijevaju manje eksplicitnog uvoza od Java izvornih datoteka, posebno za klase koje koriste zbirke i / ili standardni I / O.

Nema poziva na 'novo' za konstruktore

U Kotlinu ključna riječ newnije potrebna za stvaranje novog objekta. Da biste pozvali konstruktor, samo upotrijebite ime klase sa zagradama. Java kôd

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

u Kotlinu bi se moglo napisati kako slijedi:

 var s = Student(...) 

Predlošci niza

Nizovi mogu sadržavati izraze predloška , koji su izrazi koji se vrednuju rezultatima umetnutim u niz. Izraz predloška započinje znakom dolara ($) i sastoji se od jednostavnog naziva ili proizvoljnog izraza u kovrčavim zagradama. Predlošci nizova mogu skratiti izraze nizova smanjenjem potrebe za eksplicitnim spajanjem nizova. Kao primjer, sljedeći Java kôd

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

mogao zamijeniti kraćim, ali ekvivalentnim Kotlinovim kodom.

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

Proširuje i provodi

Java programeri znaju da klasa može extenddrugu klasu i implementjedno ili više sučelja. U Kotlinu nema sintaktičke razlike između ova dva slična pojma; Kotlin koristi debelo crijevo za oboje. Na primjer, Java kôd

 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 } 

Dalje, usporedio sam izvedbu dviju verzija Kotlina s Java i s doubleJava Double, radeći sva četiri mjerila na mom trenutnom prijenosnom računalu. Budući da postoji mala količina "buke" u pokretanju svakog mjerila, tri sam puta pokrenuo sve verzije i usredsredio rezultate koji su sažeti u tablici 1.

Tablica 1. Izvedbene performanse referentne vrijednosti umnožavanja matrice

Vremenski određeni rezultati (u sekundama)
Java

( double)

Java

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

Uz pomoć IntelliJ IDEA pretvorio sam Java verziju mjerila SciMark u Kotlin. IntelliJ IDEA se automatski pretvorio double[]i int[]na Javi u DoubleArrayi IntArrayu Kotlin. Zatim sam usporedio Java verziju pomoću primitiva s verzijom Kotlin koristeći DoubleArrayi IntArray. Kao i prije, tri sam puta pokrenuo obje verzije i usredsredio rezultate koji su sažeti u tablici 2. Još jednom tablica pokazuje približno usporedive rezultate.

Tablica 2. Izvedba izvedbe mjerila SciMark

Izvedba (u Mflops-ima)
Java Kotlin
1818.22 1815.78