kotlin學習--類的延申

一、內(nèi)部類

在現(xiàn)實開發(fā)中,內(nèi)部類出現(xiàn)的場景可以說非常多,例如,adapter(適配器)中的viewHolder、數(shù)據(jù)模型中也有可能出現(xiàn)一個或者多個對象,這里的對象就是一個內(nèi)部類。讓我們來了解下內(nèi)部類哩。

1.1.嵌套類

所謂的嵌套類,通俗來講就是一個類嵌套在另一個類里面
eg:

/**
 * 嵌套類
 */
 private var name = "biao"
    fun outdoorMethou(){
        //nesteMethou()//無法直接調(diào)用嵌套類的成員
        //只能通過實例嵌套類來調(diào)用
        val nestingClass = NestingClass()
        nestingClass.nesteMethou()
    }

    class NestingClass{
        fun nesteMethou(){
            //println("name:$name")//無法使用外部類的成員
            println("to do something magic")
        }
    }

fun main(args:Array<String>){
    val nestingClass = OutdoorClass.NestingClass()
    nestingClass.dowhat()
}

reslut:

to do something magic

說明:

  • 嵌套類相當于Java中的靜態(tài)內(nèi)部類,但是在kotlin中完全取消了static關鍵字,所以在kotlin類中,除了嵌套類,其余都是非靜態(tài)成員
  • 在Java中靜態(tài)成員是不能訪問非靜態(tài)成員的,參考于此,可以解釋在嵌套類中不能訪問外部類的成員,但是可以訪問其他嵌套類
  • 跟內(nèi)部類一樣,外部類不能直接調(diào)用嵌套類成員,如需調(diào)用,需創(chuàng)建嵌套類對象,通過對象調(diào)用嵌套類成員
  • 調(diào)用嵌套類的屬性或方法的格式為:外部類.嵌套類().嵌套類方法/屬性。在調(diào)用的時候嵌套類是需要實例化的。

1.2.內(nèi)部類

內(nèi)部類相當于Java中沒有用static修飾的內(nèi)部類,使用關鍵字:inner
eg:

class OutClass{

    //外部類的私有屬性可以被內(nèi)部類使用
    private var name = "xie"
    //外部類的私有函數(shù)可以被內(nèi)部類使用
    private fun outFun(){
        println("i am outFun")
    }

    //通過此方法實例化內(nèi)部類,再調(diào)用內(nèi)部類方法
    fun getInnerClassTodo(){
        //innerFun()//這種外部類直接調(diào)用內(nèi)部類成員的方式是編譯不通過的,因為此時根本不存在內(nèi)部類的對象
        val innerClass = InnerClass()
        //外部類想調(diào)用內(nèi)部類,需實例化內(nèi)部類
        innerClass.innerFun()
    }

    inner class InnerClass{
        var age = 20

        fun innerFun(){
            println("name:$name+age:$age")
            outFun()
        }
    }
}

fun main(args:Array<String>){
    val innerClass = OutClass().InnerClass()
    innerClass.innerFun()
}

reslut:

name:xie+age:20
i am outFun

說明:

  • 外部類的成員可以被內(nèi)部類使用,外部類無法直接使用內(nèi)部類成員,只能通過實例化內(nèi)部類成員
  • 調(diào)用內(nèi)部類的屬性或方法的格式為:外部類().內(nèi)部類().內(nèi)部類方法/屬性。在調(diào)用的時候外部類、內(nèi)部類都是需要實例化的。

1.3.匿名內(nèi)部類

在實際開發(fā)中,匿名內(nèi)部類并不陌生,隨處可見,像view的點擊事件:OnClickListener,多線程的實現(xiàn)。
其實匿名內(nèi)部類就是一個沒有名字的內(nèi)部類,通常用來簡化代碼,使用匿名內(nèi)部類的前提條件是:必須繼承一個父類或者實現(xiàn)一個接口
eg:

/**
 * 定義一個接口
 */
interface OnClickListener{
    fun onItemClick(str : String)
}

/**
 * 匿名內(nèi)部類
 */
class AnonymousClass{
    lateinit private var listener : OnClickListener

    fun setOnClickListener(listener: OnClickListener){
        this.listener = listener
    }

    fun testListener(){
        listener.onItemClick("我是匿名內(nèi)部類的測試方法")
    }
}

fun main(args:Array<String>){
    val anonymousClass = AnonymousClass()
    anonymousClass.setOnClickListener(object :OnClickListener{
        override fun onItemClick(str: String) {
            println("to do something")
        }
    })
}

