天天看點

瘋狂Kotlin講義學習筆記07章:面向對象(上)對象,中綴,解構,幕後字段、屬性,延遲初始化,通路控制符,構造器,繼承,重寫,super限定,重寫,多态,is類型檢查,as強制類型轉換

1、定義類的标準格式

修飾符 class 類名 [ constructor 主構造器]{
	零到多個次構造器定義
	零到多個屬性....
	零到多個方法....
}
           

修飾符open是final的反義詞,用于修飾一個類,方法或屬性,表明類可派生子類、方法或屬性可被重寫

定義主構造器

  • 如果主構造器沒有任何注解及修飾符,則可以省略constructor關鍵字如:
  • 可以定義0-1個主構造器,0-n個次構造器
  • 如果沒有定義主構造器,系統會預設定義一個以public修飾的構造器(同java)

定義屬性

标準格式為:

【修飾符】var|val 屬性名:類型【=預設值】
【getter】
【setter】
           

修飾符:public、 protected、internal、private、final、open、abstruct

val是隻讀

定義方法

同函數的定義

定義構造器的方法

【修飾符】constructor(形參清單){
構造器執行體
}
           

示例

class Demo002 {
    var name:String=""
    var age:Int=0
    
    fun say(content:String){
        println(content)
    }
}
           

2、對象的産生和使用

無需使用new關鍵字建立對象

格式:

var person:Person=Person()
           

通過點句法通路屬性和方法

fun main() {
    var person:Person=Person()
    person.name="zhangsan"
    person.age=18
    println(person.name)
    println(person.age)
    person.say("say hello to all")
}
           

列印

zhangsan

18

say hello to all

3、對象的this應用(和java基本一緻)

this總是指向調用該方法的對象。根據this出現位置的不同,this作為對象的預設應用有兩種情形

this作為對象的預設引用有兩種情形

  • 在構造器引用該構造器正在初始化的對象
  • 在方法中應用調用該方法的對象

this關鍵字的最大作用就是讓類的一個方法通路該類的另一個方法或屬性

當局部變量和屬性同名的時候,程式要通路隐藏的屬性的時候必須使用this關鍵字

class ReturnThis{
    var age=0
    fun grow():ReturnThis{
        age++
        return this
    }
}
fun main() {
    var rt=ReturnThis()
    //可以持續調用同一個方法
    rt.grow().grow().grow()
    println(rt.age)
}
           

列印

3

4、方法與函數的關系

函數與方法是統一的。其實兩者之間并沒有太多的差別

擷取的類方法名前面添加兩個冒号(:)

class Dog{
    fun run(){
        println("這是一個方法")
    }

    fun eat(food:String){
        println("現在正在吃"+food)
    }
}
fun main() {
    var rn:(Dog)->Unit=Dog::run//直接使用類名加方法名的方式應用
    val d=Dog()
    rn(d)

    var et=Dog::eat//et的變量類型應該是(Dog,String)->Unit,這裡是及時系統自動推斷出來的。調用類的對象方法,必須要給他一個對象,盡管方法體裡面沒有對象參數
    et(d,"肉骨頭")
}
           

列印

這是一個方法

現在正在吃肉骨頭

5、中綴表示法

中綴方法可以是方法想雙目運算符一項

class ApplePack(weight:Double){
    var weight=weight
    override fun toString(): String {
        return "ApplePack[weight=${this.weight}]"
    }
}

class Apple(weight: Double){
    var weight=weight
    override fun toString(): String {
        return "Apple[weight=${this.weight}]"
    }

    //定義中綴方法,使用infix修飾
    infix  fun add(other:Apple) :ApplePack{
        return ApplePack(this.weight+other.weight)
    }

    infix  fun drop(other:Apple) :Apple{
        this.weight-=other.weight
        return this
    }

}
fun main() {
    var origin=Apple(3.5)
    //使用add方法。add是一個雙目運算符
    val ap=origin add Apple(2.4)//這裡有個匿名的Apple執行個體,add方法沒有使用小括号
    println(ap)

    origin drop Apple(1.5)
    println(origin)
}
           

ApplePack[weight=5.9]

Apple[weight=2.0]

6、componetN方法與結構

kotlin允許将一個對象的N個屬性 “解構” 給多個變量,寫法如下

實際上執行了

var name=user.component1()
var pass=user.component2()
           

有幾個屬性就執行幾個n

class User(name:String,pass:String,age:Int){
    var name=name
    var pass=pass
    var age=age

