קוטלין למתכנתי ג'אווה - מבוא חלק ב'
9 דקות קריאה
שלום לכולם,
הפעם אנחנו נמשיך במדריך שלנו לקוטלין עבור מתכנתי ג'אווה.
בפעם הקודמת דיברנו על למה בכלל אנחנו נרצה ללמוד קוטלין
הסברנו על פונקציות, משתנים ואבני קוד בסיסיות
הפעם אנחנו נתחיל לדבר על חבילות ועל אובייקטים
הבהרה קטנה, הסדרה הזאת מניחה שאתם מתכנתים מנוסים, ומכירים את ג'אווה.
כמו כל הפוסטים בסדרה הזאת, הפוסט הזה מבוסס על הספר הנהדר Atomic Kotlin
חבילות וטסטים
הגדרת חבילות
אני מניח שרובכם מכירים את הקונספט של חבילות.
אבל לטובת אלו שאינם מכירים נעשה תקציר קצר.
כל מספר של של מחלקות אשר נועדו לשימוש חוזר ברכבי התוכנה שלנו צריכים להיות תחת ספרייה אחד על ידי שימוש ב- package
לדוגמה:
Kotlin
package com.yoururl.libraryname
// Componentes to reuse...
fun f() = "result"
בקוטלין אנחנו יכולים לשים כמה components בקובץ אחד, או לפזר components בכמה קבצים שונים תחת אותו שם של ספרייה.
בדוגמה למעלה הגדרנו component בשם f().
על מנת לשמור על שמות ספרייה יחודיים, אנחנו משתמשים בקונבציה של שם דומיין הפוך.
בדוגמה הזאת הדומיין שלנו הוא yoururl.com
בג'אווה השם של הסיפרייה גם מציין את הנתיב בו היא יושבת.
למשל, שם הספרייה com.yoururl.libraryname מחייב שהנתיב של התיקייה יהיה com/yoururl/libraryname.
קוטלין לא מחייבת את זה, אבל כיוון שמאוד נפוץ לראות פרוייקטים משותפים של ג'אווה וקוטלין אנחנו משתדלים לשמור על הקונבציה הזאת.
שימוש בחבילות
על מנת להשתמש בקוד מחבילה אחרת, אנחנו צריכים להשתמש ב-import
Kotlin
import com.yoururl.libraryname.*
fun main() {
val x x = f()
}
הכוכבית אחרי libraryname מציינת לקוטלין לייבא את כל ה-components תחת הספרייה libraryname.
יש גם דרך לייבא component בודד, על ידי החלפת הכוכבית בשם של ה-component.
atomic test
בהמשך הסדרה אנחנו נדבר הרבה על טסטים, וספרייה שימושית אשר נעבוד איתה היא atomictest.
זוהי לא ספרייה רשמית, אלא משהו שמחברי הספר כתבו.
אפשר למצוא אותה כאן
אחרי שמייבאים את atomictest, אנחנו יכולים להשתמש ב-eq (לבדוק שיוויון) וב-neq (לבדוק אי שיוויון).
Kotlin
import atomictest.*
fun main() {
val pi = 3.14
val pie = "A round dessert"
pi eq 3.14
pie eq "A round dessert"
pi neq pie
}
/* Output:
3.14
A round dessert
3.14
*/
היכולת להשתמש ב-eq או ב neq בלי נקודות או סוגריים נקראת infix notation.
אנחנו יכולים לקרוא לפונקציות infix בצורה הרגילה: pi.eq(3.14) או באמצאות infix notation, למשל: pi eq 3.14.
שתי הפונקציות eq ו neq מבצעות assersions וגם מדפיסות למסך את מה שיש בצד שמאל של הפונקציה.
בנוסף, הן ידפיסו את הודעת השגיאה אם הבדיקה נכשלה.
atomictest.trace משתמש בסינטקס של פונקציה על מנת להוסיף תוצאות שאפשר אחרי זה לבדוק אותן כולן ביחד
Kotlin
import atomictest.*
fun main() {
trace("Hello,")
trace(47)
trace("World!")
trace eq """
Hello,
47
World!
"""
}
וככה אפשר להחליף בקלות את println() בטסטים.
אובייקטים בכל מקום
קוטלין מוגדרת להיות שפה היברידית.
כלומר היא תומכת גם ב Object Oriented וגם ב-Functional Programming.
אובייקטים יכולים להחזיק שדות משני הסוגים vals ו-vars כדי להחזיק מידע (הם נקראית properties).
האובייקטים יכולים לבצע פעולות באמצעות פונקציות אשר מוגדרות בתוך המחלקות אשר מוגדרות כמתודות.
כאשר אנחנו מייצרים var או val של מחלקה שיצרנו, זה נקרא שאנחנו יוצרים מופע של המחלקה.
סוג מאוד פופולרי של אובייקטים הוא ה-container או collection.
קונטיינר זה אובייקט אשר מחזיק אובייקטים אחרים.
בדוגמה הבאה אנחנו נבצע פעולות על List אשר מחזיק Doubles.
המתודה listOf() מייצרת List חדש
Kotlin
import atomictest.eq
fun main() {
val lst = listOf(19.2, 88.3, 22.1)
lst[1] eq 88.3 // Indexing
lst.reversed() eq listOf(22.1, 88.3, 19.2)
lst.sorted() eq listOf(19.2, 22.1, 88.3)
lst.sum() eq 129.6
}
בקוטלין אנחנו לא צריכים להשתמש ב-import על מנת להשתמש ב-List.
קוטלין משתמשת בסוגריים מרובעים [] כדי לגשת לאיברים ברשימה באמצעות אינדקס, וכמובן שהספריה מתחילה ב-0.
הדוגמה למעלה גם מראה כמה מהכלים ש-List יודעת לתת out of the box, כמו למיין (sorted()), להפוך את הסדר (reversed()), ולסכום (sum()).
כאשר אנחנו קוראים ל-sorted() ול-reversed() הרשימה המקורית לא משתנית, אלא חוזרת רשימה חדשה עם התוצאה הרצויה.
ההתנהגות הזאת - שלא לשנות את האובייקט המקורי עליו אנחנו עובדים - היא הגישה השלטת בקוטלין, ומומלץ בחום שתאמצו אותה גם בקוד שלכם.
מחלקות
על מנת ליצור מחלקה חדשה, עלינו להשתמש ב-class, שם של המחלקה החדשה וגוף של המחלקה.
Kotlin
class NoBody
class SomeBody {
val name = "Shaked Eyal"
}
class EveryBody {
val all = listOf(SomeBody(), SomeBody(), SomeBody())
}
fun main() {
val noBody = NoBody()
val someBody = SomeBody()
val everyBody = EveryBody()
}
בדומה בג'אווה, על מנת ליצור מופע של המחלקה, אנחנו נקרא לשם של המחלקה עם סוגיים מיד אחריה
אך נשים להבדל שבקוטלין אנחנו לא צריכים להשתמש ב-new.
השדות של המחלקה יכולים להיות כל סוג.
למשל SomeBody מחזיקה שדה מסוג String ואילו EveryBody מחזיקה שדה מסוג של List של מופעי SomeBody
הנה דוגמה של קלאס עם פונקציות
Kotlin
package summary_2
import atomictest.eq
class Temperature {
var current = 0.0
var scale = "f"
fun setFahrenheit(now: Double) {
current = now
scale = "f"
}
fun setCelsius(now: Double) {
current = now
scale = "c"
}
fun getFahrenheit(): Double =
if (scale == "f")
current
else
current * 9.0 / 5.0 + 32.0
fun getCelsius(): Double =
if (scale == "c")
current
else
(current - 32.0) * 5.0 / 9.0
}
fun main() {
val temp = Temperature()
temp.setFahrenheit(98.6)
temp.getFahrenheit() eq 98.6
temp.getCelsius() eq 37.0
temp.setCelsius(100.0)
temp.getFahrenheit() eq 212.0
}
המתודות הללו הן בדיוק כמו הפונקציות שהגדרנו בחלק הקודם.
ההבדל היחיד הוא שכדי להריץ את הפונקציות הללו אנחנו נצטרך מופע של המחלקה שאליה המתודות הללו שייכות.
נקודה חשובה
למרות שהגדרנו את temp להיות val, אנחנו שורה אחרי זה משנים את השדות שלו.
ה-val רק ימנע מאיתנו לשנות את האובייקט אליו מצביע ה-temp, אבל הוא לא מונע מאיתנו לשנות את האובייקט המקורי.
הנה דוגמה לשתי מחלקות שהן הבסיס למשחק איקס עיגול
Kotlin
package summary_2
import atomictest.eq
class Cell {
var entry = ' '
fun setValue(e: Char): String {
return if (entry == ' ' && (e == 'X' || e == 'O')) {
entry = e
"Successful move"
} else {
"Invalid move"
}
}
}
class Grid {
val cells = listOf(
listOf(Cell(), Cell(), Cell()),
listOf(Cell(), Cell(), Cell()),
listOf(Cell(), Cell(), Cell())
)
fun play(e: Char, x: Int, y: Int): String =
if (x !in 0..2 || y !in 0..2)
"Invalid move"
else
cells[x][y].setValue(e)
}
fun main() {
val grid = Grid()
grid.play('X', 1, 1) eq "Successful move"
grid.play('X', 1, 1) eq "Invalid move"
grid.play('O', 1, 3) eq "Invalid move"
}
במחלקה Cell השדה entry מוגדר כ-var כדי שנוכל לשנות אותו אחר כך במתודה setValue().
השימוש ב ' מסמן לקוטלין שאנחנו עובדים עם Char ולא עם String, ולכן כל הפרמטרים שאמורים לדרוס את entry גם צריכים להיות מסוג Char.
המתודה setValue() בודקת האם ה-Cell פנוי והאם היא קיבלה פרמטר תקין והיא מחזירה String
המתודה play() בודקת אם הקורדינטות שהיא קיבלת הן בתחום התקין של הלוח, ואז קוראת ל-setValue() של Cell על מנת לנסות ולרשום בתוך התא את הערך.
בנאים - Constructors
בנאים יוצרים מופע חדש של מחלקות.
כמו בג'אווה אנחנו יכולים לשלוח מידע לבנאי באמצעות רשימת הפרמטרים שלו.
קודם כל נשים לב להבדל משמעותי של איך מגדירים בנאי בקוטלין. לא צריך ממש לרשות את הבנאי, אלא חלק מהגדרת הקלאס אנחנו נותנים את הפרמטרים.
במחלקה Badger אנחנו מגדירים את השדות בצורה ידנית, ואז בתוך הבנאי אנחנו משתמשים בפרמטרים כדי לאתחל אותם.
הפרמטרים id ו-years זמינים רק בזמן הבנאי.
נשים לב שאנחנו לא צריכים להגדיר ממש שדות במחלקה, אם אנחנו לא רוצים לשנות את השמות או הסוגים של הפרמטרים בבנאי. ככה עשינו גם ב- Snake וגם ב-Moose
משתנים בבנאי אשר מוגדרים כ-val לא יכולים להשתנות אבל אלו שהוגדרו כ-var יכולים.
בדומה לג'אווה בכל פעם שאתם מצפים ל-String אבל משתמשים באובייקט במקום, קוטלין תקרא ל-toString()
Kotlin
package summary_2
import atomictest.eq
class Badger(id: String, years: Int) {
val name = id
val age = years
override fun toString(): String {
return "Badger: $name, age: $age"
}
}
class Snake(var type: String, var length: Double) {
override fun toString(): String {
return "Snake: $type, length: $length"
}
}
class Moose(val age: Int, val height:Double) {
override fun toString(): String {
return "Moose, age: $age, height: $height"
}
}
fun main() {
Badger("Bob", 11) eq "Badger: Bob, age: 11"
Snake("Garden", 2.4) eq "Snake: Garden, length: 2.4"
Moose(16, 7.2) eq "Moose, age: 16, height: 7.2"
}
הגבלת נראות
קוטלין מספקת הגבלת גישה בצורה דומה ל-CPP או JAVA.
קוטלין משתמשת ב-public, private, protected, internal.
public או private יכולים להופיע לפני מחלקה, פונקציה או שדה.
בואו נעשה רענון קצר
public
מאפשר גישה לכולם, בפרט למי שמשתמש בקוד שאנחנו כותבים עכשיו.
ולכן כל שינוי בשדה public ישפיע על הקוד של מי שמתשמש בקוד שלנו.
אם לא מסופק שום הגדרה אחרת, קוטלין תשתמש ב-public באופן דיפולטיבי.
private
אם נגדיר מחלקה, פונקציה, או שדה כ-private זה אומר שזה יהיה זמין רק לקוד שרץ באותו הקונטקסט (או קובץ)
נשים לב ש-counter מוגדר להיות private, אבל כיוון שכל הקוד הנ"ל נמצא באותו קובץ, אז אנחנו יכולים לגשת אליו. כל קוד אשר יהיה מחוץ לקובץ הנ"ל לא תהיה לו גישה ל-counter.
Kotlin
package summary_2
import atomictest.trace
private var count = 0
private class Box(val dimension: Int) {
fun volume() =
dimension * dimension * dimension
override fun toString(): String {
return "Box volume: ${volume()}"
}
}
private fun countBox(box: Box) {
trace("$box")
count++
}
fun countBoxes() {
countBox(Box(4))
countBox(Box(5))
}
fun main() {
countBoxes()
trace("$count boxes")
trace eq """
Box volume: 64
Box volume: 125
""".trimIndent()
}
נשים לב ש-fuel ו-warning מוגדרים כ-private בקונטקסט של המחלקה JetPack. כלומר רק מי שמוגדר בתוך הקונטקסט של JetPack יכול לגשת אליהם.
burn() גם מוגדר כ-private, ולכן נגיש רק מתוך הקונטקסט של JetPack
המתדות fly() ו-check() הן שתיהן public ולכן אפשר לגשת אליהן מחוץ למחלקה
Kotlin
package summary_2
import atomictest.eq
class JetPack(private var fuel: Double) {
private var warning = false
private fun burn() =
if (fuel - 1 <= 0) {
fuel = 0.0
warning = true
} else {
fuel -= 1
}
public fun fly() = burn()
fun check() =
if (warning) {
"Warning"
} else {
"OK"
}
}
fun main() {
val jetPack = JetPack(3.0)
while (jetPack.check() != "Warning") {
jetPack.check() eq "OK"
jetPack.fly()
}
jetPack.check() eq "Warning"
}
internal
זה יכול להיות מועיל לחלק תוכנות גדולות למודולים.
מודול הוא חלק בקוד אשר מוגדר כעצמאי מבחינה לוגית.
אם נגדיר משהו כ-internal הוא יהיה נגיש רק בתוך המודול בו הוא מוגדר.
אנחנו יכולים להשתמש ב-[Gradle](https://gradle.org/) או ב-[Maven](https://maven.apache.org/) על מנת לחלק את הקוד שלנו למודולים, אך זה מחוץ לסקופ של הסדרה הזאת.
Exceptions
אם נסתכל על toDouble(), אשר ממירה String ל-Double.
מה יקרה אם נקרא לה עם פרמטר שהוא לא מספר?
Kotlin
fun main() {
// val i = "$1.9".toDouble()
}
אם אנחנו נוריד את הסימן של ההערה בשורה 2, הקוד שלנו יזרוק שגיאות.
כאשר שגיאה נזרקת, כמו בג'אווה גם בקוטלין הענף של הקוד שממנו נזרקה השגיאה עוצר, ואנחנו יוצאים מהקונטקסט הנוכחי.
אם אנחנו לא תופסים את השגיאה התוכנית שלנו קורסת ומציגה את ה-stack trace אשר הוביל לשגיאה.
ל-atomictest יש דרך נחמדה לטפל בשגיאות כדי שתוכלו לבדוק את הקוד שלכם בלי להקריס
Kotlin
import atomictest.*
fun main() {
capture {
"$1.9".toDouble()
} eq "NumberFormatException: For input string: \"$1.9\""
}