1.4.局部類

局部類:跟Java一樣,就是在方法中的類
eg:

class LocalClass{

    var numOut = 1

    fun partMouth(){

        var name = "partMouth"

        class PartClass{
            var numPart : Int = 2

            fun test(){
                name = "PartClass"
                numOut = 3
                numPart = 4
                println("我是局部類中的方法")
            }
        }

        val partClass = PartClass()
        println("name = $name \t numPart = " + partClass.numPart + "\t numOut = $numOut")
        partClass.test()
        println("name = $name \t numPart = " + partClass.numPart + "\t numOut = $numOut")
    }
}

fun main(args:Array<String>){
    val localClass = LocalClass()
    localClass.partMouth()
}

result:

name = partMouth     numPart = 2     numOut = 1
我是局部類中的方法
name = PartClass     numPart = 4     numOut = 3

說明:

  • 局部類只能在定義該局部類的方法中使用。
  • 定義在實例方法中的局部類可以訪問外部類的所有變量和方法。也能修改
  • 局部類中的可以定義屬性、方法。并且可以修改局部方法中的變量。

二、抽象類

在我們?nèi)粘i_發(fā)中,一般會需要寫一個基類,封裝一些方法以及處理一些共有的邏輯,只是不同的類會因其不同的功能實現(xiàn)不同的代碼。就這樣的一個基類,一般都是一個抽象類

2.1.抽象類的定義

抽象類:可以理解為一個模板,所有的子類根據(jù)這個模板填充自己的代碼

2.1.1 關鍵字

跟Java一樣,kotlin抽象類的關鍵字也是abstract
在這里我們需要注意的是抽象分為抽象類、抽象函數(shù)和抽象屬性,而一個抽象類和一個普通類的區(qū)別就在于抽象類的除了可以有其自己的屬性、構造函數(shù)和函數(shù)等組成部分,還包含了抽象函數(shù)和抽象屬性。
eg:

abstract class AbstractClass {
    //自身的屬性
    var tag = this.javaClass.simpleName

    //自身的方法
    fun simpleFun():Unit{//Unit相當于java中的void,表示沒有返回值,可以省略不寫
        TODO("do something")
    }

    //抽象屬性和方法
    abstract var string:String
    abstract fun init()
}

class ChildrenClass : AbstractClass() {
    //不是抽象類的子類必須實現(xiàn)父類的方法和屬性
    override var string: String
        get() = tag
        set(value) {}

    override fun init() {
        println("Implementing parent class methods,Obtain tag:$string")
    }
}

abstract class AbsChildrenClass : AbstractClass(){
    //抽象子類可不用實現(xiàn)父類的方法和屬性
    var name = this.javaClass.simpleName
    abstract fun check()
}

class absExtend : AbsChildrenClass(){
    var className = this.javaClass.simpleName
    override var string: String
        get() = name
        set(value) {}

    override fun init() {
        println("name:$name")
    }

    override fun check() {
        if (className == string) println("do something")
        else println("Nothing can be done")
    }
}

fun main(args : Array<String>){
    //抽象類不能直接實例化
    //val abstractClass = AbstractClass()//Cannot create an instance of an abstract class
    val childrenClass = ChildrenClass()
    var abstractClass:AbstractClass = ChildrenClass()//若要實現(xiàn)抽象類的實例化,需要依靠子類采用向上轉型的方式處理
    val absExtend = absExtend()

    childrenClass.init()
    absExtend.init()
    absExtend.check()
}

result:

Implementing parent class methods,Obtain tag:ChildrenClass
name:absExtend
do something

小結:

  • 抽象類本身具有普通類特性,以及組成部分。不過值得注意的是,抽象類不能直接被實例化
  • 抽象類是為其子類定義了一個模板。不同是類實現(xiàn)不同的功能
  • 若普通類繼承了抽象類,需全部重寫父類的抽象方法和屬性,如子類是抽象則不用

規(guī)則:

  • 在Kotlin中的抽象類在頂層定義的時候只能使用public可見性修飾符修飾。
  • 抽象類中可以定義內(nèi)部抽象類。
  • 只能繼承一個抽象類。
  • 若要實現(xiàn)抽象類的實例化,需要依靠子類采用向上轉型的方式處理。eg:var abstractClass:AbstractClass = ChildrenClass()
  • 抽象類可以繼承自一個繼承類,即抽象類可以作為子類。不過,抽象類建議不用open修飾符修飾,因為可以覆寫抽象類的父類的函數(shù)。

