מידע על Codelab זה
1. שלום!
במעבדה זו תוכלו ללמוד איך להמיר את הקוד מ-Java ל-Kotlin. תלמדו גם מהן המוסכמות של קוטלין ואיך תוכלו לוודא שהקוד שאתם כותבים תואם להן.
Codelab זה מתאים לכל מפתח שמשתמש ב-Java ששוקלים להעביר את הפרויקט שלו ל-Kotlin. נתחיל במספר שיעורי Java שניתן להמיר לקוטלין באמצעות IDE. לאחר מכן, נבחן את הקוד שהומר ונבדוק איך אנחנו יכולים לשפר אותו על ידי הגדרתו דיאומטית ומניעת שגיאות נפוצות.
מה תלמדו
נסביר איך להמיר את Java ל-Kotlin. לשם כך, תלמדו את התכונות והקונספטים הבאים של השפה בקוטלין:
- טיפול באפסיות (null)
- הטמעת טונים אחידים
- מחלקות נתונים
- טיפול במחרוזות
- האופרטור של Elvis
- הרס
- נכסים ונכסים לגיבוי
- ארגומנטי ברירת מחדל ופרמטרים בעלי שם
- עבודה עם אוספים
- פונקציות של תוספים
- פונקציות ופרמטרים ברמה עליונה
let
,apply
,with
ו-run
מילות מפתח
הנחות
אם אתם כבר מכירים את Java.
מה תצטרך להכין
2. תהליך ההגדרה
יצירה של פרויקט חדש
אם אתם משתמשים ב- IntelliJ IDEA, צרו פרויקט Java חדש עם Kotlin/JVM.
אם אתם משתמשים ב-Android Studio, יש ליצור פרויקט חדש ללא פעילות.
הקוד
אנחנו ניצור אובייקט מודל מסוג User
ומחלקה אחת (Repository
) של ישות אחת שעובדת עם אובייקטים User
וחושפת רשימות של משתמשים ושל שמות משתמשים בפורמט.
יוצרים קובץ חדש בשם User.java
בקטע app/javascript/<yourpackagename> ומדביקים את הקוד הבא:
public class User {
@Nullable
private String firstName;
@Nullable
private String lastName;
public User(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}
בהתאם לסוג הפרויקט, יש לייבא את androidx.annotation.Nullable
אם משתמשים בפרויקט Android, או org.jetbrains.annotations.Nullable
אם לא.
יוצרים קובץ חדש בשם Repository.java
ומדביקים את הקוד הבא:
import java.util.ArrayList;
import java.util.List;
public class Repository {
private static Repository INSTANCE = null;
private List<User> users = null;
public static Repository getInstance() {
if (INSTANCE == null) {
synchronized (Repository.class) {
if (INSTANCE == null) {
INSTANCE = new Repository();
}
}
}
return INSTANCE;
}
// keeping the constructor private to enforce the usage of getInstance
private Repository() {
User user1 = new User("Jane", "");
User user2 = new User("John", null);
User user3 = new User("Anne", "Doe");
users = new ArrayList();
users.add(user1);
users.add(user2);
users.add(user3);
}
public List<User> getUsers() {
return users;
}
public List<String> getFormattedUserNames() {
List<String> userNames = new ArrayList<>(users.size());
for (User user : users) {
String name;
if (user.getLastName() != null) {
if (user.getFirstName() != null) {
name = user.getFirstName() + " " + user.getLastName();
} else {
name = user.getLastName();
}
} else if (user.getFirstName() != null) {
name = user.getFirstName();
} else {
name = "Unknown";
}
userNames.add(name);
}
return userNames;
}
}
3. הצהרה על null, val, var ושיעורי נתונים
ממשק ה-IDE שלנו יכול לבצע עבודה טובה מחדש בהסבה מחדש של קוד Java לקוד של Kotlin, אבל לפעמים הוא צריך קצת עזרה. אנחנו עושים זאת קודם כל, ולאחר מכן עוברים על הקוד שהוקלד מחדש כדי להבין איך הן הוזכרו מחדש.
עוברים אל הקובץ User.java
וממירים אותו ל-Kotlin: סרגל התפריטים -> קוד -> המרת קובץ Java לקובץ Kotlin.
אם קובץ ה-IDE מציג בקשה לתיקון לאחר ההמרה, לוחצים על כן.
אתם אמורים לראות את קוד Kotlin הזה:
class User(var firstName: String?, var lastName: String?)
לידיעתך, השם של User.java
השתנה ל-User.kt
. הסיומת kt של קובצי Kotlin מתבצעת.
בשיעור Java User
שלנו, היו לנו שני נכסים: firstName
ו-lastName
. לכל אחד הייתה שיטת getter ו-setter, כך שהערך שלו משתנה. מילת המפתח של Kotlin למשתנים משתנים היא var
, כך שהממיר משתמש ב-var
לכל אחד מהנכסים האלו. אם נכסי ה-Java שלנו קיבלו רק משתנים, לא ניתן יהיה לשנות אותם והכריזו עליהם כמשתני val
. val
דומה למילת המפתח final
ב-Java.
אחד ההבדלים העיקריים בין Kotlin ל-Java הוא ש-Kotlin מציינת באופן מפורש אם משתנה יכול לקבל ערך null. פעולה זו מתבצעת על ידי צירוף '?
' להצהרת הסוג.
מאחר שסמןנו את firstName
ואת lastName
בתור null, המשתמש שהשלים המרה אוטומטית יסמן את המאפיינים בתור null עם String?
. אם מוסיפים הערות למשתמשי Java בתור null (באמצעות org.jetbrains.annotations.NotNull
או androidx.annotation.NonNull
), הממיר יזהה את השדה ויהפוך את השדות שאינם Null ל-Kotlin גם.
החישוב מחדש כבר בוצע. אבל אנחנו יכולים לנסח את זה באופן אידוי יותר. בואו נראה איך.
סיווג נתונים
השיעור שלנו ב-User
מכיל נתונים בלבד. קוטלין מכילה מילת מפתח בקורסים של תפקיד זה: data
. כשמסמנים את הכיתה הזו ככיתה data
, המהדר יוצר באופן אוטומטי getter ומגדירים. היא תציג גם את הפונקציות equals()
, hashCode()
ו-toString()
.
נוסיף את מילת המפתח data
לכיתה User
:
data class User(var firstName: String, var lastName: String)
קוטלין, כמו Java, יכול להיות בנאי ראשי ובנייה משנית אחת או יותר. הדוגמה בדוגמה שלמעלה היא ה-constructor הראשי של מחלקת המשתמשים. אם אתם ממירים מחלקת Java שיש בה מספר בנאים, הממיר ייצור באופן אוטומטי גם מספר בונים ב-Kotlin. הם מוגדרים באמצעות מילת המפתח constructor
.
אם אנחנו רוצים ליצור מופע של הכיתה הזו, ניתן לעשות זאת כך:
val user1 = User("Jane", "Doe")
שוויון
לקוטלין יש שני סוגים של שוויון:
- אי-שוויון מבני משתמש באופרטור
==
ומתקשר ל-equals()
כדי לקבוע אם שני מופעים שווים. - שוויון הפניה משתמש באופרטור
===
ובודק אם שתי הפניות מפנות לאותו אובייקט.
הנכסים שמוגדרים בבונה הראשי של מחלקת הנתונים ישמשו לבדיקות שוויון מבניות.
val user1 = User("Jane", "Doe")
val user2 = User("Jane", "Doe")
val structurallyEqual = user1 == user2 // true
val referentiallyEqual = user1 === user2 // false
4. ארגומנטים המוגדרים כברירת מחדל, ארגומנטים בעלי שם
ב-Cotlin, אנחנו יכולים להקצות ערכי ברירת מחדל לארגומנטים בקריאות לפונקציה. המערכת משתמשת בערך ברירת המחדל אחרי שהארגומנט מושמט. בקוטון, בנאים הם גם פונקציות, לכן אנחנו יכולים להשתמש בארגומנטים שמוגדרים כברירת מחדל כדי לציין שערך ברירת המחדל של lastName
הוא null
. לשם כך אנחנו מקצים את null
ל-lastName
.
data class User(var firstName: String?, var lastName: String? = null)
// usage
val jane = User("Jane") // same as User("Jane", null)
val joe = User("John", "Doe")
ניתן לתת שם לפרמטרים של פונקציית במהלך קריאה לפונקציות:
val john = User(firstName = "John", lastName = "Doe")
כתרחיש לדוגמה אחר, נניח שהפקודה firstName
היא null
כברירת המחדל שלה, וlastName
לא. במקרה הזה, מפני שפרמטר ברירת המחדל יקודם לפרמטר שאין לו ערך ברירת מחדל, תצטרכו לקרוא לפונקציה לפונקציה באמצעות ארגומנטים עם שם:
data class User(var firstName: String? = null, var lastName: String?)
// usage
val jane = User(lastName = "Doe") // same as User(null, "Doe")
val john = User("John", "Doe")
5. אתחול אובייקט, אובייקט נלווה וטון יחיד
לפני שממשיכים, צריך לוודא שהכיתה שלך ב-User
היא כיתה ב-data
. יש להמיר את הכיתה מRepository
לקוטלין. תוצאת ההמרה האוטומטית אמורה להיראות כך:
import java.util.*
class Repository private constructor() {
private var users: MutableList<User?>? = null
fun getUsers(): List<User?>? {
return users
}
val formattedUserNames: List<String?>
get() {
val userNames: MutableList<String?> =
ArrayList(users!!.size)
for (user in users) {
var name: String
name = if (user!!.lastName != null) {
if (user!!.firstName != null) {
user!!.firstName + " " + user!!.lastName
} else {
user!!.lastName
}
} else if (user!!.firstName != null) {
user!!.firstName
} else {
"Unknown"
}
userNames.add(name)
}
return userNames
}
companion object {
private var INSTANCE: Repository? = null
val instance: Repository?
get() {
if (INSTANCE == null) {
synchronized(Repository::class.java) {
if (INSTANCE == null) {
INSTANCE =
Repository()
}
}
}
return INSTANCE
}
}
// keeping the constructor private to enforce the usage of getInstance
init {
val user1 = User("Jane", "")
val user2 = User("John", null)
val user3 = User("Anne", "Doe")
users = ArrayList<Any?>()
users.add(user1)
users.add(user2)
users.add(user3)
}
}
בואו נראה מה ביצע ההמרה האוטומטית:
- נוסף בלוק
init
(Repository.kt#L50) - השדה
static
הוא חלק מבלוקcompanion object
(Repository.kt#L33) - הרשימה של
users
ניתנת לביטול כי האובייקט לא נוצר בזמן ההצהרה (Repository.kt#L7) - השיטה של
getFormattedUserNames()
היא עכשיו נכס בשםformattedUserNames
(Repository.kt#L11) - באיטרציה שבה מופיעה רשימת המשתמשים (שהיתה חלק מ-
getFormattedUserNames(
) יש תחביר שונה מזה של Java (Repository.kt#L15)
לפני שנמשיך, כדאי לנקות את הקוד עוד קצת. ניתן לראות שהממיר הפך את רשימת users
שלך לרשימת שינויים שכוללת אובייקטים שניתן לבטל. הרשימה אכן יכולה להיות ריקה, אבל אפשר לומר שהיא יכולה להחזיק משתמשים ריקים. אז אז...
- יש להסיר את
?
מUser?
בתוך הצהרת הסוגusers
getUsers
צריך להחזירList<User>?
בנוסף, הממיר האוטומטי מבצע פיצול שלא לצורך, בשתי שורות של הצהרות המשתנים של המשתנים של המשתמשים ושל המשתנים שהוגדרו בבלוק של הכניסה. צריך להוסיף כל הצהרת משתנה בשורה אחת. כך הקוד שלנו אמור להיראות:
class Repository private constructor() {
private var users: MutableList<User>? = null
fun getUsers(): List<User>? {
return users
}
val formattedUserNames: List<String?>
get() {
val userNames: MutableList<String?> =
ArrayList(users!!.size)
for (user in users) {
var name: String
name = if (user!!.lastName != null) {
if (user!!.firstName != null) {
user!!.firstName + " " + user!!.lastName
} else {
user!!.lastName
}
} else if (user!!.firstName != null) {
user!!.firstName
} else {
"Unknown"
}
userNames.add(name)
}
return userNames
}
companion object {
private var INSTANCE: Repository? = null
val instance: Repository?
get() {
if (INSTANCE == null) {
synchronized(Repository::class.java) {
if (INSTANCE == null) {
INSTANCE =
Repository()
}
}
}
return INSTANCE
}
}
// keeping the constructor private to enforce the usage of getInstance
init {
val user1 = User("Jane", "")
val user2 = User("John", null)
val user3 = User("Anne", "Doe")
users = ArrayList<Any?>()
users.add(user1)
users.add(user2)
users.add(user3)
}
}
בלוק של Init
ב-Kotlin, ה-constructor הראשי לא יכול להכיל קוד, לכן קוד האתחול מוצב בבלוקים של init
. הפונקציונליות זהה.
class Repository private constructor() {
...
init {
val user1 = User("Jane", "")
val user2 = User("John", null)
val user3 = User("Anne", "Doe")
users = ArrayList<Any?>()
users.add(user1)
users.add(user2)
users.add(user3)
}
}
חלק גדול מהקוד של init
מטפל באתחול של נכסים. ניתן לעשות זאת גם בהצהרה של הנכס. לדוגמה, בגרסה של Kotlin של הכיתה Repository
שלנו, אנחנו רואים שמאפיין המשתמש הופעל בהצהרה.
private var users: MutableList<User>? = null
נכסים ושיטות של static
Ktlin'
ב-Java, אנחנו משתמשים במילת המפתח static
בשדות או בפונקציות כדי לציין שהם שייכים לכיתה, אבל לא למופע של הכיתה. לכן יצרנו את השדה הסטטי INSTANCE
בקורס שלנו Repository
. המקבילה של Kotlin לערך הזה היא הבלוק companion object
. בקטע הזה צריך גם להצהיר על השדות הסטטיים והפונקציות הסטטיות. הממיר יצר והעביר את השדה INSTANCE
לכאן.
טיפול בטון יחיד
מכיוון שאנחנו צריכים רק מופע אחד של המחלקה Repository
, השתמשנו ב-singleton template ב-Java. באמצעות Kotlin, ניתן לאכוף את הדפוס הזה ברמת המהדר על ידי החלפת מילת המפתח class
ב-object
.
יש להסיר את המבנה הפרטי ואת האובייקט הנלווה ולהחליף את הגדרת הכיתה ב-object Repository
.
object Repository {
private var users: MutableList<User>? = null
fun getUsers(): List<User>? {
return users
}
val formattedUserNames: List<String>
get() {
val userNames: MutableList<String> =
ArrayList(users!!.size)
for (user in users) {
var name: String
name = if (user!!.lastName != null) {
if (user!!.firstName != null) {
user!!.firstName + " " + user!!.lastName
} else {
user!!.lastName
}
} else if (user!!.firstName != null) {
user!!.firstName
} else {
"Unknown"
}
userNames.add(name)
}
return userNames
}
// keeping the constructor private to enforce the usage of getInstance
init {
val user1 = User("Jane", "")
val user2 = User("John", null)
val user3 = User("Anne", "Doe")
users = ArrayList<Any?>()
users.add(user1)
users.add(user2)
users.add(user3)
}
}
כשמשתמשים במחלקה object
, אנחנו קוראים לפונקציות ולמאפיינים ישירות לגבי האובייקט, כך:
val users = Repository.users
השחתה
Kotlin מאפשר להשמיד אובייקט במספר משתנים באמצעות תחביר של הצהרת הרס. אנחנו יוצרים משתנים מרובים ומשתמשים בהם באופן עצמאי.
לדוגמה, מחלקת נתונים תומכת ב ניהול היררכיות כדי שנוכל להשמיד את האובייקט User
בחלק for
של (firstName, lastName)
. כך אנחנו יכולים לעבוד ישירות עם הערכים firstName
ו-lastName
. יש לעדכן את הלולאה של for
כך:
for ((firstName, lastName) in users!!) {
val name: String?
if (lastName != null) {
if (firstName != null) {
name = "$firstName $lastName"
} else {
name = lastName
}
} else if (firstName != null) {
name = firstName
} else {
name = "Unknown"
}
userNames.add(name)
}
6. טיפול באפסיות (null)
בזמן ההמרה של המחלקה Repository
ל-Kotlin, הממיר האוטומטי הפך את רשימת המשתמשים לחסרה, מפני שהיא לא הופעלה לאובייקט בעת ההכרזה. לכל השימושים באובייקט users
, ייעשה שימוש באופרטור הטענה שאינו Null !!
. היא ממירה כל משתנה לסוג שאינו Null ויוצרת חריגה אם הערך הוא null. השימוש ב-!!
עלול לסכן את החריגים בזמן הריצה.
במקום זאת, אפשר להשתמש ב-אחת מהשיטות הבאות כדי לטפל באיפוס
- מתבצעת בדיקה ריקה (
if (users != null) {...}
) - שימוש באופרטור Elvis
?:
(שמכוסה מאוחר יותר ב-Codelab) - שימוש בחלק מהפונקציות הסטנדרטיות של Kotlin (שמתוארות בהמשך ב-Codelab)
במקרה שלנו, אנחנו יודעים שרשימת המשתמשים לא חייבת להיות ריקה, כי היא מתחילה לפעול מיד אחרי שהאובייקט נוצר, כדי שנוכל לספק אובייקט באופן ישיר כאשר אנחנו מצהירים עליו.
בעת יצירת מופעים של סוגי אוספים, Kotlin מספק כמה פונקציות מסייעות להפיכת הקוד לקריא וגמיש יותר. כאן אנחנו משתמשים ב-MutableList
עבור users
:
private var users: MutableList<User>? = null
כדי לפשט את השימוש, אנחנו יכולים להשתמש בפונקציה mutableListOf()
, לציין את סוג הרכיב של הרשימה, להסיר את הקריאה לבנייה של ArrayList
מבלוק ה-init
ולהסיר את הצהרת הסוג הבוטה של הנכס users
.
private val users = mutableListOf<User>()
בנוסף, שינינו את הערך ל-val כי המשתמשים יכללו הפניה בלתי הפיכה לרשימת המשתמשים. שימו לב שההפניה אינה ניתנת לשינוי, אבל הרשימה עצמה משתנה (תוכלו להוסיף או להסיר רכיבים).
בעקבות השינויים האלה, הנכס שלנו ב-users
אינו Null, ואנחנו יכולים להסיר את כל האירועים החסרים של אופרטור !!
.
val userNames: MutableList<String?> = ArrayList(users.size)
for ((firstName, lastName) in users) {
...
}
כמו כן, מכיוון שמשתנה המשתמשים כבר אותחל, אנחנו צריכים להסיר את האתחול מבלוק init
:
init {
val user1 = User("Jane", "")
val user2 = User("John", null)
val user3 = User("Anne", "Doe")
users.add(user1)
users.add(user2)
users.add(user3)
}
מאחר ש-lastName
ו-firstName
יכולים להיות null
, עלינו לטפל בביטולים כאשר אנחנו יוצרים את רשימת שמות המשתמשים בפורמט הנכון. מכיוון שאנחנו רוצים להציג את "Unknown"
אם אחד השמות לא נמצא, נוכל להגדיר את השם כ'לא ריק' על ידי הסרת ?
מהצהרת הסוג.
val name: String
אם הערך של lastName
הוא null, הערך name
הוא firstName
או "Unknown"
:
if (lastName != null) {
if (firstName != null) {
name = "$firstName $lastName"
} else {
name = lastName
}
} else if (firstName != null) {
name = firstName
} else {
name = "Unknown"
}
אפשר לכתוב את הטקסט הזה בצורה אידאלית באמצעות האופרטור elvis ?:
. האופרטור elvis יחזיר את הביטוי מצד שמאל שלו אם הוא לא Null, או את הביטוי בצד ימין שלו, אם הצד השמאלי שלו הוא null.
לכן, הקוד הבא: user.firstName
מוחזר אם הוא לא null. אם הערך user.firstName
הוא null, הביטוי יחזיר את הערך מצד ימין , "Unknown"
:
if (lastName != null) {
...
} else {
name = firstName ?: "Unknown"
}
7. תבניות מחרוזת ואם ביטוי
עם Kotlin קל לעבוד עם String
באמצעות תבניות מחרוזת. תבניות מחרוזת מאפשרות להפנות למשתנים בתוך הצהרות מחרוזת.
הממיר האוטומטי עדכן את שרשור השם הפרטי ושם המשפחה כך שיתייחס לשם המשתנה ישירות במחרוזת באמצעות הסמל $
ויוסיף את הביטוי בין { }
.
// Java
name = user.getFirstName() + " " + user.getLastName();
// Kotlin
name = "${user.firstName} ${user.lastName}"
בקוד, מחליפים את שרשור המחרוזת ב:
name = "$firstName $lastName"
בקוטלי (if
), when
, for
ו-while
הם ביטויים – הם מחזירים ערך. בסביבת הפיתוח המשולבת שלך מופיעה גם אזהרה על כך שיש להסיר את ההקצאה מהif
:
בואו לעקוב אחר ההצעה של IDE' והסירו את ההקצאה של שתי ההצהרות if
. השורה האחרונה של הצהרת ההצהרה תוקצה. כך ברור יותר שמטרת החסימה הזו היא להפעיל את ערך השם:
name = if (lastName != null) {
if (firstName != null) {
"$firstName $lastName"
} else {
lastName
}
} else {
firstName ?: "Unknown"
}
לאחר מכן תקבלו אזהרה על כך שאפשר לצרף את הצהרת name
למטלה. גם היא עובדת. מאחר שניתן להסיק את סוג משתנה השם, אנחנו יכולים להסיר את הצהרת הסוג הבוטה. עכשיו נראה מכשיר ה-formattedUserNames
שלנו:
val formattedUserNames: List<String?>
get() {
val userNames: MutableList<String?> = ArrayList(users.size)
for ((firstName, lastName) in users) {
val name = if (lastName != null) {
if (firstName != null) {
"$firstName $lastName"
} else {
lastName
}
} else {
firstName ?: "Unknown"
}
userNames.add(name)
}
return userNames
}
8. פעולות באוספים
נבחן את הכלי הזה ב-formattedUserNames
יותר לעומק כדי להבין איך לשפר אותו. בשלב הזה, הקוד מבצע את הפעולות הבאות:
- יצירת רשימה חדשה של מחרוזות
- חוזר ברשימת המשתמשים
- בונה את השם המפורמט עבור כל משתמש, על סמך השם הפרטי ושם המשפחה של המשתמש
- החזרת הרשימה החדשה שנוצרה
val formattedUserNames: List<String>
get() {
val userNames = ArrayList<String>(users.size)
for ((firstName, lastName) in users) {
val name = if (lastName != null) {
if (firstName != null) {
"$firstName $lastName"
} else {
lastName
}
} else {
firstName ?: "Unknown"
}
userNames.add(name)
}
return userNames
}
Kotlin מספק רשימה מקיפה של טרנספורמציות באוסף שהופכות את הפיתוח למהיר ולבטוח יותר על ידי הרחבת היכולות של Java collections API. אחת מהן היא הפונקציה map
. פונקציה זו מחזירה רשימה חדשה הכוללת את התוצאות של החלת פונקציית הטרנספורמציה הנתונה על כל רכיב ברשימה המקורית. לכן, במקום ליצור רשימה חדשה ולחזור על כל רשימת המשתמשים באופן ידני, אנחנו יכולים להשתמש בפונקציה map
ולהעביר את הלוגיקה שהיו לנו בלולאה של for
בתוך גוף map
. כברירת מחדל, השם של פריט הרשימה הנוכחי המשמש ב-map
הוא it
, אבל כדי לשנות את הקריאה הזו, תוכלו להחליף את it
בשם המשתנה שלכם. במקרה שלנו, תן שם ל-user
:
val formattedUserNames: List<String>
get() {
return users.map { user ->
val name = if (user.lastName != null) {
if (user.firstName != null) {
"${user.firstName} ${user.lastName}"
} else {
user.lastName ?: "Unknown"
}
} else {
user.firstName ?: "Unknown"
}
name
}
}
כדי לפשט את הנושא עוד יותר, אנחנו יכולים להסיר לחלוטין את המשתנה name
:
val formattedUserNames: List<String>
get() {
return users.map { user ->
if (user.lastName != null) {
if (user.firstName != null) {
"${user.firstName} ${user.lastName}"
} else {
user.lastName ?: "Unknown"
}
} else {
user.firstName ?: "Unknown"
}
}
}
9. נכסים ונכסים לגיבוי
שמנו לב שהממיר האוטומטי החליף את הפונקציה getFormattedUserNames()
בנכס בשם formattedUserNames
שיש לו getter מותאם אישית. מאחורי הקלעים, Kotlin עדיין יוצר שיטת getFormattedUserNames()
שמחזירה List
.
ב-Java, נחשוף את מאפייני הכיתה שלנו באמצעות פונקציות getter ו-Setter. קוטלין מאפשר לנו ליצור הבחנה טובה יותר בין מאפיינים של מחלקה, מתבטאת בשדות ובפונקציות, פעולות שמחלקה יכולה לבצע, מבוטאות בפונקציות. במקרה שלנו, המחלקה Repository
פשוטה מאוד ולא מבצעת פעולות כלשהן כך שהיא תכיל שדות בלבד.
הלוגיקה שהופעלה בפונקציה Java getFormattedUserNames()
מופעלת עכשיו בעת הפעלת getter של מאפיין formattedUserNames
Kotlin.
אין לנו שדה מפורש המשויך לנכס formattedUserNames
, אבל קוטלין מספק לנו שדה גיבוי אוטומטי בשם field
, שאליו אנחנו יכולים לגשת, אם יהיה בכך צורך ממפתחים ומהגדרות בהתאמה אישית.
עם זאת, לפעמים אנחנו רוצים פונקציונליות נוספת ששדה הגיבוי האוטומטי לא מספק. לפניכם דוגמה להמחשה.
במסגרת הכיתה Repository
, יש לנו רשימה מרובה של משתמשים שנחשפים בפונקציה getUsers()
, שנוצרה מקוד Java שלנו:
fun getUsers(): List<User>? {
return users
}
הבעיה כאן היא שהחזרת users
על ידי כל צרכן ממחזור המאגר יכולה לשנות את רשימת המשתמשים שלנו – לא רעיון טוב! תיקון הבעיה באמצעות נכס גיבוי.
תחילה, יש לשנות את השם של users
ל-_users
. עכשיו צריך להוסיף נכס ציבורי שאינו קבוע שמחזיר רשימה של משתמשים. זה הזמן להתקשר אל users
:
private val _users = mutableListOf<User>()
val users: List<User>
get() = _users
בעקבות השינוי הזה, נכס _users
הפרטי יהפוך לנכס הגיבוי של נכס users
הגלוי לכול. מחוץ לקטגוריה של Repository
, לא ניתן לשנות את הרשימה _users
, כי לקוחות של הכיתה יכולים לגשת לרשימה רק דרך users
.
קוד מלא:
object Repository {
private val _users = mutableListOf<User>()
val users: List<User>
get() = _users
val formattedUserNames: List<String>
get() {
return _users.map { user ->
if (user.lastName != null) {
if (user.firstName != null) {
"${user.firstName} ${user.lastName}"
} else {
user.lastName ?: "Unknown"
}
} else {
user.firstName ?: "Unknown"
}
}
}
init {
val user1 = User("Jane", "")
val user2 = User("John", null)
val user3 = User("Anne", "Doe")
_users.add(user1)
_users.add(user2)
_users.add(user3)
}
}
10. פונקציות ונכסים ברמה עליונה ותוספים
נכון לעכשיו, הכיתה Repository
יודעת איך לחשב את שם המשתמש המפורמט עבור אובייקט User
. עם זאת, אם אנחנו רוצים להשתמש מחדש באותו לוגיקה של עיצוב בכיתות אחרות, עלינו להעתיק ולהדביק אותו או להעביר אותו לכיתה User
.
Kotlin מאפשר להצהיר על פונקציות ומאפיינים מחוץ לכל מחלקה, אובייקט או ממשק. לדוגמה, הפונקציה mutableListOf()
שבה השתמשנו כדי ליצור מופע חדש של List
מוגדרת ישירות ב-Collections.kt
מהספרייה הרגילה.
ב-Java, בכל פעם שתזדקקו לפונקציונליות כלשהי, תצטרכו ליצור מחלקת Util
ולהצהיר על הפונקציונליות הזו כפונקציה סטטית. ב-Cotlin אפשר להצהיר על פונקציות ברמה עליונה בלי כיתה. עם זאת, Kotlin מאפשר גם ליצור פונקציות של תוספים. פונקציות אלה מרחיבות סוג מסוים אך מוצהרות מחוץ לסוג זה. לכן, יש להם תחום עניין משותף מהסוג הזה.
ניתן להגביל את הרשאות הגישה של פונקציות ומאפיינים של תוספים באמצעות מגבילי חשיפה. הן מגבילות את השימוש בכיתות שצריכות את התוספים, והן לא מזהמות את מרחב השמות.
עבור המחלקה User
, אנחנו יכולים להוסיף פונקציית תוסף שמחשב את השם בפורמט, או לשמור את השם המעוצב בנכס של תוסף. אפשר להוסיף אותו מחוץ לכיתה ב-Repository
באותו קובץ:
// extension function
fun User.getFormattedName(): String {
return if (lastName != null) {
if (firstName != null) {
"$firstName $lastName"
} else {
lastName ?: "Unknown"
}
} else {
firstName ?: "Unknown"
}
}
// extension property
val User.userFormattedName: String
get() {
return if (lastName != null) {
if (firstName != null) {
"$firstName $lastName"
} else {
lastName ?: "Unknown"
}
} else {
firstName ?: "Unknown"
}
}
// usage:
val user = User(...)
val name = user.getFormattedName()
val formattedName = user.userFormattedName
לאחר מכן נוכל להשתמש בפונקציות ובמאפיינים של התוספים כאילו הם חלק מהכיתה User
.
מכיוון שהשם בפורמט הוא נכס של המשתמש ולא פונקציונליות של המחלקה Repository
, יש להשתמש בנכס התוסף. הקובץ Repository
נראה עכשיו:
val User.formattedName: String
get() {
return if (lastName != null) {
if (firstName != null) {
"$firstName $lastName"
} else {
lastName ?: "Unknown"
}
} else {
firstName ?: "Unknown"
}
}
object Repository {
private val _users = mutableListOf<User>()
val users: List<User>
get() = _users
val formattedUserNames: List<String>
get() {
return _users.map { user -> user.formattedName }
}
init {
val user1 = User("Jane", "")
val user2 = User("John", null)
val user3 = User("Anne", "Doe")
_users.add(user1)
_users.add(user2)
_users.add(user3)
}
}
הספרייה הרגילה של Kotlin משתמשת בפונקציות של תוספים כדי להרחיב את הפונקציונליות של מספר ממשקי API של Java. פונקציות רבות ב-Iterable
וב-Collection
מיושמות כפונקציות של תוספים. לדוגמה, הפונקציה map
שבה השתמשנו בשלב הקודם היא פונקציית תוסף ב-Iterable
.
11. פונקציות בהיקף: הרשאה, יישום, הפעלה, הרצה, גם
בקוד הכיתה Repository
, אנחנו מוסיפים מספר אובייקטים של משתמשים לרשימה _users
. ניתן לבצע את השיחות האלה בצורה אידאומטית יותר באמצעות פונקציות של היקף.
כדי להפעיל קוד רק בהקשר של אובייקט ספציפי, ללא צורך בגישה לאובייקט על סמך השם שלו, Kotlin יצר 5 פונקציות היקף: let
, apply
, with
, run
ו-also
. הקצרים והחזקים, בכל הפונקציות האלה, יש מקלט (this
), שעשוי להיות ארגומנט (it
) והם יכולים להחזיר ערך. עליך להחליט באיזה מהם להשתמש, בהתאם למטרה שלך.
הנה דף מידע שימושי שיעזור לך לזכור את הדברים הבאים:
מכיוון שאנחנו מגדירים את האובייקט _users
שלנו בRepository
, אנחנו יכולים להפוך את הקוד לאידיומטי יותר באמצעות הפונקציה apply
:
init {
val user1 = User("Jane", "")
val user2 = User("John", null)
val user3 = User("Anne", "Doe")
_users.apply {
// this == _users
add(user1)
add(user2)
add(user3)
}
}
12. סיום
במעבדה זו התייחסנו ליסודות הנחוצים כדי להתחיל לקחת בחשבון מחדש את הקוד שלכם מ-Java ועד קוטלין. החישוב מחדש אינו תלוי בפלטפורמת הפיתוח ועוזר לכם להבטיח שהקוד שאתם כותבים הוא אידיומטי.
אידיומטי קוטלין יוצר קוד קצר ומתוק. עם כל התכונות שקוטלין מספקת, יש כל כך הרבה דרכים להפוך את הקוד שלכם לבטוח, תמציתי יותר וקריא יותר. לדוגמה, אנחנו יכולים אפילו לבצע אופטימיזציה של המחלקה שלנו ב-Repository
על ידי יצירת רשימה _users
של המשתמשים ישירות בהצהרה, וכך מבטלים את החסימה של init
:
private val users = mutableListOf(User("Jane", ""), User("John", null), User("Anne", "Doe"))
כללנו מגוון רחב של נושאים, החל מטיפול באפסים, טונים, מחרוזות ואוספים ועד נושאים כמו פונקציות תוספים, פונקציות ברמה עליונה, נכסים ופונקציות היקף. עברנו משתי כיתות Java ושני קורסים של Kotlin שנראים עכשיו כך:
User.kt
class User(var firstName: String?, var lastName: String?)
Repository.kt
val User.formattedName: String
get() {
return if (lastName != null) {
if (firstName != null) {
"$firstName $lastName"
} else {
lastName ?: "Unknown"
}
} else {
firstName ?: "Unknown"
}
}
object Repository {
private val _users = mutableListOf(User("Jane", ""), User("John", null), User("Anne", "Doe"))
val users: List<User>
get() = _users
val formattedUserNames: List<String>
get() = _users.map { user -> user.formattedName }
}
להלן TL ו-DR של הפונקציות של Java והמיפוי שלהן ל-Kotlin:
Java | קוטלין |
אובייקט אחד ( | אובייקט אחד ( |
|
|
|
|
מחלקה ששומרת רק נתונים | כיתה אחת ( |
אתחול בבונה | אתחול בבלוק |
| השדות והפונקציות שהוכרזו ב- |
סינגל טון |
|
כדי לקבל מידע נוסף על קוטלין ועל אופן השימוש בו בפלטפורמה שלכם, אפשר לעיין במקורות המידע הבאים:
- קוטלין קואנס
- מדריכים ב-Kotlin
- פיתוח אפליקציות ל-Android באמצעות Kotlin – קורס בחינם
- מחנה קוטלין למתכננים
- Kotlin למפתחי Java - קורס בחינם במצב ביקורת