    //定義operator修飾的componentN方法,用于解構
    operator fun component1():String{
        return this.name
    }
    //定義operator修飾的componentN方法,用于解構
    operator fun component2():String{
        return this.pass
    }
    //定義operator修飾的componentN方法,用于解構
    operator fun component3():Int{
        return this.age
    }
}
fun main() {
   //建立user對象
    val user=User("zhangsan","558899",23)
    //将user對象解構給2個變量
    var (name,pass:String)=user
    println(name)
    println(pass)
    println("-----")

    var (name2,pass2,age)=user
    println(name)
    println(pass)
    println(age)
    println("-----")

    var(_,pass3,age2)=user
    println(name)
    println(pass)
    println(age)
}
           

列印

zhangsan
558899
-----
zhangsan
558899
23
-----
zhangsan
558899
23
           

7、資料類型和傳回多個值的函數

資料類型就是為了簡化解構的實作,用來封裝資料的類稱之為資料類型

資料類型除了使用data修飾符外,還需要滿足如下要求

  • 主構造器至少需要由一個參數
  • 主構造器的所有參數需要用val或var聲明為屬性
  • 資料類不能用abstract,open,sealed修飾,也不能定義成内部類
  • 資料類可以繼承其他類

定義資料類後,系統自動為資料類型生成如下内容

生成equals()/hashCode()方法

自動重寫toString()方法,傳回形如“User(name=John,age=42)”的字元串

為每個屬性自動生成operator修飾的componentN()方法

生成copy()方法,用于完成對象複制

後面暫時沒看懂

8、在lambda表達式中解構

後面暫時沒看懂

9、讀寫屬性和隻讀屬性

val為隻讀屬性,隻會為生成getter方法

var為讀寫屬性,系統為其生成getter和setter方法

定義屬性的時候一般需要用權限修飾符進行修飾

顯示指定方法為setter和getter方法與java一樣

屬性的調用使用點(.)句法

10、自定義getter和setter

大緻和java的自定義的getter方法和setter方法類似

11、幕後字段

設定getter和setter方法是可以自定義屬性的get和set方式,實際上就是重寫getter和setter方法

12、幕後屬性

使用private修飾的屬性就是幕後屬性。不生成任何getter和setter,是以程式不能直接通路幕後屬性,開發者需要為幕後屬性提供getter和setter方法

13、延遲初始化屬性

延遲初始化是暫時不在定義屬性之初指派。

kotlin提供lateinit修飾符來解決屬性的延遲初始化,對lateinit修飾符有如下限制

  • 隻能修飾var定義的可變屬性,val定義的不可以
  • lateinit修飾的屬性不能有自定義的getter或setter方法
  • lateinit修飾的屬性必須是非空類型
  • lateinit修飾的屬性不能是原生類型(也就是java的8種基本類型對應的屬性)

    類檔案

class User {
    //設定延遲屬性
    lateinit var name:String
    lateinit var birth:Date

}
           

主函數

fun main(args:Array<String>){
    var user1=User()

    //println(user1.name) //報初始化異常
    //println(user1.birth)//報初始化異常

    user1.name="zhangsan"
    user1.birth= Date()
    println(user1.name) //不再報初始化異常
    println(user1.birth)//不再報初始化異常

}
           

列印

zhangsan

Thu Sep 17 09:13:12 CST 2020

14、内聯屬性

實際就是對沒有幕後屬性(自定義getter、setter)的屬性進行修飾,使其擁有了内聯化(設定getter、setter)

使用inline修飾符,既可以修飾屬性,也可以修飾getter或setter方法

class Name(name: String,desc:String){
    var name=name
    var desc=desc
}

class Product{
    var porductor:String?=null
    //inline修飾屬性的getter方法,表明讀取屬性時會内聯化
    val proName:Name
    inline get()=Name("瘋狂安卓講義","最系統的kotlin書")
    //inline修飾屬性的setter方法,表明設定屬性值時,會内聯化
    var author:Name
    get() = Name("李剛","無")
    inline set(v){
        this.porductor=v.name
    }
    //inline修飾屬性本身,表明讀取getter和設定setter屬性時都會發生内聯化
    inline var pubHouse:Name
    get()=Name("電子工業出版社","無" )
    set(v){
        this.porductor=v.name
    }
}
           

15、包和導包

與java的包概念相同

package packagename