三、接口類

3.1 聲明以及用法

3.1.1 聲明

關鍵字:interface
定義格式:interface 接口名{
}

3.1.2 用法

關鍵字:“:”,表示實現(xiàn)該接口,Java不一樣的是,Java使用implement關鍵字
綜合之前我之前寫過的文章,內(nèi)容使用到:的次數(shù)表示,這里講一下:有哪些代表含義:
a、用于屬性的定義
b、用于表示實現(xiàn)接口
c、用戶繼承
d、用于方法返回類型定義

eg:

interface InterfaceClass{
    fun funOne()
}

class Demo:InterfaceClass{
    override fun funOne() {
        println("funOne are using")
    }
}

fun main(args: Array<String>) {
    val demo = Demo()
    demo.funOne()
}

result:

funOne are using

3.2.接口中的方法使用

eg:

interface InterfaceClass {

    fun funOne()

    fun funTwo(num: Int)

    fun funThree(num: Int): Int

    /**
     * 定義了一個有參無返回值的方法
     * 因為有結構體故可以不用重寫
     */
    fun funFour(): String {
        return "I am funFour"
    }
}

class Demo : InterfaceClass {
    override fun funOne() {
        println("funOne are using")
    }

    override fun funTwo(num: Int) {
        println("funTwo are using and parem is num:$num")
    }

    override fun funThree(num: Int): Int {
        println("funThree are using and parem is num:$num")
        return num + 1
    }

    /**
     * 接口中的funFour()方法默認返回”funFour“字符串.
     * 可以用super.funFour()返回默認值
     * 也可以不用super關鍵字,自己返回一個字符串
     */
    override fun funFour(): String {
        println("user I am funFour")
        return super.funFour()
    }
}

fun main(args: Array<String>) {
    val demo = Demo()
    demo.funOne()
    demo.funTwo(8)
    println(demo.funThree(9))
    demo.funFour()
}

result:

funOne are using
funTwo are using and parem is num:8
funThree are using and parem is num:9
10
user I am funFour
I am funFour

說明:

  • 不帶結構體的函數(shù)可以省略大括號,且不用強制重寫帶結構體的函數(shù)就可以直接調(diào)用。
  • 接口方法可以默認實現(xiàn),Java8中需要一個default關鍵字,kotlin只需要寫方法體就好

3.3.接口中的屬性使用

  • 在接口中申明屬性。接口中的屬性要么是抽象的,要么提供訪問器的實現(xiàn)。接口屬性不可以有后備字段。而且訪問器不可以引用它們。
3.3.1 作為抽象的

eg:

interface InterfaceFeild {
    var num:Int
    var name:String
}

class Demo1(override var num: Int, override var name: String) :InterfaceFeild{
    fun test(){
        println("num:$num\t+name:$name")
    }
}

fun main(args: Array<String>) {
    val demo1 = Demo1(21, "biao")
    demo1.test()
}

result:

num:21 name:biao

說明:

  • 即重寫屬性的時候是在實現(xiàn)類的類參數(shù)中。這也是用代碼提示去重寫的實現(xiàn)方法
3.3.2 作為訪問器

手動方式去重寫,并提供get方法

eg:

interface InterfaceFeild {
    var num:Int
    var name:String
    val age:Int
        get() = 2

    var number:Int
}

class Demo1(override var num: Int, override var name: String) :InterfaceFeild{

    fun test(){
        println("age:$age\tnumber:$number")
    }

    override val age: Int
        get() = super.age

    override var number: Int = 25
}

fun main(args: Array<String>) {
    val demo1 = Demo1(21, "biao")
    demo1.test()
    demo1.number = 3
    demo1.test()
}

result:

num:21  name:biao
age:2   number:25
num:21  name:biao
age:2   number:3
3.3.3 接口的沖突問題解決

該問題是指當我們在父類中聲明了許多類型,有可能出現(xiàn)一個方法的多種實現(xiàn)

eg:

interface InfOne{
    fun funTest(){
        println("I am InfOne")
    }
}

interface InfTwo{
    fun funTest(){
        println("I am InfTwo")
    }
}

class Demo2 : InfOne,InfTwo{
    override fun funTest() {
        super<InfOne>.funTest()
        super<InfTwo>.funTest()
    }

}

