למה קוטלין? שמונה תכונות שיכולות לשכנע את מפתחי Java לעבור

שוחרר רשמית בשנת 2016, קוטלין משך תשומת לב רבה בשנים האחרונות, במיוחד מאז שגוגל הודיעה על תמיכתה בקוטלין כחלופה לג'אווה בפלטפורמות אנדרואיד. עם ההחלטה שהוכרזה לאחרונה להפוך את קוטלין לשפה המועדפת על Android, ייתכן שאתה תוהה אם הגיע הזמן להתחיל ללמוד שפת תכנות חדשה. אם זה המקרה, מאמר זה יכול לעזור לך להחליט.

היסטוריית השחרורים של קוטלין

קוטלין הוכרז בשנת 2011, אך המהדורה היציבה הראשונה, גרסה 1.0, לא הופיעה עד 2016. השפה חופשית ופתוחה, שפותחה על ידי JetBrains עם אנדריי ברסלב המשמש כמעצב השפות הראשי שלה. קוטלין 1.3.40 שוחרר ביוני 2019.

על קוטלין

קוטלין היא שפת תכנות מודרנית ומוקלדת באופן סטטי, הכוללת מבני תכנות מונחים עצמים ופונקציונליים כאחד. היא מכוונת למספר פלטפורמות, כולל JVM, והיא ניתנת לפעולה מלאה בין Java. במובנים רבים, קוטלין הוא איך שג'אווה עשויה להיראות אם היא תוכננה היום. במאמר זה אני מציג שמונה מאפיינים של Kotlin שלדעתי מפתחי Java יתלהבו לגלותם.

  1. תחביר נקי וקומפקטי
  2. מערכת מסוג יחיד (כמעט)
  3. בטיחות אפסית
  4. פונקציות ותכנות פונקציונלי
  5. שיעורי נתונים
  6. הרחבות
  7. העמסת יתר על המפעיל
  8. אובייקטים ברמה העליונה ודפוס סינגלטון

שלום עולם! קוטלין מול ג'אווה

רישום 1 מציג את החובה "שלום, עולם!" פונקציה שנכתבה בקוטלין.

רישום 1. "שלום עולם!" בקוטלין

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

עד כמה שזה פשוט, דוגמה זו מגלה הבדלים מרכזיים מג'אווה.

  1. mainהיא פונקציה ברמה העליונה; כלומר, אין צורך לקנן את פונקציות הקוטלין בתוך הכיתה.
  2. אין public staticשינויים. בעוד שלקוטלין יש משתני נראות, ברירת המחדל היא publicוניתן להשמיט אותה. Kotlin גם אינו תומך staticבשינוי, אך אין צורך במקרה זה משום mainשהוא פונקציה ברמה העליונה.
  3. מאז Kotlin 1.3, פרמטר מערך המיתרים עבור mainאינו נדרש וניתן להשמיטו אם לא נעשה בו שימוש. במידת הצורך, יוכרז כ- args : Array.
  4. לא מוגדר סוג החזרה עבור הפונקציה. כאשר Java משתמש void, Kotlin משתמש Unit, ואם סוג החזרה של פונקציה הוא Unit, זה יכול להיות מושמט.
  5. בפונקציה זו אין נקודה-פסיק. בקוטלין נקודה-פסיק היא אופציונלית, ולכן מעברי קו משמעותיים.

זוהי סקירה כללית, אך יש הרבה יותר מה ללמוד כיצד קוטלין שונה מג'אווה ובמקרים רבים הוא משתפר בה.

1. תחביר נקי וקומפקטי יותר

לעתים קרובות מתחים ביקורת על ג'אווה על היותה מילולית מדי, אך מילוליות מסוימת יכולה להיות חבר שלך, במיוחד אם זה הופך את קוד המקור ליותר מובן. האתגר בתכנון השפה הוא להפחית את הפירוט תוך שמירה על בהירות, ואני חושב שקוטלין עושה דרך ארוכה לעמוד באתגר זה.

כפי שראית ברישום 1, Kotlin אינו דורש נקודה-פסיק, והוא מאפשר השמטת סוג ההחזרה עבור Unitפונקציות. בואו ניקח בחשבון כמה תכונות אחרות שעוזרות להפוך את קוטלין לחלופה נקייה וקומפקטית יותר ל- Java.

סוג מסקנה

בקוטלין תוכלו להכריז על משתנה כ- var x : Int = 5, או להשתמש בגרסה הקצרה אך הברורה באותה מידה var x = 5. (בעוד שג'אווה תומכת כעת varבהצהרות, תכונה זו לא הופיעה עד שג'אווה 10, זמן רב לאחר שהתכונה הופיעה בקוטלין.)