導包使用import關鍵字,是java的import和import static的合體,不僅可以導入類,還可以導入如下内容

  • 頂層函數及屬性
  • 在對象生命中聲明的函數和屬性
  • 枚舉常量

16、kotlin的預設導入

類似于List,Map都預設自動導包了

17、使用通路控制符

private:同java

internal:internal成員可以在該類的内部或檔案的内部或者同一子產品内被通路

protected:在該哪類的内部或檔案的内部或者子類中被通路

public:同java

取消了預設通路權限,引入internal權限

取消了protected的包通路權限

預設通路權限是public

位于包内的頂層成員

對于位于包内的頂層成員(包括頂層類、接口、函數、屬性),隻能使用private,internal,和public其中之一,不能使用protected修飾符

位于類,接口之内的成員

位于類、接口之内的成員(包括頂層類,接口,函數,屬性),能使用private,internal,protected和public其中之一。

類的住構造器,由于是在類頭部分聲明的,如果需要為主構造器指定通路權限修飾符,則一定要使用constructor關鍵字,并在該關鍵字前面添加private、internal、protected和public其中之一。如果不為主構造器指定通路全休修飾符,那麼主構造器預設的通路權限修飾符是public

18、主構造器和初始化塊

類定義時可以定義0-1個主構造器,0-n個次構造區,如果主構造器沒有任何的注解或可見性修飾符,則可以省略constructor關鍵字

主構造器可以有形參,但沒有執行體,形參有兩點作用

  • 初始化塊可以使用主構造器定義的形參
  • 在聲明屬性石可以使用主構造器定義的形參
fun main(args:Array<String>){
    var person=Person("zhangsanfeng")

}
class Person(name:String){
    //定義一個初始化塊
    init{
        var a=6
        if (a>4){
            println("person初始化塊,局部變量a的值大于4")
        }
        println("Person的初始化塊")
        println("name參數為:${name}")
    }
    //定義第二個初始化塊
    init {
        println("Person的第二個初始化塊")
    }
}
           

列印:

person初始化塊,局部變量a的值大于4

Person的初始化塊

name參數為:zhangsanfeng

Person的第二個初始化塊

構造器最大的用處就是在建立對象時執行初始化,可見,主構造器的作用就是為初始化塊定義參數,是以主構造器更像是初始化塊的一部分,也可以說,初始化塊就是主構造器的執行體

一旦程式員提供了自定義的構造器,系統就不在提供預設的構造器。也可以提供多個構造器,構成構造器重載

fun main(args:Array<String>){
    var tc=ConstructorTest("瘋狂java講義",9000)
    println(tc.name)
    println(tc.count)

}
class ConstructorTest(name:String,count:Int){
    var name:String
    var count:Int
    init {
        this.name=name
        this.count=count
    }
}
           

列印:

瘋狂java講義

9000

19、次構造器和構造器重載

kotlin要求素有的次構造器都必須委托調用主構造器,可以直接委托或通過别的次構造器簡介委托。所謂“委托”、其實就是要先調用主構造器(執行初始化快中的代碼),然後才執行次構造器代碼

如果兩個構造器有相同的代碼,可以放在初始代碼塊中執行(初始代碼塊總是先于構造器執行)如果初始化代碼塊需要參數,則可将參數放在主構造器中定義,進而提高代碼的複用性。

系統通過形參清單的不同來區分不同的次構造器(構造器重載)

fun main(args:Array<String>){
   //無參構造對象
    var c1=ConstructorOverLoad()
    var c2=ConstructorOverLoad("zhangsan",33)
    println("${c1.name},${c1.count}")
    println("----")
    println("${c2.name},${c2.count}")

}
class ConstructorOverLoad{
    var name:String?
    var count:Int
    init {
       println("初始化塊")
    }
    constructor(){
        name=null
        count=0
    }
    constructor(name:String,count:Int){
        this.name=name
        this.count=count
    }
}
           

列印:

初始化塊
初始化塊
null,0
----
zhangsan,33
           

又例

fun main(args:Array<String>){
    var user1=User("孫悟空")
    println("${user1.name}--${user1.age}--${user1.info}")
    var user2=User("白骨精",16)
    println("${user2.name}--${user2.age}--${user2.info}")
    var user3=User("蜘蛛精",26,"吐絲結網")
    println("${user3.name}--${user3.age}--${user3.info}")
}
//定義主構造器

