kotlin(3):類和物件以及其他的概念屬性
類和物件
類宣告由類名,類頭(指定其型別引數,主建構函式等)和由大括號包圍的類體構成,類頭和類體都是可選的,如果一個類沒有類體,可以省略花括號.
1.主建構函式
在kotlin中的一個類可以有一個主建構函式和多個次建構函式,主建構函式是類頭的一部分,跟在類名後.
class Demo constructor(data: String){}
如果主建構函式沒有任何註解或者可見性修飾符,可以省略constructor關鍵字
class Demo(data: String){}
主建構函式不能包含任何的程式碼,初始化的程式碼可以放到以init關鍵字作為字首的初始化塊中
class Demo(data: String){
init{
print(“init”)
}
}
主建構函式的引數可以在初始化塊中使用,它們也可以在類體內宣告的屬性初始化器中使用
class Demo(var data: String){}
與普通屬性一樣,主建構函式中宣告的屬性可以是可變的var或者是隻讀的val
如果建構函式有註解或者可見性修飾符,這個constructor關鍵字是必需的,並且這些修飾符必須在它的前面
2.次建構函式
類也可以宣告字首有constructor的次建構函式
class Demo { var data:Int = 0//若是以這樣的形式宣告成員變數必須賦予初值 constructor(data:Int) {//可以有多個不同引數的次建構函式 } }
如果一個非抽象類沒有宣告任何(主或次)建構函式,它會有一個生成的不帶任何引數的主建構函式,建構函式的可見性是public,可以宣告一個帶有非預設可見性的空的主建構函式
class Demo private constructor(){}
注:在jvm上,如果主建構函式的所有的引數都有預設值,編譯器會生辰給一個額外的無參建構函式,它將使用預設值
class Demo(var data:Int = 1) {}
var demo = Demo()//demo的成員變數的data的值為1
3.建立類的例項
Kotlin沒有new,這點是與java最不同的地方
var demo = Demo()//可變的物件 var val demo = Demo(1);//不可變的物件 val
4.類成員
一個類可以包含:建構函式,初始化塊,函式,屬性,巢狀類和內部類,物件宣告
5.繼承
在kotlin中所有的類都有一個共同的超類Any(類似於java中的Object類),這對於沒有超型別宣告的類是預設超類
不過Any中的成員很少,只有equals() hashCode() toString()
要宣告一個顯式的超型別,把型別放到類頭的冒號之後:
open class Base()//可以定義為open或者abstract中的一個
class Demo():Base(){}
如果該類有一個主建構函式,其基類必須用基類的主建構函式引數就地初始化
如果類沒有主建構函式,那麼每個次建構函式必須用super關鍵字初始化其基類,或委託給另一個建構函式做到這一點,注意:在這種情況下,不同的次建構函式可以呼叫基型別的不同的建構函式
class MyView : View {
constructor(ctx: Context) : super(ctx)
constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)
}
類上的open標註與java的final相反,它允許其他類從這個類繼承,預設情況下,在kotlin中所有的類都是final
6.覆蓋方法
Kotlin需要顯式標註可覆蓋的成員和覆蓋後的成員
open class Base{
open fun v(){}
}
class Demo():Base(){
Override fun v(){}
}
在Demo類中的v()函式上必須加上override標註,如果沒寫,編譯器會報錯.如果函式沒有標註open則子類中不允許定義相同簽名的函式,不論加不加override.在一個final類中,開放成員是禁止的,相反在標記為override的成員本事是開放的,也就是說,它可以在子類中覆蓋,如果想禁止再次覆蓋,可以使用final關鍵字修飾,這樣就禁止被再次覆蓋了
open class Demo():Base(){
Final override fun v(){}
}
7.覆蓋屬性
屬性覆蓋與方法覆蓋類似,在超類中宣告然後在派生類中重新宣告的屬性必須以override開頭,並且它們必須具有相容的型別,每個宣告的屬性可以由具有初始化器的屬性或者具有getter()方法的屬性覆蓋
open class Base {
open var data:Int = 1
get() {return 2}
}
class Demo : Base(){
override var data:Int = 3
get() = 4
}
最後的Demo的data的值為4
可以用一個var覆蓋基類的val,但反之不行.
8.覆蓋規則
在kotlin中,實現繼承由下述規則規定:如果一個類從它的直接超類繼承相同成員的多個實現,它必須覆蓋這個成員並提供其自己的實現(也許用繼承來的其中之一).為了表示採用從哪個超型別繼承的實現,我們使用由尖括號中超型別名限定的super,如super<Base>
open class Base {
open fun f(){}
}
interface Base2{
fun f(){}
}
class Demo : Base(),Base2{
override fun f(){
super<Base2>.f()
super<Base>.f()
}
}
9.抽象類
類和其中的某些成員可以宣告為abstract,抽象成員在本類中可以不用實現,需要注意的是,我們並不需要用open標註一個抽象類或者函式,,因為是廢話..
可以用一個抽象成員覆蓋基類的費抽象的開放成員
10.伴生物件
與java不同,在kotlin中類沒有靜態方法,在多數情況下,它建議簡單地使用包級函式
如果需要寫一個可以無需用一個類的例項來呼叫,但需要訪問類內部的函式,也就是對java中的static的需要.在kotlin中可以寫成該類內物件宣告中的一員,中文意思就是:如果在類內宣告一個伴生物件,就可以像在java中呼叫static相同的語法來呼叫其成員,只使用類名作為限定符.
如下:
open class Demo {
companion object {
fun f(){
}
}
}
注意:每一個類中只能有一個companion object 此時的物件名稱是Companion
也可以自定義名字:
open class Demo {
companion object MyCompanion{
fun f(){
}
}
}
呼叫的時候:
Demo.f()
Demo.MyCompanion.f()
11.單例模式(物件宣告)
在kotlin中單例實現很簡單,直接貼程式碼:
object Single{
fun f(){
println("single")
}
}
注意:物件宣告不能在區域性作用域中,但是它們可以巢狀到其他物件宣告或非內部類中
物件表示式和物件宣告的區別:
A.物件表示式是在使用它們的地方立即執行,建立物件
B.物件宣告是在第一次被訪問到時延遲初始化的,懶漢式的單例模式
C.伴生物件的初始化是在相應的類被載入時,與java靜態初始化的寓意
12.屬性和欄位
宣告屬性:
Kotlin的類可以有屬性,屬性可以用var宣告為可變的,否則使用只讀關鍵字val
class Demo{
var data1:String = “”
var data2:Int = 0
val data3:Int = 1
}
要使用一個屬性,名稱引用即可
Getters和Setters
宣告一個屬性的完整語法是
var <propertyName> [:<propertyType>] [=<property_initializer>]
[<getter>]
[<setter>]
初始器getter和setter是可選的,屬性型別如果可以從初始器(或者從getter返回值)中推斷出來,也可以省略
EG:
var demo1:Int? //錯誤:需要顯示地初始化,隱含預設getter和setter
var demo2 = 1 //型別Int,預設getter和setter
一個只讀屬性的語法和一個可變的屬性的語法有兩方面的不同:1.只讀屬性的用val開始代替var, 2.只讀屬性不允許setter
val demo:Int?//型別 Int, 預設getter,必須在建構函式中初始化
val demo2 = 1//型別Int 預設getter
自定義getter和setter的例子
var demo:String
get() = this.toString()
set(value) {
This.demo = value
}
改變一個訪問器的可見性或者對其註解,但是不需要改變預設的實現,可以定義訪問器而不定義其實現
var demo:String=”hello”
private set//此setter是私有的並且有預設的實現
Var demo2:Any? = null
@Inject set //用Inject註解此setter
幕後欄位:
Kotlin中類不能有欄位,當使用自定義訪問器的時候,有時有一個幕後欄位(backing field)有時是必需的.為此kotlin提供一個自動幕後欄位,可通過使用field識別符號訪問
var counter = 0 //此初始化值直接寫到幕後欄位裡面
set(value) {
If(value > 0) filed = value
}
Field識別符號只能用在屬性的訪問器內
如果屬性至少一個訪問器使用預設實現,或者自定義訪問器通過field引用幕後欄位,將會為該屬性生成一個幕後欄位.
例如下面的情況,就沒有幕後欄位:
val isEmpty : Boolean
get() = this.size == 0
編譯期常量
已知值的屬性可以使用const修飾符標記為編譯期常量,這些屬性需要滿足以下的要求:
A.位於頂層或者是object的一個成員
B.用String或原生型別值初始化
C.沒有自定義getter
這些屬性可以用在註解中:
const val demo:String=”hello world”
惰性初始化屬性
一般的,屬性宣告為非空型別必須在建構函式中初始化,然而這經常不方便,例如:屬性可以通過依賴注入來初始化,或者在單元測試的setup方法中初始化,這種情況下,不可以在建構函式中提供一個非空初始器,這時候可以用lateinit修飾符標記該屬性
lateinit var demo:String
該修飾符只能用於在類體中(不是在主建構函式中)宣告的var屬性,並且僅當該屬性沒有自定義getter或者setter時,該屬性必須是非空型別,並且不能是原生型別.
在初始化前訪問一個lateinit屬性會丟擲一個特定異常,該異常明確標識該屬性被訪問及它沒有初始化的事實
委託屬性
最常見的一類屬性就是簡單地從幕後欄位中讀取,另一方面,使用自定義getter和setter可以實現屬性的任何行為,介於兩者之間,屬性如何工作有一些常見的模式