在第6課,學到了如何設計自己的類別,包含定義建構式、屬性與方法,並建立物件來操作。本文會講解存取權限的設置,讓類別中的這些成員只能在允許的地方被存取。
接著介紹初始化函數,讓物件建立後還能進行額外的工作。最後提供一個情境,讓讀者練習類別的設計,並接觸更多第5課所學到的集合的方法。
一、存取權限
學習 Java 時曾經認識 public 與 private 等存取權限,它決定了類別、方法與屬性能夠被使用的範圍。
在 Kotlin 也有相同的關鍵字可以控制,其中 public、protected 與 private 的效果與 Java 是一樣的。不同的地方在於 Kotlin 沒有「package private」的概念,且預設的存取權限是 public。以下示範使用 private。
class Student constructor(val id: String,
firstName: String,
lastName: String) {
var fullName = "$firstName $lastName"
private set
private fun getDetail() = "Id: $id, Full name: $fullName"
fun printDetail() {
println(getDetail())
}
}
這邊將 fullName 的 set 方法設為 private,意義在於保護該屬性不被外部賦予值。此外,get 方法的權限固定與屬性相同,此處仍為 public。而 set 方法的存取權限不可大於 get 方法。
至於 getDetail 方法已經設為 private,所以無法在主程式被呼叫。但同一類別中的 printDetail 方法則可以。
Kotlin 比 Java 多了一種存取權限,叫做「internal」,它的範圍涵蓋當前的模組。簡單的程式專案一般只有一個模組,因此我們看到的大多數範例中,internal 的效果常常與 public 相同,但實質範圍仍小於 public。
二、初始化函數
如果想要在主要建構式每次產生物件後,都能執行額外的程式,可以利用「初始化函數」。其實它就跟第6課第三節介紹的次要建構式,結束後能執行額外程式的道理相同。
class Course constructor(id: String,
name: String,
credit: Int) {
val id: String
var name: String
var credit: Int
init {
this.id = id
this.name = if (name.isEmpty()) "(Not provided)" else name
this.credit = if (credit < 0) 0 else credit
println("Main constructor finished.")
}
constructor(id: String, name: String)
: this(id, name, 0) {
println("Sub constructor finished.")
}
}
使用「init {}」語法可以宣告初始化函數,它會在主要建構式結束後執行。在一個類別中可以有多個,執行順序等同宣告順序。至於前面提到次要建構式的額外程式,則會等初始化函數結束後才執行。
三、綜合應用
結合先前所學,以下提供一個練習,讀者可以嘗試用程式解決問題。下面給定了可選修課程的 Map,key 為課程編號,value 為整個課程資料。請模擬出學生在某學年度某學期進行選課,並印出選課清單的過程。
fun initCources(): Map<String, Course> {
val cources = listOf(
Course("IT001", "計算機概論", 3),
Course("IT002", "Java程式設計", 3),
Course("IT003", "資料庫系統應用", 3),
Course("MG001", "人力資源管理", 3),
Course("MG002", "管理資訊系統", 3),
Course("FN001", "中等會計學I", 4),
Course("FN001", "財務報表分析", 3)
)
return cources.map { it.id to it }.toMap()
}
實作內容:
- 請設計一個「選課紀錄」的新類別,裡面能儲存學生與選課資料。也可視需要,練習重新設計課程(Course)與學生(Student)的類別。
- 在選課紀錄類別中撰寫能產生該學期選課清單文字的方法,內容不拘。
讀者可參考下方筆者的做法。
class Course constructor(val id: String,
name: String,
credit: Int) {
var name = if (name.isEmpty()) "(Not provided)" else name
var credit = if (credit < 0) 0 else credit
}
class Student constructor(val id: String,
firstName: String,
lastName: String) {
var fullName = "$firstName $lastName"
}
課程類別的屬性包含編號、名稱與學分數。在建立物件時,若名稱給予空字串,則會修正為「(Not provided)」文字。若學分數給予小於0的數值,則會修正為0。學生類別的屬性包含編號與完整名字。在建立物件時,會將建構式參數中的姓與名組合成完整名字。
class CourceSelection(var year: Int,
var semester: Int,
var student: Student) {
var courses: MutableSet<Course> = mutableSetOf()
fun addCourse(vararg selectedCourse: Course?) {
val courses = selectedCourse.filterNotNull()
this.courses.addAll(courses.toList())
}
fun generateDetail(): String {
var detail = ""
var totalCredit = 0
detail += "${year}學年度第${semester}學期選課清單\n"
detail += "學生:${student.id} ${student.fullName}\n"
detail += "課程編號\t\t課程名稱\t\t學分數\n"
for (c in courses) {
detail += "${c.id}\t\t${c.name}\t\t${c.credit}\n"
totalCredit += c.credit
}
detail += "總學分:$totalCredit\n"
return detail
}
}
選課紀錄類別的屬性包含年度、學期、學生資料與課程清單。課程清單使用 Set 的原因是不應該有課程重複的情形,而 Set 正好具備元素不重複的特色。
選課紀錄提供 addCourse 方法讓外部傳入課程資料,它是可變長度參數。接著使用 filterNotNull 方法篩選出非 null 的元素。再使用 toList 方法將陣列轉為 List,存入課程清單。
該類別還提供 generateDetail 方法來產生選課清單的文字。輸出的內容包含大標題、學生資料、選課清單的標頭與內容。
以上三個類別準備好,就能回到 main 主程式實際使用。
fun main(args: Array<String>) {首先取得題目給定的可選修課程 Map,再建立選課紀錄的物件。
// 取得可選修課程
val cources = initCources()
// 學生 Anna 進行選課
val student = Student("1056001", "Anna", "Su")
val selection = CourceSelection(107, 2, student)
selection.addCourse(
cources["IT003"], cources["MG001"], cources["MG002"],
cources["FN001"], cources["FN002"]
)
// 印出選課清單
println(selection.generateDetail())
}
接著用 Map 的 key 可以取出課程物件,再將課程添加到選課紀錄。由於 Map 在資料不存在時會回傳 null,因此 addCourse 方法才設計成允許接收 null 值再另行排除的做法,避免了 NullPointerException 例外。
選課完成後,便能呼叫方法,產生選課清單的文字並印出來。
留言
張貼留言