Kotlin 中的類別和物件執行個體

在本課程的程式碼研究室,您會建構 Dice Roller Android 應用程式。使用者「擲骰子」時,系統會隨機產生結果。系統在產生結果時會考慮到骰子的面數。例如,只能來自 6 面骰子的 1-6 值。

這是最終應用程式的畫面。

為了協助您專注瞭解這個應用程式的最新程式設計概念,您會使用 Kotlin 程式設計工具為基礎的瀏覽器,來建立核心應用程式功能。該程式會將結果輸出至主控台。稍後您將在 Android Studio 中導入使用者介面。

在第一個程式碼研究室中,您要建立 Kotlin 程式來模擬擲骰子的動作,並輸出隨機數字,就像實際擲骰子一樣。

必要條件

  • 如何在 https://try.kotlinlang.org/ 中開啟、編輯和執行程式碼
  • 建立並執行使用變數和函式的 Kotlin 程式,然後將結果輸出至主控台。
  • 使用包含 ${variable} 標記的字串範本,設定文字內的數字格式。

課程內容

  • 如何透過程式輔助方式產生隨機數字來模擬擲骰子的動作。
  • 如何透過建立含有變數和方法的 Dice 類別來建構程式碼。
  • 如何建立類別的物件執行個體、修改其變數,以及呼叫其方法。

建構項目

  • 使用 Kotlin 程式工具為基礎的瀏覽器,建立可執隨機擲骰子的 Kotlin 程式。

軟硬體需求

  • 已連上網際網路的電腦

遊戲通常都有隨機的特性,讓您贏得隨機獎勵,或是在棋盤上隨機移動步數。在日常生活中,您可以使用隨機數字和字母來產生更安全的密碼!

您可以編寫程式模擬擲骰子的動作,而不必真的擲骰子。每次擲骰子時,可能值範圍內的任何數字都可以是結果。幸運的是,你並不需要為這類程式建構自己的隨機數字產生器。大部分程式設計語言 (包括 Kotlin) 都內建隨機產生數字的方法。在這項工作中,您會使用 Kotlin 程式碼產生一個隨機數字。

設定範例程式碼

  1. 在瀏覽器中開啟 https://try.kotlinlang.org/
  2. 刪除程式碼編輯器中所有現有程式碼,並以下方程式碼取代。這是您在舊版程式碼研究室中所使用的 main() 函式 (請參閱編寫第一個 Kotlin 程式程式碼研究室)。
fun main() {

}

使用隨機函式

擲骰子時,您要設法呈現所有有效的骰子值。一般的 6 面骰子,可接受的結果有 1、2、3、4、5 和 6。

你在先前的課程中已學會資料類型,例如 Int 代表整數,String 代表文字。IntRange 是另一種資料類型,代表從起點到終點的整數範圍。IntRange 是適合的資料類型,用於代表骰子可能產生的值。

  1. main() 函式中,請將變數定義為名為 diceRangeval。將這個值指派給從 1 到 6 的 IntRange,表示 6 面骰子可以累計的整數範圍。
val diceRange = 1..6

可以看出,1..6 是 Kotlin 範圍,因為它包含開始數字、兩個點,後面接著是結束號碼數字 (中間沒有空格)。以下列舉其他整數範圍的例子:2..5 代表數字 2 到 5,100..200 代表數字 100 到 200。

與呼叫 println() 會指示系統列印指定文字的做法類似,您可以使用名為 random() 的函式,產生並傳回指定範圍的隨機數字。和之前一樣,您可以將結果儲存在變數中。

  1. main() 中,將變數定義為名為 randomNumberval
  2. 請為 randomNumber 設定對 diceRange 範圍呼叫 random() 的結果,如下所示。
 val randomNumber = diceRange.random()

