類、對象和接口
接口
kotlin接口可以包含抽象方法的定義以及非抽象方法的實現(與 Java 8 中的默認方法類似),但它們不能包含任何狀態,通過interface 關鍵字定義,可以有一個默認實現
interface Clickable {
fun click ()
fun showoff() = println ("clickable!")
}
如果多個接口,每一個都包含了帶默認實現的 showoff 方法,如果要調用,需要顯示實現,
super<Clickable>. showoff ()
可見性修飾符
Java 的類和方法默認是 open 的,而 Kotlin 中默認都是 final 的。
如果想創建一個類的子類,就需要用open修飾符來標示這個類,需要給每一個可以被重寫的屬性或方法添加 open 修飾符。如果你重寫了一個基類或者接口的成員,重寫了的成員同樣默認是open 的 。 如果你想改變這一行爲,阻止你的類的子類重寫你的實現,可以顯式地將重寫的成員標註爲 final 。抽象成員始終是 open 的, 所以不需要顯式地使用 open 修飾符.在接口中 , 不能使用final、 open 或者是 abstract。 接口中的成員始終是 open 的,
不能將其聲明爲 final 的
public :公開,可見性最大,哪裏都可以引用。
private:私有,可見性最小,根據聲明位置不同可分爲類中可見和文件中可見。
protected:保護,相當於 private + 子類可見。
internal:內部,僅對 module 內可見。
比Java少了一個default 「包內可見」,多了一個internal「module 內可見」。比如Android Studio 裏的 module。默認是public。protected 成員只在類和它的子類中可見,類的擴展函數不能訪問它的 private 和 protected 成員。Kotlin 中一個外部類不能看到其內部(或者嵌套)類中的 private 成員
內部類和嵌套類
Kotlin 的嵌套類不能訪問外部類的實例,要把它變成一個內部類來持有一個外部類的引用的話需要使用 inner 修飾符。
需要使用 this@Outer 從Inner 類去訪問 Outer 類
class Outer {
inner class Inner {
fun getOuterReference(): Outer= this@Outer
}
}
但是,嵌套函數中可以訪問在它外部的所有變量或常量,例如類中的屬性、當前函數中的參數與變量等。
fun login(user: String, password: String, illegalStr: String) {
fun validate(value: String, illegalStr: String) {
if (value.isEmpty()) {
throw IllegalArgumentException(illegalStr)
}
}
validate(user, illegalStr)
validate(password, illegalStr)
}
改進:
fun login(user: String, password: String, illegalStr: String) {
fun validate(value: String) {
if (value.isEmpty()) {
throw IllegalArgumentException(illegalStr)
}
}
...
}
validate()可以直接訪問illegalStr
爲父類添加一個 sealed修飾符,對可能創建的子類做出嚴格的限制。所有的直接子類必須嵌套在父類中 。就是說,當你在 when 中使用 sealed 類並且添加 一個新的子類的時候,有返回值的when 表達式會導致編譯失敗,它會告訴你哪裏的代碼必須要修改
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)
}
}
構造函數
class User constructor(_nickname: String) {
val nickname: String
init {
nickname = nickname
}
}
主構造函數:在類名之後加上constructor,用來開始一個主構造方法或從構造方法的聲明。 init 關鍵字用來引入一個初始化語句塊。init 代碼塊是緊跟在主構造器之後執行的,這是因爲主構造器本身沒有代碼體,init 代碼塊就充當了主構造器代碼體的功能。主構造方法有語法限制,不能包含初始化代碼。如果類中有主構造器,那麼其他的次構造器都需要通過 this 關鍵字調用主構造器,可以直接調用或者通過別的次構造器間接調用
class User constructor(var name: String) {
// 直接調用主構造器
constructor(name: String, id: Int) : this(name) {
}
// 通過上一個次構造器,間接調用主構造器
constructor(name: String, id: Int, age: Int) : this(name, id) {
}
}
init 代碼塊是先於次構造器執行的。如果主構造方法沒有註解或可見性修飾符,同樣可以去掉 constructor 關鍵字
class User(_nickname: String) {
val nickname = nickname
}
如果有註解或可見性修飾符,就不可省略
class User private constructor(name: String) {
//主構造器被修飾爲私有的,外部就無法調用該構造器
}
如果屬性用相應的構造方法參數來初始化,代碼可以通過把 val或var 關鍵字加在參數前的方式來進行簡化
class User(val nickname: String)
//val 意味着相應的屬性會用構造方法的參數來初始化.就等價於在類中創建了該名稱的屬性(property),並且初始值就是主構造器中該參數的值
可以給構造函數裏的參數加上默認值。
class User (val nickname: String,val isSubscribed : Boolean = true)
//爲 isSubscribed 參數使用默認值“true”
val alice = User ("Alice")
//可以按照聲明順序寫明所有的參數
val bob = User ("Bob", false)
//可以顯式地爲某些構造方法參數標明名稱
val carol =User ("Carol", isSubscribed = false)
屬性
接口可以包含抽象屬性聲明,但是接口本身並不包含任何狀態,因此只有實現這個接口的類在需要的情況下會存儲這個值。
interface User {
val nickname: String
}
可以在實現類中賦值
override val nickname: String
get() =.....
接口還可以包含具有 getter 和 setter 的屬性,只要它們沒有引用一個支持字段(支持宇段需要在接口中存儲狀態,而這是不允許的)
interface User {
val email: String
val nickname: String
get () = ....
}
屬性沒有支持字段:結果值在每次訪問時通過計算或其他方式獲得。
val 和 Java 中的 final 類似,表示只讀變量,不能修改,但是可以通過getter()來修改返回值
val size: Int
get() { // 每次獲取 size 值時都會執行 items.size
return items.size
}
在setter 的函數體中,使用了特殊的標識符 field 來訪問支持字段的值。在
getter 中,只能讀取值;而在setter 中 , 既能讀取它 也 能修改它。
object
在java中,常量可以通過static final來定義,Kotlin中,靜態變量和靜態方法這兩個概念被去除了。可以通過companion object來達到相同的效果
class Sample {
...
companion object {
val anotherString = "Another String"
}
}
“object ”關鍵字:將聲明一個類與創建一個實例結合起來,也就是說,創建一個類,並且創建一個這個類的對象。如果要使用它,直接通過類名來訪問:Sample.name。
單例
在java中單例這樣寫:
public class A {
private static A sInstance;
public static A getInstance() {
if (sInstance == null) {
sInstance = new A();
}
return sInstance;
}
...
}
Kotlin中可以這樣寫
// class 替換成了 object
object A {
val number: Int = 1
fun method() {
println("A.method()")
}
}
這種通過 object 實現的單例是一個餓漢式的單例,並且實現了線程安全
object 聲明的類,不允許使用構造方法(包括主構造方法和從構造方法)。因爲在定義的時候就立即創建了,不需要在代碼的其他地方調用構造方法 。 因此,定義一個構造方法是沒有意義的。
匿名類
在Java中,匿名類是這樣寫的,
ViewPager.SimpleOnPageChangeListener listener = new ViewPager.SimpleOnPageChangeListener() {
@Override //
public void onPageSelected(int position) {
// override
}
};
Kotlin中,是這樣寫的
val listener = object: ViewPager.SimpleOnPageChangeListener() {
override fun onPageSelected(position: Int) {
// override
}
}
伴生對象
Kotlin 中的類不能擁有靜態成員,作爲替代, Kotlin 依賴包級別函數(在大多數情形下能夠替代 Java 的靜態方法)和對象聲明(在其他情況下替代 Java 的靜態方法,同時還包括靜態字段) 。但是頂層函數不能訪問類的 private成員。通過關鍵字companion,獲得了直接通過容器類名稱來訪問這個對象的方法和屬性的能力
class A {
companion object {
fun bar() {
println ("Companion object called")
}
}
}
A.bar()
一個類中最多隻可以有一個伴生對象。
java中的靜態初始化,在Kotlin中也可以實現,
class Sample {
companion object {
init {
...
}
}
}
工廠模式:
//伴生對象,工廠模式
class UserCom private constructor(val nickName: String) {
companion object {
fun newSubscribingUser(email: String) =
UserCom(email)
fun newSubscribingUser2(email2: String) =
UserCom(email2)
}
}
數據類
在開發中,經常獲取網路數據,需要定義數據類來解析(可以用JsonToKotlin,方便轉換),在java中,會有很多get/set方法,在kotlin中,通過data標識符,可以實現相同的效果:
data class User(val name: String, val age: Int)
重寫了:
• equals 用來比較實例
• hashCode 用來作爲例如 HashMap 這種基於哈希容器的鍵
• toString 用來爲類生成按聲明順序排列的所有字段的字符串表達形式
數據類有一些限制:
• 主構造函數需要至少有一個參數;
• 主構造函數的所有參數需要標記爲 val 或 var;
• 數據類不能是抽象、開放、密封或者內部的;
數據類的屬性,強烈建議使用val來定義。數據類可以通過copy()來複制副本,副本有着單獨的生命週期而且不會影響代碼中引用原始實例的位置。
fun copy(name: String = this.name, age: Int = this.age) = User(name, age)
或者
val jack = User(name = "Jack", age = 1)
val olderJack = jack.copy(age = 2)
類委託
如果要實現一個接口,可以使用 by 關鍵字將接口的實現委託到另一個對象 。官網上給的例子
interface Base {
fun print()
}
class BaseImpl(val x: Int) : Base {
override fun print() { print(x) }
}
class Derived(b: Base) : Base by b
fun main() {
val b = BaseImpl(10)
Derived(b).print()
}
看一個覆蓋的例子
interface Base {
fun printMessage()
fun printMessageLine()
}
class BaseImpl(val x: Int) : Base {
override fun printMessage() { println(x) }
override fun printMessageLine() { println(x) }
}
class Derived(b: Base) : Base by b {
override fun printMessage() { println("abc") }
}
fun main(){
val b = BaseImpl(10)
Derived(b).printMessage()
Derived(b).printMessageLine()
b.printMessage()
b.printMessageLine()
}
輸出:
abc
10
10
10
可以看出,這種方式重寫的成員不會在委託對象的成員中調用 , 委託對象的成員只能訪問其自身對接
口成員實現
委託屬性:val/var <屬性名>: <類型> by <表達式> 。 在 by 後邊的表達式是該 委託,因爲屬性對應
的 get() (與 set() ) 會被委託給它的 getValue() 與 setValue() 方法。 屬性的委託不必實現
任何的接口, 但是需要提供一個 getValue() 函數 (與 setValue() -對於 var 屬性)
class Example {
var p: String by Delegate()
}
class Delegate {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "$thisRef, thank you for delegating '${property.name}' to me!"
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
println("$value has been assigned to '${property.name}' in $thisRef.")
}
}
委託屬性,就是可以把一些常見的屬性類型,比如:
延遲屬性 (lazy properties) : 其值只在首次訪問時計算;
可觀察屬性 (observable properties) : 監聽器會收到有關此屬性變更的通知;
把多個屬性儲存在一個映射 (map) 中, 而不是每個存在單獨的字段中。
把get和set方法的一些處理,封裝成一個庫或者公共類,以後的處理可以通過by 來委託給這個庫或公共類