לקוטלין יש valהצהרות גם על משתנים לקריאה בלבד, שהם אנלוגיים למשתני Java שהוכרזו finalכמשמעותם, כלומר לא ניתן להקצות מחדש את המשתנה. רישום 2 נותן דוגמה.

רישום 2. משתנים לקריאה בלבד בקוטלין

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

מאפיינים מול שדות

כאשר ל- Java יש שדות, ל- Kotlin יש מאפיינים. נכסים מוכרזים ונגישים באופן הדומה לשדות ציבוריים ב- Java, אך Kotlin מספק יישומי ברירת מחדל של פונקציות accessor / mutator עבור מאפיינים; כלומר, קוטלין מספק get()פונקציות עבור valמאפיינים get()וגם set()ופונקציות עבור varמאפיינים. גרסאות מותאמות אישית של get()ו set()יכול להיות מיושם בעת הצורך.

לרוב המאפיינים בקוטלין יהיו שדות גיבוי, אך ניתן להגדיר מאפיין מחושב , שהוא למעשה get()פונקציה ללא שדה גיבוי. לדוגמא, למחלקה המייצגת אדם יכול להיות מאפיין עבורו dateOfBirthומאפיין מחושב עבורו age.

ברירת מחדל לעומת יבוא מפורש

Java מייבאת במשתמע מחלקות שהוגדרו בחבילה java.lang, אך יש לייבא במפורש את כל המחלקות האחרות. כתוצאה מכך, קבצי מקור רבים של Java מתחילים בייבוא ​​שיעורי אוסף מ- java.util, שיעורי קלט / פלט java.ioוכו '. כברירת מחדל, יבוא Kotlin במשתמע kotlin.*, אשר מקביל בערך ל ייבוא Java java.lang.*, אבל Kotlin גם ביבוא kotlin.io.*, kotlin.collections.*, וכיתות ממספר חבילות אחרות. מסיבה זו, קבצי מקור Kotlin דורשים בדרך כלל פחות יבוא מפורש מקבצי מקור Java, במיוחד עבור שיעורים המשתמשים באוספים ו / או קלט / פלט רגילים.

אין קריאה ל'חדש 'עבור בנאים

בקוטלין newאין צורך במילת המפתח כדי ליצור אובייקט חדש. כדי להתקשר לבנאי, פשוט השתמש בשם הכיתה עם סוגריים. קוד הג'אווה

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

ניתן לכתוב כדלקמן בקוטלין:

 var s = Student(...) 

תבניות מחרוזת

מחרוזות יכולות להכיל ביטויי תבנית , שהם ביטויים המוערכים עם תוצאות מוכנסות למחרוזת. ביטוי תבנית מתחיל בסימן דולר ($) ומורכב משם פשוט או ביטוי שרירותי בסוגריים מסולסלים. תבניות מחרוזת יכולות לקצר ביטויי מחרוזות על ידי הקטנת הצורך לשרשור מחרוזות מפורש. כדוגמה, קוד Java הבא

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

יכול להיות מוחלף בקוד קוטלין הקצר אך המקביל.

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

מאריך ומיישם

מתכנתים של ג'אווה יודעים שמחלקה יכולה להכין מחלקה extendאחרת implementוממשק אחד או יותר. בקוטלין, אין הבדל תחבירי בין שני מושגים דומים אלה; קוטלין משתמש במעי הגס לשניהם. לדוגמא, קוד 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 } 

לאחר מכן, השוויתי את הביצועים של שתי גרסאות Kotlin לזו של Java עם doubleו- Java עם Double, והפעלתי את כל ארבעת המדדים במחשב הנייד הנוכחי שלי. מכיוון שיש הפעלת "רעש" מועט בהפעלת כל אמת מידה, הריצתי את כל הגרסאות שלוש פעמים ומצעתי את התוצאות בממוצע, המסוכמות בטבלה 1.

טבלה 1. ביצועי זמן ריצה של מדד כפל מטריצות

תוצאות מתוזמנות (בשניות)
ג'אווה

( double)

ג'אווה

( Double)

קוטלין

( DoubleArray)

קוטלין

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

בעזרת IntelliJ IDEA הסבתי את גרסת Java של מדד ה- SciMark לקוטלין. IntelliJ IDEA המרה אוטומטית double[]וב- int[]Java ל- Kotlin DoubleArrayוב- IntArray. לאחר מכן השוויתי את גרסת Java באמצעות פרימיטיבים לגרסת Kotlin באמצעות DoubleArrayו- IntArray. כמו בעבר, הריצתי את שתי הגרסאות שלוש פעמים וממוצע ממוצע של התוצאות, המסוכמות בטבלה 2. שוב הטבלה מציגה תוצאות דומות בערך.

טבלה 2. ביצועי זמן ריצה של מדד SciMark

ביצועים (ב- Mflops)
ג'אווה קוטלין
1818.22 1815.78