請注意,在變數與函式呼叫之間使用句號或一點,即可在 diceRange 呼叫 random()。您可以將其讀取為「從 diceRange 產生隨機數字」,然後儲存在 randomNumber 變數中。

  1. 如要查看隨機產生的數字,請使用字串格式標記法 (也稱為「字串範本」) ${randomNumber} 輸出,如下所示。
println("Random number: ${randomNumber}")

完成的程式碼應如下所示。

fun main() {
    val diceRange = 1..6
    val randomNumber = diceRange.random()
    println("Random number: ${randomNumber}")
}
  1. 執行程式碼數次。每次都會看到下方的輸出內容,其中顯示不同的隨機數字。
Random number: 4

擲骰子時,骰子是你手中的真實物品。您剛才編寫的程式碼雖然可以正常運作,但很難讓人聯想到真正的骰子。編寫程式時,如果讓程式更貼近所代表的事物,會讓人更容易理解。所以,如果能導入程式輔助專用骰子也沒關係!

所有骰子的運作原理都大同小異。它們有相同的屬性 (例如面),而且有相同的行為 (例如可以投擲)。在 Kotlin 中,您可以建立骰子的程式輔助藍圖,說明骰子有面,而且可以擲出隨機數字。這個藍圖稱為類別

接下來,就可以從該類別中建立實際骰子物件,稱為「物件執行個體」。舉例來說,你可以製作 12 邊的骰子,或是 4 邊的骰子。

定義 Dice 類別

在下列步驟中,您會定義名為 Dice 的新類別來代表可投擲的骰子。

  1. 如要開始執行更新作業,請清除 main() 函式中的程式碼,最後執行程式碼如下所示。
fun main() {

}
  1. 在這個 main() 函式下方,新增空白行,然後新增程式碼來建立 Dice 類別。如下所示,請先輸入關鍵字 class,然後輸入類別名稱,後面加左右大括號。請在左右大括號之間留出空格,以便放入類別的程式碼。
class Dice {

}

在類別定義中,您可以使用變數為類別指定一或多個屬性。真實的骰子可以有多個面、一種顏色或重量。在這項工作中,您的重點會放在骰子面數的屬性上。

  1. Dice 類別中,新增名為 sidesvar 做為骰子的面數。將 sides 設為 6。
class Dice {
    var sides = 6
}

就行了!現在您有一個非常簡單的類別,可代表骰子。

建立骰子類別的執行個體

有了這個 Dice 類別,您就有了表示骰子的藍圖。如要在程式中出現真正的骰子,您必須建立 Dice 物件執行個體。(如果您需要三個骰子,請建立三個物件執行個體)。

  1. 如要建立 Dice 的物件執行個體,請在 main() 函式中建立名為 myFirstDiceval,並將其初始化為 Dice 類別。請注意類別名稱之後的括號,這代表您要從該類別建立新的物件執行個體。
fun main() {
    val myFirstDice = Dice()
}

現在,您已經有了根據藍圖建立的 myFirstDice 物件,便可以存取其屬性。Dice 唯一的屬性是 sides。您可以使用「點標記法」存取屬性。因此,如要存取 myFirstDicesides 屬性,請呼叫 myFirstDice.sides,這個發音為 myFirstDicesides

  1. myFirstDice 宣告下方,新增 println() 陳述式,以輸出 myFirstDice.sides 數量
println(myFirstDice.sides)

您的程式碼應如下所示。

fun main() {
    val myFirstDice = Dice()
    println(myFirstDice.sides)
}

class Dice {
    var sides = 6
}
  1. 執行程式,它應會輸出 Dice 類別中定義的 sides 數量。
6

你現在已經有一個 Dice 課程,實際 1 道骰子的 myFirstDice 則含有 6 則 sides

我們來擲骰子吧!

擲骰子

您之前用了函式來執行輸出蛋糕圖層的動作。擲骰子操作也可以用函式來實作。由於所有骰子都可以投擲,所以您可以在 Dice 類別中新增該函式。在類別中定義的函式也稱為「方法」

  1. Dice 類別 sides 變數下方﹐插入一行空行,然後建立新的函式來擲骰子。請依序輸入 Kotlin 關鍵字 fun、方法的名稱、括號 () 以及左右大括號 {}。您可以在左右大括號之間留出空行,以便輸入更多程式碼,如下所示。您的課程應如下所示。