fun main(args: Array<String>) {
    val demo2 = Demo2()
    demo2.funTest()
}

說明:Demo4實現(xiàn)了InfOne和InfTwo兩個接口,而兩個接口中都存在兩個相同方法名的方法。因此編譯器不知道應該選哪個,故而我們用super<接口名>.方法名來區(qū)分。

四、繼承類

4.1 超類 Any

在Kotlin中,說有的類都是繼承與Any類,這是這個沒有父類型的類。即當我們定義各類時,它默認是繼承與Any這個超類的

來看看Any的源碼

package kotlin

/**
 * The root of the Kotlin class hierarchy. Every Kotlin class has [Any] as a superclass.
 * 看這個源碼注釋:意思是任何一個Kotlin的類都繼承與這個[Any]類
 */
public open class Any {
    
    // 比較: 在平時的使用中經(jīng)常用到的equals()函數(shù)的源碼就在這里額
    public open operator fun equals(other: Any?): Boolean
    
    // hashCode()方法:其作用是返回該對象的哈希值
    public open fun hashCode(): Int
    
    // toString()方法
    public open fun toString(): String
}

從源碼可以我們看出,它直接屬于kotlin這個包下。并且只定義了上面所示的三個方法?;蛟S你具有Java的編程經(jīng)驗。在我們熟知的Java中,所有的類默認都是繼承與Object類型的。而Object這個類除了比Any多了幾個方法與屬性外,沒有太大的區(qū)別。
此外,從上面的源碼中我們可以看到類和函數(shù)都有open關鍵字,那么這個修飾符的作用是什么呢?
來,讓我們來分析一下,超類(Any)是所有類的父類,而分類中所有元素都有open修飾符,如果我們跟Any的語法去寫一個類,是不是就是一個繼承類了,說明,open修飾符是我們定義繼承類的修飾符

4.2 定義

4.2.1 基礎使用
  • 定義繼承類的關鍵字為:open。不管是類、還是成員都需要使用open關鍵字。
  • 定義格式為:
open class 類名{
    open var/val 屬性名 = 屬性值
    open fun 函數(shù)名()
}

eg:

open class InheritClass{
    open var num = 3
    open fun funOne() = "funOne"
    open fun funTwo() = 25
}

class DemoTest : InheritClass(){
    // 這里值得注意的是:Kotlin使用繼承是使用`:`符號,而Java是使用extends關鍵字
}

fun main(args:Array<String>){
    val demoTest = DemoTest()
    println(demoTest.num)
    println(demoTest.funOne())
    println(demoTest.funTwo())
}

result:

3
funOne
25

說明:
從上面的代碼可以看出,DemoTest類只是繼承了Demo類,并沒有實現(xiàn)任何的代碼結構。一樣可以使用Demo類中的屬性與函數(shù)。這就是繼承的好處。

4.2.2 構造函數(shù)

在上次的分享中有提到過,在kotlin中,可以有一個主構造函數(shù),或者多個輔助函數(shù)?;蛘邲]有構造函數(shù)的情況。

在這里,來說一下實現(xiàn)類無主構造函數(shù),和存在主構造函數(shù)的情況。

  • 無主構造函數(shù)

當實現(xiàn)類無主構造函數(shù)時,則每個輔助構造函數(shù)必須使用super關鍵字初始化基類型,或者委托給另一個構造函數(shù)。 請注意,在這種情況下,不同的輔助構造函數(shù)可以調(diào)用基類型的不同構造函數(shù)

這里用view的自定義來說明一下,eg:

class MyView : View(){

    constructor(context: Context) : super(context)

    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)

    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int)
        : super(context, attrs, defStyleAttr)
}

可以看出,當實現(xiàn)類無主構造函數(shù)時,分別使用了super()去實現(xiàn)了基類的三個構造函數(shù)。

  • 存在主構造函數(shù)

當存在主構造函數(shù)時,主構造函數(shù)一般實現(xiàn)基類型中參數(shù)最多的構造函數(shù),參數(shù)少的構造函數(shù)則用this關鍵字引用即可了。這點在Kotlin——中級篇(一):類(class)詳解這篇文章是講解到的。

這里同樣用view自定義的例子,eg:

class MyView(context: Context?, attrs: AttributeSet?, defStyleAttr: Int)
    : View(context, attrs, defStyleAttr) {

    constructor(context: Context?) : this(context,null,0)
    
    constructor(context: Context?,attrs: AttributeSet?) : this(context,attrs,0)
}
4.2.3 函數(shù)的重載和重寫