class User (name:String){
    var name:String
    var age:Int
    var info:String?=null
    init {
        println("User的初始化塊")
        this.name=name
        this.age=0
    }
    //委托給主構造器
    constructor(name:String,age:Int):this(name){
        this.age=age
    }
    //委托給次構造器并通過次構造器間接委托給主構造器
    constructor(name: String,age: Int,info:String):this(name,age){
        this.info=info
    }
}
           

列印:

孫悟空–0--null

User的初始化塊

白骨精–16–null

User的初始化塊

蜘蛛精–26–吐絲結網

主構造器聲明屬性

類的主構造器屬性

fun main(args:Array<String>){
   var im=Item("123456",6.7)
    println(im.code)
    println(im.price)
}
//定義主構造器
class Item (val code:String,var price:Double){

}
           

列印

123456

6.7

如果主構造器的所有參數都有預設值,程式能以構造參數的預設值來調用該構造器(即不需要為構造參數傳入值),此時看上去就像調用無參數的構造器

//定義主構造器
class Customer(val name:String="匿名",var addr:String="天河"){

}
fun main(args:Array<String>){
    //調用有參的主構造器
    var ct=Customer("孫悟空","花果山")
    println(ct.name)
    println(ct.addr)
    //以構造參數的預設值調用構造器,看上去像調用無參數的構造器
    var ctm=Customer()
    println(ctm.name)
    println(ctm.addr)
}
           

列印:

孫悟空

花果山

匿名

天河

20、繼承的文法

使用冒号:來繼承

Any類是所有類的父類,類似于java的Object類

如果kotlin類未顯示指定父類,預設繼承自Any類

kotlin的類預設為final修飾,如果需要該類被繼承,應當在前面用open進行修飾

kotlin的子類構造器最終都要調用父類的構造器,kotlin将其稱之為委托父類構造器,這裡分兩種情況

  • 子類主構造器調用父類構造器
  • 子類次構造器調用父類構造器

子類主構造器調用父類構造器

子類如果沒有顯式聲明主構造器,預設有一個主構造器,是以要在聲明繼承是委托調用父類構造器

//定義基類
open class BaseClass{
    var name:String
    constructor(name:String){
        this.name=name
    }
}

//子類沒有顯式聲明主構造器,預設有一個主構造器,是以要在聲明繼承是委托調用父類構造器
class Subclass1:BaseClass("張三"){

}

//子類顯式聲明主構造器,主構造器必須聲明繼承是委托調用父類構造器
class Subclass2(name:String):BaseClass(name){

}
fun main(args:Array<String>){
    var s1=Subclass1()
    println(s1.name)
    var s2=Subclass2("李四")
    println(s2.name)
}
           

列印

張三

李四

子類次構造器調用父類構造器

分三種情況調用父類構造器

  • 子類構造器顯式使用:this(參數)顯式調用本類中重載的構造器,系統根據this(參數)調用中傳入的實參清單調用本類中的另一個構造器。最終調用父類構造器
  • 子類構造器顯式使用:super(參數)顯式調用父類構造器,系統根據super(參數)調用中傳入的實參清單調用父類對應的構造器
  • 子類構造器既沒有使用super(參數)也沒有使用this(參數)條用,系統将會在執行子類構造器之前,隐式調用父類無參數的構造器

    //定義基類

open class BaseClass{
    constructor(){
        println("Base基類的無參構造器")
    }
    constructor(name:String){
        println("Base的帶參(String)構造器:${name}的構造器")
    }
}

class Sub:BaseClass{
    //構造器沒有顯式委托,是以該次構造器将會隐式委托調用父類無參數的構造器
    constructor(){
        println("Sub子類的無參構造器")
    }
    //構造器用super(name)顯式委托父類帶String參數的構造器
    constructor(name:String):super(name){
        println("Sub的String構造器,String參數為${name}")
    }
    //構造器用super(name)顯式委托本類帶String參數的構造器
    constructor(name: String,age:Int):this(name){
        println("Subd的String構造器,Int構造器,Int參數為:${age}")
    }
}

fun main(args:Array<String>){
    Sub()
    Sub("Sub")
    Sub("子類",29)
}
           

列印:

Base基類的無參構造器

Sub子類的無參構造器

Base的帶參(String)構造器:Sub的構造器

Sub的String構造器,String參數為Sub

Base的帶參(String)構造器:子類的構造器

Sub的String構造器,String參數為子類

Subd的String構造器,Int構造器,Int參數為:29

21、重寫父類方法