class Dice {
    var sides = 6

    fun roll() {

    }
}

在你倒出六面骰子時,會產生 1 到 6 之間的隨機數字。

  1. roll() 方法中建立一個 val randomNumber。在 1..6 範圍內指派一個隨機數字。使用點標記法對該範圍呼叫 random()
val randomNumber = (1..6).random()
  1. 產生隨機號碼後,將其輸出至主控台。完成的 roll() 方法應如以下程式碼所示。
fun roll() {
     val randomNumber = (1..6).random()
     println(randomNumber)
}
  1. 如要實際投擲 myFirstDice,請在 main() 中對 myFirstDice 呼叫 roll() 方法。您可以使用「點標記法」來呼叫方法。那麼,如要對 myFirstDice 呼叫 roll() 方法,請輸入 myFirstDice.roll(),讀音為「myFirstDiceroll()」。
myFirstDice.roll()

完成的程式碼應如下所示。

fun main() {
    val myFirstDice = Dice()
    println(myFirstDice.sides)
    myFirstDice.roll()
}

class Dice {
    var sides = 6

    fun roll() {
        val randomNumber = (1..6).random()
        println(randomNumber)
    }
}
  1. 執行程式碼!你應會看到隨機擲出的骰子數字小於骰子面數。執行程式碼數次,會發現面數維持不變,而擲骰子結果的值則會改變。
6
4

恭喜!您已使用 sides 變數和 roll() 函式來定義 Dice 類別。在 main() 函式中,您已建立新的 Dice 物件執行個體,然後對該執行個體呼叫 roll() 方法,以產生隨機數字。

目前您在 roll() 函式中輸出 randomNumber 的值,一切運作正常!但在某些情況下,將函式結果傳回給呼叫該函式的對象會更為實用。舉例來說,您可以將 roll() 方法的結果指派給變數,然後為玩家移動相應的步數。我們來看看具體操作。

  1. main() 中,修改顯示 myFirstDice.roll() 的行。建立名為 diceRollval。請設為 roll() 方法傳回的值。
val diceRoll = myFirstDice.roll()

目前還沒有任何變化,因為 roll() 尚未傳回任何內容。這個程式碼必須傳回一些內容,才能正常運作。roll()

在先前的程式碼研究室中,您瞭解到需要為函式的輸入引數指定資料類型。同理,您需要為函式傳回的資料指定資料類型。

  1. 變更 roll() 函式,以指定要傳回的資料類型。在本例子中,隨機數字是 Int,因此傳回類型為 Int。指定傳回類型的語法如下:在函式名稱後面,在括號後加上冒號和空格,然後為函式的傳回類型加上 Int 關鍵字。函式定義的程式碼應如下所示。
