שלום לכולם,
הפעם אני רוצה לדבר אתכם על java 17. ב-Tufin עד לאחרונה היינו משתמשים בג'אווה 8, ובזמן האחרון אנחנו עושים את המעבר לג'אווה 17.
תוך כדי המעבר הזה שמתי לב שאני לא כל כך יודע להסביר את ההבדלים בין הגרסאות הללו אז החלטתי לצלול קצת פנימה.
ציר הזמן
ג'אווה 8
אנחנו מתחילים את המסע שלנו בנבכי הג'אווה בשנת 2014, אז יוצאת גרסת ג'אווה 8 לעולם. גרסה שנחשבת כגרסה מאוד משמעותית בעולמות הג'אווה. היא הביאה הרבה חידושים כמו Lambda Expressions, – Stream API
ג'אווה 9
לאחר שלוש שנים וחצי, בספטמבר 2017, אנחנו מקבלים את גרסת ג'אווה 9, שהיא מציגה לעולם את המודולים.
עוד שינוי משמעותי בגרסה הזאת היא ההחלטה של אורקל לעבור לעולם של שני releases בשנה. אחד במרץ ואחד בספטמבר.
המטרה הייתה שכאשר תצא גרסה חדשה, הגרסה הקודמת הופכת להיות פגת תוקף. שזה בעיקרון צעד בכיוון הנכון, אבל לא כל התעשייה מוכנה לעבוד ככה.
ה-corporates של העולם למשל מחפשים יציבות. הם לא רוצים להחליף כל חצי שנה גרסת ג'אווה. ולכן מה שקורה בפועל הוא שכל כמה גרסאות, מגדירים גרסה בתור גרסת LTS. זוהי גרסה שתקבל תמכיה לזמן ארוך (Long Term Support).
ג'אווה 10
משוחררת לעולם במרץ 18, והיא מציגה לעולם את ה-local variable type interface, שבקצרה זה האפשרות לכתוב קוד ככה:
var list = new ArrayList();
var stream = list.stream();
הערה חשובה גרסאות ג'אווה 9 ו-10 נחשבות לגרסאות ביניים, שהייתה להן תמיכה קצרה.
ג'אווה 11
הגרסת LTS הראשונה מאז ג'אווה 8. היא משוחררת בספטמבר 18.
ג'אווה 11 הציגה את ה-Flight Recorder. זהו כלי אשר מאפשר לנו לעשות מעקב אחרי איוונטים אשר קוראים בזמן ריצה.
ג'אווה 17
נדלק קצת קדימה בזמן, עד לג'אווה 17 משתי סיבות.
הראשונה היא שזה נושא הפוסט עצמו, והשנייה היא ש-17 היא הגרסת LTS הבאה בתור.
בין גרסאות 12 עד גרסה 17 יש סה"כ 67 JEP.
איך משנים שפת תכנות
מה זה JEP
שוב פעם הוא התחיל לקלל? (בטח זה מה שעובר לחלקכם בראש).
אז JEP זה ראשי תיבות של JDK Enhancement Proposal. כלומר הצעה לשינוי ב-JDK, שבאה עם פורמט מאוד מסודר.
אוסף כל ה-JEP מהווה בעצם את ה-road map של ה-JDK.
את כל ה-JEP אפשר לחלק לארבע קטגוריות:
- דברים שנוספו
- דברים שירדו
- דברים שנוספו ולא עד הסוף
- דברים שירדו אבל לא עד הסוף
דברים שנוספו
גם כאן אנחנו יכולים לחלק לקטגוריות: לאן נוספו הדברים.
- דברים שנוספו ל-JVM, למשל ZGCשזהו Garbage Collector חדש ומאוד מאוד יעיל (מוכן לפרודקשיין מאז ג'אווה 15).
- שינויים בשפה – על זה אנחנו נרחיב בהמשך הפוסט
- ספריות- מימוש מחדש של Socket API למשל.
- כלים – כלים חדשים שנכנסו לשפה, למשל jpackageשנכנס בג'אווה 14 ומאפשר לנו לבנות installers.
דברים שנוספו ולא עד הסוף
כאשר Oracle עברו לשחרר שתי גרסאות בשנה, הם היו צריכים לפתח שיטה איך להכניס שינויים בשפה על מנת לקבל פידבק מהתעשייה.
אז בג'אווה 12 הם הכניסו פיצ'רים חדשים, כשכדי להשתמש בהם צריך להדליק פלאג גם בזמן קומפילציה וגם בזמן ריצה.
בעקבות הפידבק Oracle יכולה להבין מה צריך לשנות ומה לא.
דברים שירדו אבל לא עד הסוף
אלו בעצם פיצ'רים ש-Oracle רוצה להוציא החוצה, אבל היא לא רוצה לעשות את זה בצעד חד מאוד, אז היא מגדירה אותם כ-deprecated. כלומר הם עדיין יעבדו, אבל מכינים את המשתמשים שבגרסאות הבאות זה יכול כבר לא לעובד, ולכן צריך להתחיל למצוא החלפות.
בג'אווה 17, למשל הכריזו על ה-Security Manager כ-Deprecated.
דברים שירדו
אלו דברים שנמחקו רשמית וסופית מהשפה.
למשל בג'אווה 17 מחקו את ה-CMS
שינויים בשפה בג'אווה 17
אבל מה מעניין אותנו המתכנתים בסופו של דבר? לכתוב קוד!
ולכן השינויים בגרסאות ג'אווה שבדרך כלל מעניינות את המפתחים זה השינויים בשפה.
בין גרסאות 12 עד 17 נכנסו שישה JEP שקשורים לשפה
- Pattern Matching for instanceof(16)
- Records(16)
- Sealed Classes(17)
- Text Blocks(15)
- Switch Expressions(14)
- Restore Always Strict Floating Point Semantics(17)
בסוגריים מופיעות ה-JDK בו הם נכנסו סופית.
Pattern Matching for instanceof
השינוי הזה נכנס תחת JEP394
בואו נסתכל על הקטע קוד הבא:
void printInUpperCase(Object obj) {
if (obj instanceof String) {
String s = (String) obj;
System.out.println(s.toUpperCase());
}
}
אני בטוח שראיתם קוד כזה בעבר.
קיבלנו איזשהו אובייקט שאני רוצה לוודא שהאובייקט הוא מסוג String, ורק אחרי זה אנחנו ממירים אותו ל-String.
אבל נשאלת השאלה, אנחנו יודעים שזה String מיד אחרי הבדיקה, למה צריך לעשות המרה? למה הקומפיילר לא לומד את זה לבד?
ובכן החל מ-Java 17, הוא אכן יודע:
void printInUpperCase(Object obj) {
if (obj instanceof String s) {
System.out.println(s.toUpperCase());
}
}
יש לנו פה תבנית שאנחנו צריכים לעקוב אחריה:obj instance of String **s**
ככה אנחנו נותנים לאובייקט שם וממירים אותו בשורה אחת.
ועכשיו נתסכל על זה בצורה יותר כללית:
if (a instanceof Point p) {
// p is in scope
}
// p is NOT in scope here
if (b instanceof Point p) {
// new p is in scope
}
עכשיו אנחנו יכולים להרחיב את הביטוי עוד:
if (obj instanceof String s && s.length() > 5) {
flag = s.contains("jdk");
}
אם הגעתי לצד הימני של הביטוי אנחנו יכולים להיות בטוחים ש-s אכן מוגדר.
אבל את הקוד הבא אנחנו לא יכולים לכתוב:
if (obj instanceof String s || s.length() > 5) { // ERROR!!
...
}
כי כאן אנחנו כבר לא יכולים להיות בטוחים ש-s מוגדר. יכול להיות שהצד השמאלי נכשל בבדיקה שלו.
בואו נסתכל על דוגמה יותר מורכבת. הקומפיילר של ג'אווה מוגדר כ-context aware, כלומר הוא מבין את ה-flow של הקוד שלנו:
public void onlyForStrings(Object o) {
if (!(o instanceof String s)) {
throw new IllegalArgumentException();
}
// s is in scope
System.out.println(s.trim());
}
במקרה הזה הקומפיילר שלנו הבין ש-s אכן להיות String, כי אחרת היינו עפים על exception.
הקוד הזה יעבוד באותה צורה אם נכתוב string.
השוואות
איפה אנחנו רואים הרבה casting ב-Java? כשאר אנחנו בודקים אם שני אובייקטים הם שווים.
בהרבה מקרים אנחנו רואים קוד כזה:
public final boolean equals(Object o) {
if (!(o instanceof MyString)) {
return false;
}
MyString s = (MyString) o;
return this.equalsIgnoreCase(s);
}
עכשיו אנחנו יכולים לרשום את זה ככה:
public final boolean equals(Object o) {
return (o instanceof MyString ms) && this.equalsIgnoreCase(ms);
}
Records
השינוי הזה נכנס תחת JEP395
אחת התלונות הכי גדולות על Java היא שזוהי שפה שמלאה בטקסים.
הדוגמה הבולטת בנושא היא ה-DTO (Data Transfer Object).
אנחנו בעצם יוצרים class שכל מטרתו בחיים זה להחזיק ולהעביר מידע, או state.
למשל:
class Point {
private final int x;
private final int y;
Point(int x, int y) {
this.x = x;
this.y = y;
}
int x() {
return x;
}
int y() {
return y;
}
public boolean equals (Object o) {
if (!(o instanceof Point)) return false;
Point other = (Point) o;
return other.x == x && other.y == y;
}
public int hashCode() {
return Objects.hash(x, y);
}
public String toString() {
return String.format("Point[x=%d, y=%d]", x, y);
}
}
תראו כמה קוד היינו צריכים לכתוב בשביל דבר כל כך פשוט.
אנחנו צריכים בנאי, ואז אפשרות לקבל את השדות, ואז לבדוק אם שווה, ולהיות מסוגלים לשמור את זה בתוך hash tables למיניהם ולהמיר ל-String.
נורא ארוך ומסורבל.
ומה יקרה אם אני ארצה להוסיף עוד שדה? אני צריך לעדכן את הבנאי, להוסיף מתודה שתחזיר את השדה, ולעדכן את כל שאר המתודות.
נכון, יש דרכים לעקוף את זה יחד עם Lombok, אבל עדיין משהו הפריע לכותבי שפת ג'אווה.
הם רצו לסמן את המחלקות הללו בתור מחלקות שמחזיקות state.
ולכן הכניסו את השינוי הזה, את records
.
record Point(int x, int y) {}
בעצם זה דומה ל-Kotlin Data Class.
יש לנו keyword חדש
אז בואו נפרד את השורה הזאת:
יש לנו את record
שזו מילת מפתח חדשה בשפה, אשר מחליפה את class
.
השם נשאר אותו דבר Point
, ויש לנו את ה-Header
שבו יש לנו את הרשימה של המרכיבים.
מה שקורה מאחורי הקלעים הוא כזה:
- עבור כל מרכיב שעובר ב-
Header
קורים שני דברים- נוסף getter עם שם כמו השם של המרכיב (לדוגמה
x()
) - שדה שהוא
private final
- נוסף getter עם שם כמו השם של המרכיב (לדוגמה
- נוצר בנאי שהחתימה שלו היא כמו של ה-
Header
שמחבר בין כל שדה פרטי לערך המתאים לו. - מתודות דיפולטיביות של
equals
ושלhashCode
- המחלקות זהות רק אם הן מאותו סוג, ויש להם את אותן ערכים במרכיבים שלהם.
- מתודה של
toString
, שמחזירה את השדות יחד עם השמות שלהם.
Implicitly Final
==הערה חשובה== המחלקות מסוג records
הן Implicitly Final
, כלומר שהן לא יכולות לרשת מאף אחד, ואף אחד לא יכול לרשת מהם (יכולות לממש interface), והן לא יכולות להיות אבסטרקטיות.
למה בעצם?
ובכן אם כל ההיגיון שלנו היה שהמחלקות הללו הן מחזיקות state, אז אם אנחנו נירש ממחלקה אחרת, אנחנו בעצם מוסיפים עוד מצב על המחלקה הזאת.
נסתכל על הדוגמה הבאה:
class A {}
class B: A {}
מה יחזיר הקוד הבא:
B b = new B();
return b instanceof A;
יחזיר כמובן true.
וזה מצב שאנחנו מנסים למנוע כאן.
בנאים
כמו שכבר כתבתי למעלה, Java 17 תיצור באופן אוטומטי בנאי דיפולטיבי, אבל זה לא אומר שאנחנו לא יכולים ליצור אחד משלנו:
record Range(int lo, int hi) {
Range(int lo, int hi) {
if (lo > hi) {
throw new IlleagalArgumentException();
}
}
}
מה שחשוב לדעת כאן הוא שהחתימה של הבנאי צריכה להיות זהה לחלוטין ל-Header
של ה-record
.
אבל רגע, איפה ההשמה?
ובכן אם אנחנו נכתוב משהו מהסגנון הזה: this.lo = lo
אנחנו נקבל שגיאת קומפילציה.
אנחנו יכולים לעשות ולידציות על הפרמטרים שקיבלנו, אנחנו יכולים לשנות להם את הערך, אבל ההשמה תקרה אוטומטית מאחורי הקלעים.
ה-constructor הזה נקרה Canonical Constructor.
אם אנחנו מוסיפים עוד constructors אחרים, הם יהיו חייבים לקרוא ל-canonical constructor.
Local records
בואו נסתכל על עוד שימוש.
קוד אשר מייצר וצורך הרבה מופעים של record class, ככל הנראה יצטרך סוג של DTO ביניים, שמחזיק מידע לזמן קצר לטובת חישובים כאלה ואחרים.
ולכן יש לנו אפשרות להגדיר Local Record
.
למשל בדוגמה הבאה:
List<Merchant> findTopMerchants(List<Merchant> merchants, int month) {
// Local record
record MerchantSales(Merchant merchant, double sales) {}
return merchants.stream()
.map(merchant -> new MerchantSales(merchant, computeSales(merchant, month)))
.sorted((m1, m2) -> Double.compare(m2.sales(), m1.sales()))
.map(MerchantSales::merchant)
.collect(toList());
}
בדוגמה הזאת אנחנו רוצים למיין את כל הסוחרים שלנו לפי הביצועים שלהם פר חודש.
נשים לב שאנחנו משתמשים במחלקת ביניים בשם MerchantSales
על מנת לחשב כמה מכירות יש לכל אחד בחודש.
אז במקום לייצר קלאס מלא לזה בצד, אנחנו יכולים לנצל את ה-record class
שהיא גם ככה מאוד פשוט לכתיבה (שורה אחת).
Sealed Classes
אנחנו משתמשים בהורשה ב-OOP.
אבל לפעמים אנחנו רוצים לשלוט במי יכול להרחיב את המחלקה שלנו, או לאיזה ערכים אני יכול לקבל.
לדוגמה, אם אני יוצר מחלקה בשם Planet
, אני לא רוצה שהשם שלה יגיע ב-constructor, אני רוצה לשלוח מה האופציות שיהיו.
אז בשביל זה אנחנו משתמשים ב-enum
:
enum Planet { MERCURY, VENUS, EARTH }
Planet p = ...
switch (p) {
case MERCURY: ...
case VENUS: ...
case EARTH: ...
}
במקרים של ערכים enum
באמת עוזר לנו, אבל יש מקרים שבהם אנחנו רוצים לשלוט בסוג.
לדוגמה:
interface Celestial { ... }
class Planet implements Celestial { ... }
class Star implements Celestial { ... }
class Comet implements Celestial { ... }
אבל כאן אנחנו בעצם פותחים פתח לכל מי שמשתמש ב-package שלי לרשת את אחת מהמחלקות שלי וליצור לו את שלו, ובאמת יש מקרים שבהם אנחנו רוצים למנוע את זה כדי למנוע סיבוכים. אז מה נעשה? ובכן לג'אווה עד עכשיו היה פתרון, שמים final על המחלקות
interface Celestial { ... }
final class Planet implements Celestial { ... }
final class Star implements Celestial { ... }
final class Comet implements Celestial { ... }
אוקי, אבל מה לגבי ה-interface? איך אני אשלוט בו? בעצם עד ל-Java 17 חיברו לי שני עקרונות: הרחבה, גישה וחיברו ביניהם. אם אני רוצה שמשהו יהיה נגיש, הוא בהגדרה גם יהיה ניתן להרחבה. וזו הבעיה ש-`Sealed Class` באה לפתור
package com.example.geometry;
public abstract sealed class Shape
permits Circle, Rectangle, Square { ... }
גם כאן יש לנו שני keywords חדשים: sealed
ו- permits
בעצם מה שהקוד למעלה מגדיר הוא מי יכול לרשת ממני. בדוגמה הזאת רק Circle, Rectangle, Square.
אבל זה לא הכל. יש לנו עוד כללים:
- כל המחלקות שמותר להן לרשת ממני חייבות להיות קרובות:
- באותו package
- באותו module.
- כל class שמותר לו לרשת חייב לרשת ישירות את ה-
sealed class
.
כלומרCircle
חייב לעשותextend
ל-Shape
. אסור לו לעשותextend
ל-Rectangle
לדוגמה. - כל אחד מה-
class
שמותר לו לרשת חייב להגדיר איך הוא מרחיב את ההורשה:- אופציה ראשונה שהוא מגדיר שאף אחד לא יכול לרשת אותו (final)
- אופציה שניה היא להגדיר את עצמו כ-
sealed
בפני עצמו - להגדיר את עצמו כ
non-sealed
, כלומר אני לא מגביל את עצמי.
שילוב של records עם sealed classes
ג'אווה 17 בעצם מאפשרת לנו לשלב באופן מעולה בין records
לבין sealed classes
.
לדוגמה:
package com.demo.expression;
public sealed interface Expr
permits ContantExpr, PlusExpr, TimesExpr, NegateExpr {...}
public record ConstatnExpr(int i) implements Expr {...}
public record PlusExpr(Expr a, Expr b) implements Expr {...}
public record TimesExpr(Expr a, Expr b) implements Expr {...}
public record NegateExpr(Expr e) implements Expr {...}
במקרה הזה ראש ההיררכיה הוא interface ומתחת יש לנו records
שאנחנו מרשים להרחיב
אבל חסר לנו פה משהו? הכלל השלישי של sealed classes
, למה הן לא מצהירות מי יכול להרחיב אותן?
ובכן התשובה היא בגלל ש-records
הם implictly final
כלומר אף אחד לא יכול להרחיב אותן.
Text Blocks
זה אחד הפיצ'רים שבאמת היה מאוד חסר בג'אווה. הוא נכנס ב-JEP 378
כמעט בכל שפה בעולם יש אפשרות לכתוב strings
בכמה שורות, ורק בג'אווה היינו צריכים לדחוף את התו \n
כל הזמן.
למשל:
String html = "<html>\n"
" <body>\n"
" <p>Hello, world</p>\n"
" </body>\n"
"</html>\n";
ועכשיו אנחנו כבר לא צריכים:
String html = """
<html>
<body>
<p> Hello, world</p>
</body>
</html>
""";
חשוב לציין שאין לנו type חדש פה, זה עדיין String
לכל דבר ועניין.
לי לא יצא כל כך לכתוב HTML ככה, אבל בהחלט יצא לי לכתוב שאילתות SQL, וזה כבר היה יכול להיות ממש מעצבן עם הצורך לעשות escaping למרכאות:
String query = "Select \"EMP_ID\", \"LAST_NAME\" FROM \"EMPLOYEE_TP\";"
עכשיו אנחנו יכולים לכתוב את זה ככה:
String query = """
Select "EMP_ID", "LAST_NAME" FROM "EMPLOYEE_TP";
"""
אז בואו ניכנס לפרטים:
התו הראשון ב-Text Block
הוא הדלימיטר """
ואחריו לא יכול לבוא כלום, רק שורה חדשה.
ככה ג'אווה יודעת איך להתחיל לעבוד עם ה-Text Block
אנחנו יכולים להשתמש במרכאות בתוך ה-Text Block
ואנחנו יכולים להכניס שורות חדשות.
טיפול ברווחים
החלק המשמעותי הוא איך עושים אינדנטציה ב-Text Block
אם נסתכל על הקוד הבא:
String html = """
................<html>
....................<body>
.......................<p> Hello, world</p>
....................</body>
................</html>
............""";
הנקודות הן רווחים שהם מקריים לחלוטין, כי יכול להיות שב-code style שונה יהיה מספר אחר של רווחים, או אם נשים את זה בתוך בלוק של if.
ולכן ג'אווה יודעת להתעלם מהן. כנ"ל לגבי רווחים עודפים (Trailing spaces)
אבל לפעמים אנחנו כן רוצים שיהיה רווחים בתחילת השורה, אז אנחנו נכניס את זה ככה:
String html = """
<html>
<body>
<p> Hello, world</p>
</body>
</html>
.........""";
ואז הקומפיילר יקח רק את מספר הרווחים על לדלימטר האחרון """
.
שיטות escaping חדשות
בואו נסכל על הקוד הבא, שאני בטוח שרובכם ראיתם בעבר
הסיבה למה אנחנו מקצרים את האורך של ה-String
הזה הוא רק בגלל הקריאות, הרי אין בו שורות חדשות.
אז בשימוש ב-Text Block
אנחנו יכולים לבצע את זה בצורה קצת יותר אלגנטית:
String text = """
Lorem ipsum dolor sit amet, consectetur adipiscing \
elit, sed do eiusmod tempor incididunt ut labore \
et dolore magna aliqua.\
""";
שיפורים ב-Switch
השיפורים הללו נכנסו תחת JEP 361
נתחיל עם דוגמה
switch (day) {
case MONDAY:
case FRIDAY:
case SUNDAY:
System.out.println(6);
break;
case TUESDAY:
System.out.println(7);
break;
case THURSDAY:
case SATURDAY:
System.out.println(8);
break;
case WEDNESDAY:
System.out.println(9);
break;
}
בעצם ה-switch
הגיע דרך שפת C.
יחד עם ה-break
הידוע לשמצה (ואי כמה פעמים דיבגתי באג שבו שכחו בטעות להכניס את זה).
לי תמיד הוא הרגיש מאוד low level ולא מודרני.
אז בשעה טובה ב-ג'אווה 17 שיפרו מאוד את הסינטקס:
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);
}
קיבלנו את הסינטקס של החץ כמו בקוטלין, וגם אין לנו fall through שהיינו צריכים לטפל בה באמצעות ה-break כבר לא קיימת.
בנוסף אנחנו יכולים לשים כמה statements ביחד.
Switch Expressions
בלא מעט מקרים, אנחנו רוצים לתת ערך לאיזשהו משתנה בהתאם לערך של משתנה אחר.
כדי לעשות את זה עם switch case היינו צריכים לכתוב משהו כזה:
int numLetters;
switch (day) {
case MONDAY:
case FRIDAY:
case SUNDAY:
numLetters = 6;
break;
case TUESDAY:
numLetters = 7;
break;
case THURSDAY:
case SATURDAY:
numLetters = 8;
break;
case WEDNESDAY:
numLetters = 9;
break;
default:
throw new IllegalStateException("Wat: " + day);
}
עכשיו אנחנו יכולים לכתוב את זה הרבה יותר אלגנטי:
int numLetters = switch (day) {
case MONDAY, FRIDAY, SUNDAY -> 6;
case TUESDAY -> 7;
case THURSDAY, SATURDAY -> 8;
case WEDNESDAY -> 9;
};
בעצם ה-switch
הפך להיות ביטוי שמחזיר ערך.
ואם אנחנו רוצים לעשות case עם כמה שורות:
int j = switch (day) {
case MONDAY -> 0;
case TUESDAY -> 1;
default -> {
int k = day.toString().length();
int result = f(k);
yield result;
}
};
הופה, נכנס לנו פה משהו חדש בשם yield
במקור הוא היה אמור להיות ה-break
החדש.
אבל אז טענו שזה מבלבל, וגם להשתמש ב-return
לא יתאים כי אנחנו לא רוצים לצאת מהפונקציה.
ולכן הביאו את yield
שמשתמשים בו במקרה של כמה שורות אז אנחנו נגדיר עם yield
איזה ערך צריך לחזור מהמקרה הזה.
Restore Always Strict Floating Point Semantic
על השינוי הזה אני הולך לדלג (אנא סלחו לי) מכמה סיבות:
- הפוסט הזה הוא מאוד ארוך גם ככה
- זהו לא משהו שהרבה מפתחים הולכים להשתמש בו (סמנטיות של מספרים עשרוניים)
סיכום
אז עברנו פה די הרבה, אבל אני בהחלט מתרגש לעבור לג'אווה 17.
יש פה לא מעט פיצ'רים משמעותיים עבור ג'אווה ועבור הנוחות של המפתחים שכותבים בה.
כרגע לפחות, אני עדיין מעדיף את קוטלין, כיוון שכל הפיצ'רים הללו קיימים שם, והיא נותנת תמיכה בעוד כמה דברים שעדיין חסרים לי בג'אווה, כמו למשל: היכולת להרחיב מחלקות בסיסיות או שימוש בתכנות פונקציונאלי.