子類繼承父類,可以獲得父類的全部屬性和方法

被子類重寫的方法前面依然要用open關鍵字來修飾,子類重寫的方法用override方法修飾

open class Bird{
    open fun fly(){
        println("我在天空中自由自在的翺翔")
    }
}

class Ostrich:Bird(){
    //重寫fly方法
    override fun fly(){
        println("我隻能在地上奔跑")
    }
}

fun main(args:Array<String>){
    var myostrich=Ostrich()
    myostrich.fly()

}
           

列印:

我隻能在地上奔跑

方法要遵循“兩同兩小一大”,同java

22、重寫父類屬性

和重寫屬性相似,父類使用open,子類使用override

屬性重寫的額外限制

  • 重寫的子類屬性的類型與父類屬性的類型要相容
  • 重寫的子類屬性要提供更大的通路權限。子類通路權限要相同或更大,隻讀屬性可以被讀寫屬性重寫
//定義基類
open class Item{
    open protected var price:Double=10.9
    open val name:String=""
    open var validDays:Int=0
}

class Book:Item{
    override public var price:Double
    override  var name="圖書"
    constructor(){
        price=3.0
    }
}
fun main(args:Array<String>){
  
}
           

23、super限定

子類方法中調用父類中被覆寫的方法或屬性,則可以使用super限定

open class Bird{
    open fun fly(){
        println("我在天空中自由自在的翺翔")
    }
}

class Ostrich:Bird(){
    //重寫fly方法
    override fun fly(){
        println("我隻能在地上奔跑")
    }

    fun callOverideMehod(){
        //子類中顯示調用父類中被覆寫的方法
        super.fly()
    }
}

fun main(args:Array<String>){
    var myostrich=Ostrich()
    myostrich.fly()
    myostrich.callOverideMehod()

}
           

列印

我隻能在地上奔跑

我在天空中自由自在的翺翔

又一例,重寫屬性

open class BaseClss{
    open var a:Int=5
}

class SubClass:BaseClss(){
    //重寫屬性
    override var a:Int=7
    fun  accessOwner(){
        println(a)
    }

    fun accessBase(){
        //通路父類屬性
        println(super.a)
    }
}

fun main(args:Array<String>){
   val sc=SubClass()
    sc.accessOwner()
    sc.accessBase()

}
           

列印

7

5

24、強制重寫

如果子類從多個直接超類(接口或類)繼承了同名的成員,kotlin要求子類必須重寫該成員。如果需要在子類中使用super來應用超類中的成員,則可使用尖括号加超類型名限定的super進行引用,如

例子

open class Foo {
    open fun test(){
        println("Foo的test")
    }
}

interface Bar {
    //接口中的成員是預設open的
    open fun test(){
        println("Bar的test")
    }
}

class Wow:Foo(),Bar{
    //編譯器要求必須重寫test()
    override fun test(){
        super<Foo>.test()//調用父類Foo的test()
        super<Bar>.test()//調用父接口Bar的test()
    }

}
fun main(args:Array<String>){
    var w=Wow()
    w.test()
}
           

列印:

Foo的test

Bar的test

25、多态性

編譯時類型,和運作時類型

編譯時類型由聲明該變量時使用的類型決定,運作時類型由實際付給該變量的對象決定。

當編譯時和運作時類型不一緻的時候,就稱之為多态

向上轉型和向下轉型是多态的核心概念

26、使用is檢查類型

為了保證類型轉換不會出錯,kotlin提供類型檢查運算符is和**!is**

interface Testable{}
fun main(args:Array<String>){
    val hello:Any="hello"//編譯時Any類型,運作時是String類型
    println(hello is String)//傳回true
    println(hello is Date)//傳回false
    println(hello is Testable)//由于hello沒有實作Testable接口,傳回false
}
           

列印:

true

false

false

kotlin的is和 !is非常智能,隻要程式使用is或者!is對變量進行了判斷,系統就會自動将變量的類型轉換為目标類型。

27、使用as運算符轉型

強制轉換成運作時類型,需要借助as和as?強制轉換類型符号

  • as:不安全的強制轉換運算符,如果轉型失敗,引發異常
  • as?:安全的強制轉換運算符,如果轉型失敗,不會引發異常,而是傳回null
fun main(args:Array<String>){
    val obj:Any="hello"//編譯時Any類型,運作時是String類型
    val objstr=obj as String
    println(objstr)
}
           

列印:

hello

繼續閱讀