Kotlin Tutorial(16)介面與實作(下) << 前情
物件導向基本的概念有封裝、繼承與多型,設計應用程式的時候,除了撰寫類別與函式提供需要的功能外,也要注意架構的完整與資料正確性,所以就需要加入封裝的作法。另外應用程式通常需要一些主要類別,Kotlin提供方便的資料類別,可以大幅度簡化程式碼。
存取修飾子
Kotlin提供private、internal、protected與public關鍵字,可以使用在宣告類別、建構式、介面與函式的宣告,讓應用程式的架構設計更完整,讓物件在運作的時候保持資料的正確性。
在應用程式套件裡面宣告的類別與函式,可以依照應用程式的架構,使用下列的關鍵字:
- private:只能在宣告的檔案中使用
- internal:只能在同一個模組中使用
- public:沒有使用private或internal的時候,預設為public,可以在應用程式任何地方使用
在類別與介面中宣告的屬性、建構式與函式,可以使用下面的關鍵字:
- private:只能在宣告的類別或介面中使用
- protected:比private多了可以在子類別或子介面中使用
- internal:只能在同一個模組中使用
- public:沒有使用private、protected或internal的時候,預設為public,可以在應用程式任何地方使用
下面是一個使用存取修飾子的範例程式:
/* net.macdidi5.kotlin.tutorial.ch17.Visibility.kt */
package net.macdidi5.kotlin.tutorial.ch17
// 沒有使用private或internal,預設為public
fun defaultPublicFunction() = "Default public"
public fun explicitPublicFunction() = "Explicit public"
internal fun internalFunction() = "Internal"
private fun privateFunction() = "Private"
// 建構式沒有使用private或internal,預設為public
open class Visibility(_value : Int) {
// 沒有使用private、protected或internal,預設為public
val defaultPublicProperty = _value
public val explicitPublicProperty = _value
internal val internalProperty = _value
protected val protectedProperty = _value
private val privateProperty = _value
fun privateDemo() {
println("${privateFunction()}, $privateProperty")
}
}
依照應用程式的需求使用存取修飾子以後,就必須依照規定使用,否則會產生編譯錯誤:
/* net.macdidi5.kotlin.tutorial.ch17.test.VisibilityDemo01.kt */
package net.macdidi5.kotlin.tutorial.ch17.test
import net.macdidi5.kotlin.tutorial.ch17.Visibility
import net.macdidi5.kotlin.tutorial.ch17.defaultPublicFunction
import net.macdidi5.kotlin.tutorial.ch17.explicitPublicFunction
import net.macdidi5.kotlin.tutorial.ch17.internalFunction
// 編譯錯誤,不可以import private函式
// import net.macdidi5.kotlin.tutorial.ch17.privateFunction
import java.util.Random
// 繼承Visibility的子類別
class SubVisibility : Visibility(1) {
fun demo() {
println("defaultPublicProperty: $defaultPublicProperty")
println("explicitPublicProperty: $explicitPublicProperty")
println("internalProperty: $internalProperty")
// 可以使用Visibility定義為protected的屬性與函式
println("protectedProperty: $protectedProperty")
// 編譯錯誤,不可以使用private屬性
// println("privateProperty: $privateProperty")
}
}
// 把建構式定義為private
class PrivateConstructor private constructor() {
companion object {
fun game() = if (Random().nextInt(100) > 50) "Win" else "Lose"
}
}
fun main(args: Array<String>) {
println(defaultPublicFunction())
println(explicitPublicFunction())
println(internalFunction())
// 編譯錯誤,
// println(privateFunction())
val v = Visibility(3)
println("defaultPublicProperty: ${v.defaultPublicProperty}")
println("explicitPublicProperty: ${v.explicitPublicProperty}")
println("internalProperty: ${v.internalProperty}")
// 編譯錯誤,不可以使用protected屬性
// println("protectedProperty: ${v.protectedProperty}")
// 編譯錯誤,不可以使用private屬性
// println("privateProperty: ${v.privateProperty}")
// 編譯錯誤,不可以呼叫private constructor
// val pc = PrivateConstructor()
println(PrivateConstructor.game())
}
匿名、巢狀與內部與類別
如果一個類別只會在特定的類別中使用,應用程式架構為了執行更好的封裝,可以把它宣告在一個類別裡面。根據類別的用途,可以分為匿名、巢狀與內部類別。如果需要一個繼承類別或實作介面的物件,可以使用匿名類別執行宣告與建立物件,匿名類別可以存取外層類別宣告的屬性與函式:
/* net.macdidi5.kotlin.tutorial.ch17.Outer.kt */
package net.macdidi5.kotlin.tutorial.ch17
class Outer {
private val number = 3
// 宣告匿名類別
val anony = object : Any() {
val number = 5
override fun toString() : String {
// 匿名類別宣告的number
println(number)
// 匿名類別可以使用Outer類別的屬性與函式
// Outer宣告的number
println([email protected])
return "Anonymous.number: $number, Outer.number: ${[email protected]}"
}
}
...
}
fun main(args: Array<String>) {
val outer = Outer()
println(outer.anony)
}
如果需要宣告一個獨立的類別,這個類別執行的工作,不需要存取外層類別的屬性與函式,可以在類別裡面宣告一個巢狀類別:
/* net.macdidi5.kotlin.tutorial.ch17.Outer.kt */
package net.macdidi5.kotlin.tutorial.ch17
class Outer {
private val number = 3
...
// 宣告巢狀類別
class Nested {
private val number = 7
fun showNumber() {
println("Nested.number: $number")
// 不可以使用Outer類別的屬性與函式
println("can't access [email protected]")
}
}
...
}
fun main(args: Array<String>) {
val nested : Outer.Nested = Outer.Nested()
nested.showNumber()
}
如果需要宣告一個獨立的類別,這個類別執行的工作,需要存取外層類別的屬性與函式,可以在類別裡面宣告一個內部類別:
/* net.macdidi5.kotlin.tutorial.ch17.Outer.kt */
package net.macdidi5.kotlin.tutorial.ch17
class Outer {
private val number = 3
...
// 宣告內部類別
// 多了inner關鍵字,就可以使用Outer類別的屬性與函式
inner class Inner {
private val number = 9
fun showNumber() {
// Inner宣告的number
println(number)
// Inner宣告的number
println([email protected])
// Outer宣告的number
println([email protected])
}
}
}
fun main(args: Array<String>) {
val inner : Outer.Inner = Outer().Inner()
inner.showNumber()
}
資料類別(Data Class)
應用程式通常需要一些基本的類別(Domain class),用來封裝應用程式需要的物件,如果基本類別沒有特別的需求,可以使用Kotlin提供的
資料類別宣告這些基本類別。下面是一個資料類別的範例:
/* net.macdidi5.kotlin.tutorial.ch17.Item01.kt */
package net.macdidi5.kotlin.tutorial.ch17
// 宣告資料類別
// 包含編號、標題與內容三個屬性
data class Item01(val id: Long,
var title: String,
var content: String)
Kotlin會自動為資料類別加入下列的函式:
- equals()與hashCode():判斷物件相等與傳回hash code函式,
- toString():把屬性資料轉換為字串。
- componentN():產生與屬性宣告順序對應的讀取函式,可以使用在屬性解構(Destructuring),例如資料類別宣告三個屬性,Kotling會自動產生componnent1()、componnent2()與componnent3(),分別傳回對應的屬性值。
- copy():複製原有物件的屬性值,傳回一個新的物件
下面是一個使用資料類別的範例程式:
/* net.macdidi5.kotlin.tutorial.ch17.DataClassDemo01.kt */
package net.macdidi5.kotlin.tutorial.ch17
fun main(args: Array<String>) {
val item01 = Item01(101, "Hello", "Kotlin")
val item02 = Item01(101, "Hello", "Kotlin")
println(item01)
// 顯示: Item01(id=101, title=Hello, content=Kotlin)
println(item01.toString())
// 顯示: Item01(id=101, title=Hello, content=Kotlin)
println(item01.hashCode())
// 顯示: 116288980
println(item02.hashCode())
// 顯示: 116288980
println(item01.equals(item02))
// 顯示: true
println(item01 == item02)
// 顯示: true
// data class自動加入的componentN函式
// 傳回宣告順序的屬性值
val id01 = item01.component1()
val title01 = item01.component2()
val content01 = item01.component3()
println("$id01, $title01, $content01")
// 顯示: 101, Hello, Kotlin
// 使用解構(Destructuring)讀取屬性值
// 宣告三個變數,依照順序指定componentN函式的回傳值
val (id, title, content) = item01
println("$id, $title, $content")
// 顯示: 101, Hello, Kotlin
// 複製一個新的物件
val item03 = item01.copy()
println(item03)
// 顯示: Item01(id=101, title=Hello, content=Kotlin)
// 指定部份的屬性複製一個新的物件
val item04 = item01.copy(id = 104)
println(item04)
// 顯示: Item01(id=104, title=Hello, content=Kotlin)
}
下面的範例程式可以看到資料類別自動產生的函式內容:
/* net.macdidi5.kotlin.tutorial.ch17.generate.Item01.kt */
package net.macdidi5.kotlin.tutorial.ch17.generate
class Item01(val id: Long,
var title: String,
var content: String) {
// 判斷物件是否一樣
override fun equals(other: Any?): Boolean {
// 如果物件參考值一樣
if (this == other) return true
// 如果參數不是ItemData
if (other !is Item01) return false
// 判斷編號、標題與內容
if (id != other.id) return false
if (title != other.title) return false
if (content != other.content) return false
return true
}
// 傳回物件hash code
override fun hashCode(): Int {
var result = id.hashCode()
result = 31 * result + title.hashCode()
result = 31 * result + content.hashCode()
return result
}
// 轉換為字串
override fun toString(): String {
return "net.macdidi5.kotlin.tutorial.ch17.generate.Item01(id=$id, title='$title', content='$content')"
}
// 依照屬性宣告的順序傳回屬性值
operator fun component1() = id
operator fun component2() = title
operator fun component3() = content
// 複製並傳回一個新的物件
fun copy(id : Long = this.id,
title : String = this.title,
content : String = this.content)
= Item01(id, title, content)
}
fun main(args: Array<String>) {
val item = Item01(101, "Hello", "Kotlin")
// 呼叫toString函式
println(item)
// 顯示: Item01(id=101, title='Hello', content='Kotlin')
// 解構函式
val (id, title, content) = item
println("$id, $title, $content")
// 顯示: 101, Hello, Kotlin
// 複製一個新的物件
val item02 = item.copy()
println(item02)
// 顯示: Item01(id=101, title='Hello', content='Kotlin')
// 指定部份的屬性複製一個新的物件
val item03 = item.copy(content = "Java")
println(item03)
// 顯示: Item01(id=101, title='Hello', content='Java')
}
Kotlin的資料類別可以依照自己的需求,覆寫toString、equals與hashCode函式,componentN與copy函式「不可以」覆寫。下面的範例程式示範在資料類別加入額外的宣告:
/* net.macdidi5.kotlin.tutorial.ch17.Item02.kt */
package net.macdidi5.kotlin.tutorial.ch17
// 宣告資料類別
data class Item02(val id: Long,
var title: String = "",
var content: String = "") {
// 覆寫toString函式
override fun toString(): String =
"$id\t$title\t$content"
// 宣告其它函式
var details : String = toString()
// 禁止使用setter
private set
// 宣告getter
get() {
return "Item02: ${toString()}"
}
}
下面的範例程式使用上面的資料類別執行一些測式:
/* net.macdidi5.kotlin.tutorial.ch17.DataClassDemo02.kt */
package net.macdidi5.kotlin.tutorial.ch17
fun main(args: Array<String>) {
val item01 = Item02(101, "Hello", "Kotlin")
println(item01)
// 顯示: 101 Hello Kotlin
val item02 = Item02(102)
println(item02)
// 顯示: 102
item02.title = "Hi"
item02.content = "Android"
println(item02)
// 顯示: 102 Hi Android
println(item02.details)
}
下一步
不論Java或Kotlin程式語言,泛型(generic)都是一個很重要的特性,接下來準備認識泛型的設計與應用。
相關的檔案都可以在GitHub瀏覽與下載。
http://github.com/macdidi5/Kotlin-Tutorial
後續 >> Kotlin Tutorial(18)泛型的設計與應用
|