以下說明這些功能:類別、介面,關鍵字 object,data classes 及 class delegation。
不同於 java,kotlin 宣告預設為 public final,nested class 不是 inner class,沒有外層 class 的 implicit reference。將 class 宣告為 data class時,compiler 會自動產生幾個 methods。
定義 class 時使用關鍵字 keyword,同時會產生一個 instance,這可用在 singleton object, companion objects 以及 oject expressions (類似 Java anonymous classes)
class hierarchy
interface
用 interface 定義介面,用 :
表示實作 interface
override
類似 java 的 @Override
annotation,Button 裡面的 override
是一定要寫的,否則程式將無法編譯
interface Clickable {
// 只定義了 function,沒有實作
fun click()
}
class Button : Clickable {
override fun click() = println("I was clicked")
}
fun main(args: Array<String>) {
Button().click()
}
有兩個以上的 interface 的範例
class Button : Clickable, Focusable {
override fun click() = println("I was clicked")
// 因 Clickable, Focusable 都有實作 showOff,必須要用 super<Clickable> 指定是用哪一個上層實作
// 如果只要使用一種上層實作,可寫成這樣
// override fun showOff() = super<Clickable>.showOff()
override fun showOff() {
super<Clickable>.showOff()
super<Focusable>.showOff()
}
}
interface Clickable {
fun click()
// 定義 function,並提供預設的實作
fun showOff() = println("I'm clickable!")
}
interface Focusable {
fun setFocus(b: Boolean) =
println("I ${if (b) "got" else "lost"} focus.")
fun showOff() = println("I'm focusable!")
}
fun main(args: Array<String>) {
val button = Button()
button.showOff()
// Focusable 有提供 setFocus,故 Button 可使用這個 function
button.setFocus(true)
button.click()
}
執行結果
I'm clickable!
I'm focusable!
I got focus.
I was clicked
final by default
為了避免發生 fragile base class 問題,kotlin 預設將 class, method 都加上 final 的特性,如果要讓其他類別繼承的
class 或覆寫的 method,都必須要加上 open
這個 modifier。
interface Clickable {
fun click()
fun showOff() = println("I'm clickable!")
}
// 可被繼承
open class RichButton : Clickable {
// 不可被覆寫
fun disable() {}
// 可被覆寫
open fun animate() {}
// 覆寫 click,且可被覆寫
override fun click() {}
// 如果要限制 click 不能被覆寫,要加上 final
// final override fun click() {}
}
class modifier | corresponding member | comments |
---|---|---|
final | can't be overridden | default for class members |
open | can be overridden | 要特別指定 |
abstract | must be overridden | 只能用在 abstract classes, abstract members 不能有實作內容 |
override | overrides a member in a superclass | 預設為 open,如要限制不能被覆寫,要加上 final |
visibility modifiers 有 public, protected, private, internal 四種,預設為 public,其中 internal 表示為 "visible inside a module"
visibility modifiers | class member | top-level declaration |
---|---|---|
public(default) | visible everywhere | visible everywhere |
internal | visible in a module | visible in a module |
protected | visible in subclasses | -- |
private | visible in a clsss | visible in a file |
protected member 只能被 class 及 subclass 使用,跟 java 的 protected,可在同一個 package 使用的定義不同
class 的 extension function 無法使用 private or protected members
interface Focusable {
fun setFocus(b: Boolean) =
println("I ${if (b) "got" else "lost"} focus.")
fun showOff() = println("I'm focusable!")
}
internal open class TalkativeButton : Focusable {
private fun yell() = println("Hey!")
protected fun whisper() = println("Let's talk!")
}
// error: 'public' member exposes its 'internal' receiver type TalkativeButton
fun TalkativeButton.giveSpeech() {
// error: cannot access 'yell': it is private in 'TalkativeButton'
yell()
// error: cannot access 'whisper': it is protected in 'TalkativeButton'
whisper()
}
nested class, inner class
kotlin 也可在 class 裡面定義另一個 class,跟 java 的差異是 kotlin nested class 不能使用 outer class instance
首先製作兩個 interface,注意 State 為 Serializable
interface State: Serializable
interface View {
fun getCurrentState(): State
fun restoreState(state: State) {}
}
如果以 java 實作 ButtonState 這個 inner class,會遇到 java.io.NotSerializableException: Button
,這是因為 ButtonState 裡面儲存了 outer class: Button 的 implicit reference,無法被序列化,如要解決,要將 ButtonState 宣告為 static,去掉 implicit reference to Button。
/* Java */
public class Button implements View {
@Override
public State getCurrentState() {
return new ButtonState();
}
@Override
public void restoreState(State state) { /*...*/ }
public class ButtonState implements State { /*...*/ }
}
以 kotlin 實作 ButtonState,就像是 java 的 static nested class 一樣。
class Button : View {
override fun getCurrentState(): State = ButtonState()
override fun restoreState(state: State) { /*...*/ }
class ButtonState : State { /*...*/ }
}
kotlin 的 nested class 不會參考到 outer class 的 reference,但 inner class 可以
Class A declared within another Class B | in Java | in Kotlin |
---|---|---|
nested class (不會儲存 outer class 的 reference) | static class A | class A |
inner class (會儲存 outer class 的 reference) | class A | inner class A |
可用 this@Outer
參考到 Outer class 的 reference
class Outer {
inner class Inner {
fun getOuterReference(): Outer = this@Outer
}
}
sealed class
Expr 介面有兩個實作的 class: Num, Sum,在 eval 中,要注意 else 的部分,compiler 會檢查有沒有預設的條件,在這邊預設不能回傳任何數值,故直接 throw exception
interface Expr
class Num(val value: Int) : Expr
class Sum(val left: Expr, val right: Expr) : Expr
fun eval(e: Expr): Int =
when (e) {
is Num -> e.value
is Sum -> eval(e.right) + eval(e.left)
else ->
throw IllegalArgumentException("Unknown expression")
}
fun main(args: Array<String>) {
println(eval(Sum(Sum(Num(1), Num(2)), Num(4))))
}
kotlin 提供 sealed class 處理這種問題
因 Expr 限制為 sealed class,故無法被任意產生 subclass,所有 subclass 只能寫成 nested class,而在 eval 裡面,就不需要寫 else 的條件,因為系統內不可能會有其他 Expr 的 subclasses (compiler 的檢查及限制)
sealed class Expr {
class Num(val value: Int) : Expr()
class Sum(val left: Expr, val right: Expr) : Expr()
}
fun eval(e: Expr): Int =
when (e) {
is Expr.Num -> e.value
is Expr.Sum -> eval(e.right) + eval(e.left)
}
fun main(args: Array<String>) {
println(eval(Expr.Sum(Expr.Sum(Expr.Num(1), Expr.Num(2)), Expr.Num(4))))
}
使用 nontrivial constructor 及 properties
kotlin 也可以實作多個 constructor,可在 initializer blocks 增加 initilization logic
primary constructor 及 initializer blocks
constructor, init 這兩個關鍵字,用 _nickname 取代 java 習慣的 this.nickname = nickname
這樣的寫法
// 單一參數的 primary constructor
class User constructor(_nickname: String) {
val nickname: String
// initializer block
init {
nickname = _nickname
}
}
可省略 contructor, init 的語法
class User(_nickname: String) {
val nickname = _nickname
}
再進一步省略 {}
的部分
class User(val nickname: String)
在 contructor 的參數,可給予預設值,使用時,就可彈性使用一或兩個參數的 constructor
class User(val nickname: String,
val isSubscribed: Boolean = true)
fun main(args: Array<String>) {
val alice = User("Alice")
println(alice.isSubscribed)
val bob = User("Bob", false)
println(bob.isSubscribed)
val carol = User("Carol", isSubscribed = false)
println(carol.isSubscribed)
}
如果 super class 的 constructor 需要參數,要在 subclass 定義裡面提供該參數初始化 super class
open class User(val nickname: String) { ... }
class TwitterUser(nickname: String) : User(nickname) { ... }
預設會產生一個沒有參數的 contructor
open class Button
class RadioButton: Button()
可讓 contructor 變成 private method
class Secretive private constructor() {}
class Secretive {
private constructor()
}
secondary constructor
open class View {
constructor(ctx: Context) {
// some code
}
// secondary constructor
constructor(ctx: Context, attr: AttributeSet) {
// some code
}
}
// subclass 必須同時提供兩個 constructor
class MyButton : View {
constructor(ctx:Context) : super(ctx) {
// ...
}
constructor(ctx: Context, attr: AttributeSet) : super(ctx, attr) {
// ...
}
}
可以用 this() 呼叫其他 constructor
class MyButton : View {
constructor(ctx: Context): this(ctx, MY_STYLE) {
// ...
}
constructor(ctx: Context, attr: AttributeSet):super(ctx, attr) {
// ...
}
}
實作 interfaces 中宣告的 properties
interface User 中定義需要有 nickname 這個 property,PrivateUser、SubscribingUser、FacebookUser 分別用三種方式,提供 nickname
interface User {
val nickname: String
}
// primary constructor property
class PrivateUser(override val nickname: String) : User
// custom getter
class SubscribingUser(val email: String) : User {
override val nickname: String
get() = email.substringBefore('@')
}
// property initializer
fun getFacebookName(accountId: Int) = "fb:$accountId"
class FacebookUser(val accountId: Int) : User {
override val nickname = getFacebookName(accountId)
}
fun main(args: Array<String>) {
println(PrivateUser("test@kotlinlang.org").nickname)
println(SubscribingUser("test@kotlinlang.org").nickname)
}
interface 可讓 property 有 getter setter 實作
interface User {
val email: String
val nickname: String
get() = email.substringBefore('@')
}
由 getter/setter 使用 backing field
field
可在 getter/setter 中取得該 backing field property 的 value,在 setter 裡面,可修改 field 的 value
class User(val name: String) {
var address: String = "unspecified"
set(value: String) {
println("""
Address was changed for $name:
"$field" -> "$value".""".trimIndent())
field = value
}
}
fun main(args: Array<String>) {
val user = User("Alice")
user.address = "Elsenheimerstrasse 47, 80687 Muenchen"
}
修改 accessor visibility
counter 的 set 改成 private method,因此,不能在 class 以外的地方,修改 counter 的數值
class LengthCounter {
var counter: Int = 0
private set
fun addWord(word: String) {
counter += word.length
}
}
fun main(args: Array<String>) {
val lengthCounter = LengthCounter()
lengthCounter.addWord("Hi!")
println(lengthCounter.counter)
}
compiler 產生的 methods: data classes 及 class delegation
java 在每一個 class 都有 equals(), hashCode(), toString() 這些 method,kotlin 在 compiler 中自己產生這些 method
universal object methods
- toString()
override toString() method
class Client(val name: String, val postalCode: Int) {
override fun toString() = "Client(name=$name, postalCode=$postalCode)"
}
fun main(args: Array<String>) {
val client1 = Client("Alice", 342562)
println(client1)
}
- equals()
沒有調整 equals() 時,兩個物件的比較結果是相異的
class Client(val name: String, val postalCode: Int)
fun main(args: Array<String>) {
val client1 = Client("Alice", 342562)
val client2 = Client("Alice", 342562)
println(client1 == client2)
}
覆寫 equals(),Any?
其中 Any
代表 java.lang.Object
,這也是 Kotlin 所有 classes 的 super class,?
代表 other
可以是 null
hashCode 必須要跟著 equals 一同被覆寫,因為當兩個 object 相等時,hashCode 必須要一樣
class Client(val name: String, val postalCode: Int) {
override fun equals(other: Any?): Boolean {
// 先檢查 other 是不是 null, 是不是 Client
if (other == null || other !is Client)
return false
// 檢查兩個欄位的數值是否相等
return name == other.name &&
postalCode == other.postalCode
}
override fun toString() = "Client(name=$name, postalCode=$postalCode)"
override fun hashCode(): Int = name.hashCode() * 31 + postalCode
}
fun main(args: Array<String>) {
val processed = hashSetOf(Client("Alice", 342562))
println(processed.contains(Client("Alice", 342562)))
}
data classes: 自動產生 universal methods 的實作
在 class 前面加上 data
關鍵字,compiler 將會自動產生 universal methods: equals(), hashCode(), toString()
data class 並沒有限制只能使用 val property,也可以用 var,但建議還是盡量使用 val,讓 data class 成為 immutable object,所以 kotlin 在提供了一個 copy() 這個 universal method
如果自己實作 copy,會類似以下這樣的 code
class Client(val name: String, val postalCode: Int) {
...
fun copy(name: String = this.name, postalCode: Int = this.postalCode) = Client(name, postalCode)
}
object instance 使用 copy,並修改其中一個欄位的值
>>> val bob = Client("Bob", 973293)
>>> println(bob.copy(postalCode = 382555))
Client(name=Bob, postalCode=382555)
class delegation: 使用 "by"
在物件導向程式中,會因為繼承 class,覆寫 method,讓某些程式跟父類別產生連動關係,當 base class 有異動時,就造成整個系統不穩定的狀況。
kotlin 預設讓 class 都是 final,無法被繼承的,這可確保經過設計,可以被繼承的 class 才有被繼承的功能,
相較於繼承,更常見的方式,是利用 Decorator Pattern,建立一個新的 class,實作跟舊的 class 一樣的所有 methods,再根據自己的需求,修改對應的 methods,就像是以下的範例。
class DelegatingCollection<T> : Collection<T> {
private val innerList = arrayListOf<T>()
override val size: Int get() = innerList.size
override fun isEmpty(): Boolean = innerList.isEmpty()
override fun contains(element: T): Boolean = innerList.contains(element)
override fun iterator(): Iterator<T> = innerList.iterator()
override fun containsAll(elements: Collection<T>): Boolean = innerList.containsAll(elements)
}
kotlin 提供另一種更有效的方式: class delegation 使用 "by",以下這樣的 code,compiler 會自動產生所有 delegation codes,只需要寫有需要修改覆寫的 method。
class DelegatingCollection<T>(
innerList: Collection<T> = ArrayList<T>()
) : Collection<T> by innerList {
}
import java.util.HashSet
class CountingSet<T>(
val innerSet: MutableCollection<T> = HashSet<T>()
) : MutableCollection<T> by innerSet {
var objectsAdded = 0
// 不使用預設的 delegation method,改寫 add
override fun add(element: T): Boolean {
objectsAdded++
return innerSet.add(element)
}
override fun addAll(c: Collection<T>): Boolean {
objectsAdded += c.size
return innerSet.addAll(c)
}
}
fun main(args: Array<String>) {
val cset = CountingSet<Int>()
cset.addAll(listOf(1, 1, 2))
println("${cset.objectsAdded} objects were added, ${cset.size} remain")
}
關鍵字 object 的用途
- Object declaration: 定義 singleton
- Companion objects: 包含 factory method 以及所有不需要 instance 的 methods,透過 class name 使用這些 methods
- Object expression: 取代 Java 的 anonymous inner class
singleton
object 可讓該 class 只會產生一個 instance,使用時必須要用 class name
object Payroll {
val allEmployees = arrayListOf<Person>()
fun calculateSalary() {
for (person in allEmployees) {
...
}
}
}
Payroll.allEmployees.add(Person(...))
Payroll.calculateSalary()
以下是 CaseInsensitiveFileComparator 範例
import java.util.Comparator
import java.io.File
object CaseInsensitiveFileComparator : Comparator<File> {
override fun compare(file1: File, file2: File): Int {
return file1.path.compareTo(file2.path,
ignoreCase = true)
}
}
fun main(args: Array<String>) {
println(CaseInsensitiveFileComparator.compare(
File("/User"), File("/user")))
val files = listOf(File("/Z"), File("/a"))
println(files.sortedWith(CaseInsensitiveFileComparator))
}
也可以將 object 放在 class 裡面,以 Person.NameComparator
的方式使用
import java.util.Comparator
data class Person(val name: String) {
object NameComparator : Comparator<Person> {
override fun compare(p1: Person, p2: Person): Int =
p1.name.compareTo(p2.name)
}
}
fun main(args: Array<String>) {
val persons = listOf(Person("Bob"), Person("Alice"))
println(persons.sortedWith(Person.NameComparator))
}
如果要從 Java 使用 kotlin object,要透過 INSTANCE
這個 singleton object reference
/* Java */
CaseInsensitiveFileComparator.INSTANCE.compare(file1, file2);
companion objects: factory method and static members
java 的 keyword static
,在 kotlin 沒有。
kotlin 提供 companion object
,使用起來就像是java 的 static method 一樣
class A {
companion object {
fun bar() {
println("Companion object called")
}
}
}
fun main(args: Array<String>) {
A.bar()
}
通常會針對 FacebookUser, SubscribingUser 兩種 User,實作不同的 constructor
class User {
val nickname: String
constructor(email: String) {
nickname = email.substringBefore('@')
}
constructor(facebookAccountId: Int) {
nickname = getFacebookName(facebookAccountId)
}
}
如果用 companion object 的做法,可在裡面實作 factory method,FacebookUser, SubscribingUser 兩種不同的 User 就用不同的 factory method 產生物件
fun getFacebookName(accountId: Int) = "fb:$accountId"
class User private constructor(val nickname: String) {
companion object {
fun newSubscribingUser(email: String) =
User(email.substringBefore('@'))
fun newFacebookUser(accountId: Int) =
User(getFacebookName(accountId))
}
}
fun main(args: Array<String>) {
val subscribingUser = User.newSubscribingUser("bob@gmail.com")
val facebookUser = User.newFacebookUser(4)
println(subscribingUser.nickname)
}
把 companion object 當作 regular objects
companion object 就像是在 class 中宣告的一般類別,可命名,可實作 interface,可以有 extension function 或 properties
- json serialization
class Person(val name: String) {
companion object Loader {
fun fromJSON(jsonText: String): Person = ...
}
}
fun main(args: Array<String>) {
val person = Person.Loader.fromJSON("{name: 'Dmitry'}")
println(person.name)
val person2 = Person.fromJSON("{name: 'Brent'}")
println(person2.name)
}
- implement interface
interface JSONFactory<T> {
fun fromJSON(jsonText: String): T
}
class Person(val name: String) {
companion object : JSONFactory<Person> {
override fun fromJSON(jsonText: String): Person = ...
}
}
在 java 如果要使用 companion object
/* Java */
Person.Companion.fromJSON("...");
- companion object extensions
// business logic module
class Person(val firstName: String, val lastName: String) {
// 產生空白的 companion object
companion object {
}
}
// client/server communication module
// 產生 extension function
fun Person.Companion.fromJSON(json: String): Person {
...
}
val p = Person.fromJSON(json)
- anonymous inner class
在 java 使用 event listener,會用到 anonymous inner class
window.addMouseListener(
object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {
// ...
}
override fun mouseEntered(e: MouseEvent) {
// ...
}
}
)
在 java 的 anonymous class 中,只能使用外部的 final variables。
在 kotlin 可以建立 local 變數,並在 object 裡面使用該變數
fun countClicks(window: Window) {
// 宣告 local 變數
var clickCount = 0
window.addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {
// 更新變數的數值
clickCount++
}
})
// ...
}
沒有留言:
張貼留言