kotlin的重載和重寫的理解和Java一樣,重載:是在一個類里面,方法名字相同,而參數(shù)不同。返回類型可以相同也可以不同;重寫是子類對父類的允許訪問的方法的實現(xiàn)過程進行重新編寫,返回值和形參都不能改變。即外殼不變,核心重寫!

4.2.3.1 重寫函數(shù)中的兩點特殊用法

這里介紹兩點kotlin相較于Java特殊的地方

  • 當基類中的函數(shù),沒有用open修飾符修飾的時候,實現(xiàn)類中出現(xiàn)的函數(shù)的函數(shù)名不能與基類中沒有用open修飾符修飾的函數(shù)的函數(shù)名相同,不管實現(xiàn)類中的該函數(shù)有無override修飾符修飾。

eg:

open class InheritClass{
    fun test(){}
}

class DemoTest : InheritClass(){
    // 這里聲明一個和基類型無open修飾符修飾的函數(shù),且函數(shù)名一致的函數(shù)
    // fun test(){}   編輯器直接報紅,根本無法運行程序
    // override fun test(){}   同樣報紅
}
  • 當一個類不是用open修飾符修飾時,這個類默認是final的
4.2.3.1 方法重載

方法重載也是體現(xiàn)了面向對象中的多態(tài)的特性
方法重載在繼承類同樣有效
eg:

open class InheritClass{
    open fun funThree() = "funThree"
}

class DemoTest : InheritClass(){
    fun funThree(name:String):String{
        return name
    }
    override fun funThree(): String {
        return super.funThree()
    }
}

fun main(args:Array<String>){
    val demoTest = DemoTest()
    println(demoTest.funThree())
    demoTest.funThree("I am funThree,Do you known polymorphic?")
}

result:

funThree
I am funThree,Do you known polymorphic?
4.2.4 重寫屬性
  • 重寫屬性和重寫方法其實大致是相同的,但是屬性不能被重載。
  • 重寫屬性即指:在基類中聲明的屬性,然后在其基類的實現(xiàn)類中重寫該屬性,該屬性必須以override關鍵字修飾,并且其屬性具有和基類中屬性一樣的類型。且可以重寫該屬性的值(Getter)

eg:

open class InheritClass{
    open var num = 3
}

class DemoTest : InheritClass(){
    override var num: Int = 10
}

fun main(args:Array<String>){
    val demoTest = DemoTest()
    println(demoTest.num)
}

result:

10
4.2.4.1 重寫屬性中,val與var的區(qū)別

有一點值得我們注意的是當基類中屬性的變量修飾符為val的使用,其實現(xiàn)類可以用重寫屬性可以用var去修飾。反之則不能。
eg:

open class InheritClass {
    open val valStrOne = "I am val string one"
    open val valStrTwo = "I am val string two"
    open val valStrThree = "I am val string three"
    open val valStrFour = "I am val string four"
}

class DemoTest : InheritClass() {
    override val valStrOne: String
        get() = super.valStrOne

    override var valStrTwo: String = ""
        get() = super.valStrTwo

    override val valStrThree: String = ""

    override var valStrFour: String = "abc"
        set(value){field = value}
}

fun main(args: Array<String>) {
    val demoTest = DemoTest()
    println(demoTest.valStrOne)
    println(demoTest.valStrTwo)
    println(demoTest.valStrThree)
    println(demoTest.valStrFour)
    demoTest.valStrFour = "I am Icon man"
    println(demoTest.valStrFour)
}

result:

I am val string one
I am val string two
I am val string three
I am val string four
I am Icon man
4.2.4.2、Getter()函數(shù)慎用super關鍵字

在這里值得注意的是,在實際的項目中在重寫屬性的時候不用get() = super.xxx,因為這樣的話,不管你是否重新為該屬性賦了新值,還是支持setter(),在使用的時候都調(diào)用的是基類中的屬性值。

eg:

open class InheritClass {
    open val valStrFive = "I am val string five"
}

class DemoTest : InheritClass() {
    override var valStrFive: String = "cba"
        get() = super.valStrFive
        set(value){field = value}
}

fun main(args: Array<String>) {
    val demoTest = DemoTest()
    println(demoTest.valStrFive)
    demoTest.valStrFive
    println(demoTest.valStrFive)
}

result:

I am val string five
I am val string five
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容