原创

Kotlin中的嵌套类和内部类

把一个类放在另一个类的内部定义,定义在其他类内部的类就被称为嵌套类,包含嵌套类的类被称为外部类把一个类放在另一个类的内部定义,定义在其他类内部的类就被称为嵌套类,包含嵌套类的类被为部。

  • 嵌套类:只要将一个类放在另一个类中定义,这个类就变成了嵌套类,相当于Java中static修饰的静态内部类。
  • 内部类:使用inner修饰的嵌套类叫内部类,相当于Java中无static修饰的非静态内部类。

嵌套类的主要作用如下

  • 嵌套类提供了更好的封装,可以把嵌套类隐藏在外部类之内,不允许同一个包中的其他类访问该类。
  • 内部类成员可以直接访问外部类的私有数据,因为内部类被当成其外部类成员,同一个类的成员之间可以互相访问。

嵌套类定义的语法格式如下:

class  OuterClass{
    //此处可以定义嵌套类、内部类
}

大部分时候,嵌套类(内部类)都被作为成员嵌套类(内部类)定义,而不是作为局部嵌套类。成员嵌套类是一种与属性、方法、构造器和初始化块相似的成员;局部嵌套类则不是类成员。

嵌套类无须使用任何特殊修饰符,它相当于Java的静态内部类;内部类需要使用inner修饰,它相当于java的非静态内部类。

一、内部类

内部类相当于外部类的实例成员,所以它可以直接访问外部类的所有成员。

//通过主构造器为外部类定义属性
class Cow(var weight: Double = 0.0) {
    //定义一个内部类
    //通过主构造器为内部类定义属性
    private inner class CowLeg(var length: Double = 0.0, var color: String = "") {
        //内部类的方法
        fun info() {
            println("这只牛腿的颜色是:${color},高:${length}")
            //直接访问外部类的private修饰的foo()方法
            foo()
        }
    }

    fun test() {
        val cl = CowLeg(1.12, "红色")
        cl.info()
    }

    private fun foo() {
        println("Cow的foo方法")
    }
}

fun main(args: Array<String>) {
    val cow = Cow(378.9)
    cow.test()
}

输出结果:

这只牛腿的颜色是:红色,高:1.12
Cow的foo方法

如果外部类属性、内部类属性与内部类中方法的局部变量同名,则可通过使用this、带标签的this进行限定来区分。

package `0704`

class DiscernVariable {
    //隐式标签@DiscernVariable
    private val prop = "外部类的属性"

    inner class InClass {
        //隐式标签@InClass
        private val prop = "内部类的属性"

        fun info() {
            val prop = "局部属性"
            //通过外部类类名.this.varName访问外部类的属性
            println("外部类的属性值:${this@DiscernVariable.prop}")
            //通过this.varName访问内部类的属性
            println("内部类的属性值:${this.prop}")
            //直接访问局部变量
            println("局部变量的值:${prop}")
        }
    }

    fun test() {
        val ic = InClass()
        ic.info()
    }
}

fun main(args: Array<String>) {
    DiscernVariable().test()
}

输出结果:

外部类的属性值:外部类的属性
内部类的属性值:内部类的属性
局部变量的值:局部属性

Kotlin关于this的处理规则如下:

  • 在类的方法或属性中,this代表调用该方法或属性的对象;
  • 在类的构造器中,this代表该构造器即将返回的对象;
  • 在扩展函数或带接收者的函数字面值中,this表示点左边的“接收者”;
  • 如果this没有限定符,那么它优先代表包含该this的最内层的接收者,并且会自动向外搜索。如果要让this明确引用特定的接收者,则可使用标签限定符。
package `0704`

class A {
    //隐式标签@A
    inner class B {
        //隐式标签@B
        //为Int扩展foo()方法
        fun Int.foo() {//隐式标签@foo
            val a = this@A//A的this
            val b = this@B//B的this
            val c = this//不带标签的this,默认代表该方法所属对象:Int对象
            val c1 = this@foo//显示指定@foo标签,与c代表的对象相同
            println(a)
            println(b)
            println(c)
            println(c1)
            //为String扩展funLit()方法
            val funLit = lambda@ fun String.() {
                val d = this//不带标签的this,默认代表该方法所属对象:String对象
                val d1 = this@lambda//显式指定@lambda标签,与d代表的对象相同
                println(d)
                println(d1)
            }
            "kotlin".funLit()
            val funLit2 = {
                val e = this//this代表foo()方法的接收者:Int对象
                val e1 = this@foo//显式指定@foo标签,与e代表的对象相同
                println("foo()方法中Lambda表达式的this:" + e)
                println("e1的this:" + e1)
            }
            funLit2()
        }

        fun testB() {
            2.foo()
        }
    }

    fun testA() {
        var bObj = B()
        println("程序创建的B对象:${bObj}")
        bObj.testB()
    }
}

fun main(args: Array<String>) {
    var aObj = A()
    println("程序创建的A对象:${aObj}")
    aObj.testA()
}

输出结果:

程序创建的A对象:0704.A@256216b3
程序创建的B对象:0704.A$B@d7b1517
0704.A@256216b3
0704.A$B@d7b1517
2
2
kotlin
kotlin
foo()方法中Lambda表达式的this:2
e1的this:2

内部类的成员只在内部类范围内是可知的,并不能被外部类直接使用。如果外部类需要访问内部类的成员,则必须显式创建内部类对象来调用访问其成员。

package `0704`

class Outer {
    private val outProp = 9

    inner class Inner {
        val inProp = 5
        fun acessOuterProp() {
            //内部类可以直接访问外部类的private属性
            println("外部类的outProp值:${outProp}")
        }
    }

    fun accessInnerProp() {
        //如需访问内部类的属性,必须显式创建内部类对象
        println("内部类的inProp值:${Inner().inProp}")
    }
}

fun main(args: Array<String>) {
    val ot = Outer()
    ot.accessInnerProp()
}

输出结果:

内部类的inProp值:5

二、嵌套类

嵌套类直接属于外部类的类本身,而不是外部类实例相关。

Java语法有一条规则:静态成员不可访问非静态成员。

Kotlin类中的成员除嵌套类之外,全部都是非静态成员,嵌套类不可访问外部类的其他任何成员,只能访问另一个嵌套类。

package `0705`

class NestedClassTest {
    var prop1 = 5
    fun test() {
        println("外部类的test()方法")
    }

    //没有inner修饰符,是嵌套类
    class NestedClass {
        fun accessOuterMember() {
            //访问另一个嵌套类
            val a = A()
            //下面注释掉的代码是错误的
//            test()
//            println(prop1)
        }
    }

    class A
}

嵌套类唯一可访问的是外部类的其他嵌套类。

嵌套类相当于外部类的静态成员,因此外部类的所有方法、属性、初始化块都可以使用嵌套类来定义变量、创建对象等。

外部类不能直接访问嵌套类的成员,但可以使用嵌套类的对象作为调用者来访问嵌套类的成员。

class AccessNestedClass {
    class NestedClass {
        var prop = 9
    }

    fun accessNestedProp() {
        //通过对象访问嵌套类的成员
        println(NestedClass().prop)
    }
}

Kotlin还允许在接口中定义嵌套类,但不允许在接口中定义内部类。

三、在外部类以外使用内部类

定义类的主要作用就是定义变量、创建对象和派生子类。

因为内部类的对象必须寄生在外部类的对象中,因此在创建内部类对象之前,必须先创建其外部类对象。

package `0705`

class Out {
    //定义一个内部类,不适用访问控制符,默认是public
    inner class In(msg: String) {
        init {
            println(msg)
        }
    }
}

fun main(args: Array<String>) {
    var oi :Out.In=Out().In("测试信息")
}

四、在外部类以外使用嵌套类

因为嵌套类是属于外部类的类本身的,因此创建嵌套类对象时无须创建外部类对象。

package `0705`

class NestedOut {
    //定义一个嵌套类,不适用访问控制符,默认是public
    open class Nested {
        init {
            println("嵌套类的构造器")
        }
    }
}

fun main(args: Array<String>) {
    val nested: NestedOut.Nested = NestedOut.Nested()
}

不管是内部类还是嵌套类,其声明变量的语法完全一样。区别只是在创建对象时,嵌套类只需使用外部类即可调用构造器,而内部类必须使用外部类对象来调用构造器。

五、局部嵌套类

如果把一个嵌套类放在方法或函数中定义,就是一个局部嵌套类,局部嵌套类仅在该方法或函数中有效。

如果需要用局部嵌套类定义变量、创建实例或派生子类,都只能在局部嵌套类所在的方法(或函数)内进行。

package `0705`

class LocalNestedClass {
    fun info() {
        //定义局部嵌套类
        open class NestedBase(var a: Int = 0) {}

        //定义局部嵌套类的子类
        class NestedSub(var b: Int = 0) : NestedBase()

        //创建局部嵌套类的对象
        val ns = NestedSub()
        ns.a = 5
        ns.b = 8
        println("NestedSub对象的a和b属性是:${ns.a},${ns.b}")
    }
}

fun main(args: Array<String>) {
    LocalNestedClass().info()
}

输出结果:

NestedSub对象的a和b属性是:5,8

六、匿名内部类

Kotlin抛弃了匿名内部类的功能,而提供了一个更强大的语法:对象表达式

如果对象是函数式接口的实例,则可使用带接口类型前缀的Lambda表达式创建它。


fun main(args: Array<String>) {
    //使用Lambda表达式创建Runnable实例
    var t = Runnable {
        for (i in 0..100) {
            println("${Thread.currentThread().name},i:${i}")
        }
    }
    //启动新线程
    Thread(t).start()
    //主线程的循环
    for (i in 0..100) {
        println("${Thread.currentThread().name},i:${i}")
    }
}

学海无涯苦作舟

我的微信公众号.jpg

基本语法
  • 作者:HunterArley (联系作者)
  • 发表时间:2019-11-26 10:27
  • 版权声明:本网站部分内容转载于合作站点或其他站点,但都会注明作/译者和原出处。如有不妥之处,敬请指出。
  • 公众号转载:请在文末添加作者公众号二维码