fun roll(): Int {
  1. 執行這個程式碼。問題檢視畫面中會顯示錯誤訊息。內容如下:
A ‘return'  expression is required in a function with a block body. 

您已變更函式定義以傳回 Int,但系統必須

程式碼實際上並不會傳回 Int。「區塊內文」或「函式主體」是指函式的大括號之間的程式碼。您可以使用函式主體結尾的 return 陳述式傳回函式的值,藉此修正這個錯誤。

  1. roll() 中移除 println() 陳述式,並以 randomNumberreturn 來取代。roll() 函式的程式碼應如下所示。
fun roll(): Int {
     val randomNumber = (1..6).random()
     return randomNumber
}
  1. main() 中,移除骰子面的輸出陳述式。
  2. 新增陳述式,以詳盡的說明句子輸出 sidesdiceRoll 的值。您已完成的 main() 函式應與下方的程式碼類似。
fun main() {
    val myFirstDice = Dice()
    val diceRoll = myFirstDice.roll()
    println("Your ${myFirstDice.sides} sided dice rolled ${diceRoll}!")
}
  1. 執行程式碼,輸出結果應如下所示。
Your 6 sided dice rolled 4!

以下是目前為止的所有程式碼。

fun main() {
    val myFirstDice = Dice()
    val diceRoll = myFirstDice.roll()
    println("Your ${myFirstDice.sides} sided dice rolled ${diceRoll}!")
}


class Dice {
    var sides = 6

    fun roll(): Int {
        val randomNumber = (1..6).random()
        return randomNumber
    }
}

不是所有骰子都有 6 面!骰子有各形和大小:4 面,8 面,最多 120 個!

  1. 在您的 Dice 類別中,在 roll() 方法中,將硬式編碼的 1..6 改為使用 sides,這樣對於範圍數和隨機累計的數值,對側邊數沒有影響。
val randomNumber = (1..sides).random()
  1. main() 函式中的列印骰子膠卷下方,將我的 FirstDicesides 變更為 20。
myFirstDice.sides = 20
  1. 在下方複製現有的列印聲明,然後在變更邊數後貼到下方。
  2. myFirstDice 的輸出結果替換為對 diceRoll 呼叫 roll() 方法的輸出結果。
println("Your ${myFirstDice.sides} sided dice has rolled a ${myFirstDice.roll()}!")

您的計劃應如下所示。

fun main() {
   
    val myFirstDice = Dice()
    val diceRoll = myFirstDice.roll()
    println("Your ${myFirstDice.sides} sided dice rolled ${diceRoll}!")

    myFirstDice.sides = 20
    println("Your ${myFirstDice.sides} sided dice rolled ${myFirstDice.roll()}!")
}

class Dice {
    var sides = 6

    fun roll(): Int {
        val randomNumber = (1..sides).random()
        return randomNumber
    }
}
  1. 請執行您的程式,您應該會看到關於 6 面骰子的訊息,以及 20 面骰子的第二個訊息。
Your 6 sided dice rolled 3!
Your 20 sided dice rolled 15!

類別的概念是代表事物,通常是現實世界中的實物。在本例子中,Dice 類別代表實際的骰子。在現實世界中,骰子不可能改變面數。如果您需要不同的面數,便需要用另一個骰子。在程式輔助方面,代表您應建立新的骰子物件執行個體,其中包含所需的面數,而不是變更現有 Dice 物件執行個體的面屬性。

在這項工作中,您要修改 Dice 類別,以便在建立新的執行個體時指定面數。將 Dice 類別定義變更為接受邊數的引數。這類似於函式可接受輸入引數的方式。

  1. Dice 類別定義修改為接受名為 numSides 的整數引數。類別中的程式碼不會改變。
class Dice(val numSides: Int) {
   // Code inside does not change.
}
  1. Dice 類別中,刪除 sides 變數,因為您現在可以使用 numSides
  2. 此外,請將範圍修正為使用 numSides

您的 Dice 類別應如下所示。

class Dice (val numSides: Int) {

    fun roll(): Int {
        val randomNumber = (1..numSides).random()
        return randomNumber
    }
}

執行這個程式碼時,您會看見很多錯誤,因為您需要更新 main() 以處理對 Dice 類別所做的變更。

  1. main() 中,如要建立具有 6 面方形的 myFirstDice,您現在必須傳入側邊數字做為 Dice 類別的引數,如下所示。
    val myFirstDice = Dice(6)
  1. 在列印陳述式中,將 sides 變更為 numSides
  2. 然後在下方,刪除將 sides 變更為 20 的程式碼,因為該變數已不再存在。
  3. 請一併刪除下方的 println 陳述式。

您的 main() 函式應如下方所示;如果您執行此程式碼,應該不會出現錯誤。

fun main() {
    val myFirstDice = Dice(6)
    val diceRoll = myFirstDice.roll()
    println("Your ${myFirstDice.numSides} sided dice rolled ${diceRoll}!")
}
  1. 輸出第一個擲骰子結果後,新增程式碼來建立並輸出第二個名為 mySecondDiceDice 物件,其中包含 20 面。
    val mySecondDice = Dice(20)
  1. 新增可列印及列印傳回值的列印陳述式。
println("Your ${mySecondDice.numSides} sided dice rolled  ${mySecondDice.roll()}!")
  1. 您已完成的 main() 函式會如下所示。
fun main() {
    val myFirstDice = Dice(6)
    val diceRoll = myFirstDice.roll()
    println("Your ${myFirstDice.numSides} sided dice rolled ${diceRoll}!")
    
    val mySecondDice = Dice(20)
    println("Your ${mySecondDice.numSides} sided dice rolled ${mySecondDice.roll()}!")
}

class Dice (val numSides: Int) {

    fun roll(): Int {
        val randomNumber = (1..numSides).random()
        return randomNumber
    }
}
  1. 執行已完成的程式,輸出結果應該會如下所示。
Your 6 sided dice rolled 5!
Your 20 sided dice rolled 7!

編寫程式碼時,應保持簡潔。您可以去除 randomNumber 變數,並直接傳回隨機數字。

  1. 變更 return 陳述式即可直接傳回隨機數字。
    fun roll(): Int {
        return (1..numSides).random()
    }

在第二個輸出陳述式中,您會將用於取得隨機號碼的呼叫,放入字串範本中。您可以按照在第一個輸出陳述式中採取的相同動作,藉去除 diceRoll 變數。

  1. 在字串範本中呼叫 myFirstDice.roll() 並刪除 diceRoll 變數。您的 main() 程式碼的前兩行現在看起來像這樣。
    val myFirstDice = Dice(6)
    println("Your ${myFirstDice.numSides} sided dice rolled ${myFirstDice.roll()}!")
  1. 執行程式碼,輸出的結果應該沒有差異。

這是您重構後的最終程式碼。

fun main() {
    val myFirstDice = Dice(6)
    println("Your ${myFirstDice.numSides} sided dice rolled ${myFirstDice.roll()}!")
    
    val mySecondDice = Dice(20)
    println("Your ${mySecondDice.numSides} sided dice rolled ${mySecondDice.roll()}!")
}

class Dice (val numSides: Int) {

    fun roll(): Int {
        return (1..numSides).random()
    }
}
fun main() {
    val myFirstDice = Dice(6)
    val diceRoll = myFirstDice.roll()
    println("Your ${myFirstDice.numSides} sided dice rolled ${diceRoll}!")
    
    val mySecondDice = Dice(20)
    println("Your ${mySecondDice.numSides} sided dice rolled ${mySecondDice.roll()}!")
}

class Dice (val numSides: Int) {

    fun roll(): Int {
        return (1..numSides).random()
    }
}
  • IntRange 呼叫 random() 函式以產生隨機數字:(1..6).random()
  • 類別就像是物件的藍圖,會包含屬性和行為,可做為變數和函式來實作。
  • 類別的執行個體代表一個物件,通常是實物,例如骰子。您可以對物件呼叫動作,並變更其屬性。
  • 您可以在建立執行個體時,藉由指定類別定義的引數,將輸入內容傳送至類別。例如:class Dice(val numSides: Int),然後使用 Dice(6) 建立執行個體。
  • 函式可以傳回結果。在函式定義中指定要傳回的資料類型,並在函式主體中使用 return 來傳回一些內容。例如:fun example(): Int { return 5 }

請練習下列項目:

  • Dice 類別提供另一個顏色的屬性,然後建立多個有不同面數和顏色的骰子執行個體!
  • 建立 Coin 類別,讓它能夠翻轉、建立類別執行個體,以及拋擲一些硬幣!你該如何搭配範圍使用隨機() 函式來完成硬幣翻轉作業?