kotlin学习总结——object关键字数据类密封类嵌套类和内部类

Posted AC_Jobim

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了kotlin学习总结——object关键字数据类密封类嵌套类和内部类相关的知识,希望对你有一定的参考价值。

一、object关键字

object的三种使用场景:

  • 对象声明(Object Declaration)
  • 伴生对象(Companion Object)
  • 对象表达式(Object Expression)

1.1 对象声明

对象声明是将类的声明和定义该类的单例对象结合在一起。当 object 修饰一个 class 时,这个类就只有一个对象,称为对象声明。(kotlin中使用object关键字来保证单例)

对象声明的初始化过程是线程安全的并且在首次访问时进行。

//使用object定义的类为单例类
object ApplicationConfig {
    init {
        println("ApplicationConfig load...")
    }
    fun doSomething(){
        println("doSomething")
    }
}

fun main() {
    //如需引用该对象,我们直接使用其名称即可:
    ApplicationConfig.doSomething()
}

注意点:

  1. 尽管和普通类的声明一样,可以包含属性、方法、初始化代码块以及可以继承其他类或者实现某个接口,但是它不能包含构造器(包括主构造器以及次级构造器)
  2. 它也可以定义在一个类的内部
class ObjectOuter {
    object Inner{
        fun method(){
            println("声明在类的内部")
        }
    }
}
fun main() {
    ObjectOuter.Inner.method()//声明在类的内部
}

1.2 伴生对象

在Kotlin中是没有static关键字的,也就是意味着没有了静态方法和静态成员。那么在kotlin中如果要想表示这种概念,取而代之的是包级别函数(package-level function)和伴生对象

  1. 语法形式:

    class A{
        companion object 伴生对象名(可以省略){
            //define method and field here
        }
    }
    
  2. 代码实例

    class ConfigMap{
        //只有初始化ConfigMap类或调用load函数时,伴生对象的内容才会载入
        //而且无论实例化ConfigMap类多少次,这个伴生对象始终只有一个实例存在
        companion object{
            private const val PATH = "xxxx"
            fun load() = File(PATH).readBytes()
        }
    }
    fun main() {
        //方式一
        ConfigMap.Factory.load()
        println(ConfigMap.Factory.PATH)
    
        //方式二(推荐方式),可通过只使用类名作为限定符来调用
        ConfigMap.load()
        println(ConfigMap.PATH)
    }
    

    注意:

    1. 定义时如果省略了伴生对象名,那么编译器会为其提供默认的名字Companion
    2. 调用时伴生对象名是可以省略的
  3. 一个类的伴生对象只能有一个

1.3 对象表达式

Java中的匿名内部类:

  • Java中如果在匿名内部类中新添加了一些属性和方法,那么在外界是无法调用的
  • 一个匿名内部类肯定是实现了一个接口或者是继承一个类,并且只能是一个,用数学术语说是“有且只有一个”
interface Contents {
    void absMethod();
}
public class Hello {

    public Contents contents() {
        return new Contents() {
            private int i = 1;

            public int value() {
                return i;
            }

            @Override
            public void absMethod() {
                System.out.println("method invoked...");
            }
        };
    }

    public static void main(String[] args) {

        Hello hello = new Hello();
        hello.contents().absMethod();    //打印method invoked...
        hello.contents().value();  //报错,Cannot resolve method 'value' in 'Contents'
    }
}

Kotlin中的对象表达式:

有时候,我们需要创建一个对某个类做了轻微改动的类的对象,而不用为之显式声明新的子类。 Kotlin 用对象表达式和对象声明处理这种情况。

  1. 语法形式:

    object [ : 接口1,接口2,类型1, 类型2]{}    //中括号中的可省略
    
  2. 实现多个接口和类:

    open class Player{
        open fun load() = "loading nothing"
    }
    
    interface  A{
        fun a(): String
    }
    
    fun main() {
        //对象表达式,实现多个接口和类
        val p = object : Player(),A{
            override fun load() = "anonymous class load..."
    
            override fun a(): String {
                return "接口A的方法"
            }
        }
        println(p.load())
        println(p.a())
    }
    
  3. 不实现任何接口和类,并且在匿名内部类中添加方法:

    fun main(args: Array<String>) {
    
        val obj = object  {
            fun a() {
                println("a invoked")
            }
        }
    
        obj.a()  //打印:a invoked
    }
    
  4. 匿名对象只有定义成局部变量和private成员变量时,才能体现它的真实类型。如果你是将匿名对象作为public函数的返回值或者是public属性时,你只能将它看做是它的父类,当然你不指定任何类型时就当做Any看待。这时,你在匿名对象中添加的属性和方法是不能够被访问的。

    class C {
        // 私有函数,所以其返回类型是匿名对象类型
        private fun foo() = object {
            val x: String = "x"
        }
    
        // 公有函数,所以其返回类型是 Any
        fun publicFoo() = object {
            val x: String = "x"
        }
    
        fun bar() {
            val local = object {
                fun method(){
                    println("local")
                }
            }
            
            local.method()  //编译通过
            val x1 = foo().x        // 没问题
            val x2 = publicFoo().x  // 错误:未能解析的引用“x”
        }
    }
    
  5. Kotlin 允许在匿名类对象内部访问外部的成员,与 Java 不同的是此时外部成员不必再声明为 final (Kotlin 的val)

    var name: String = "Kotlin"
    
    fun test() {
        object {
            fun test(){
                println("引用外部的属性 $name")
            }
        }
    }
    

二、数据类、密封类、嵌套类、内部类

2.1 数据类

数据类即JavaBean ,当我们要定义一个数据模型时使用

1、声明数据类的关键字为:data

// 定义一个名为Person的数据类
data class Preson(var name : String,val sex : Int, var age : Int)

2、编辑器为我们做的事情:

当我们声明一个数据类时,编辑器自动为这个类做了一些事情.

  • 生成equals()函数与hasCode()函数
  • 生成toString()函数,由类名(参数1 = 值1,参数2 = 值2,....)构成
  • 由所定义的属性自动生成component1()、component2()、...、componentN()函数,其对应于属性的声明顺序。
  • copy()函数。

3、使用数据类必须满足的条件:

  • 主构造函数需要至少有一个参数
  • 主构造函数的所有参数需要标记为 val 或 var;
  • 数据类不能是抽象、开放、密封或者内部的;
data class Student(var name : String, val age:Int) {}

fun main() {
    val student1 = Student("1", 1)
    val student2 = Student("1", 1)
    println(student1)//Student(name=1, age=1),重写了toString()方法
    println(student1 == student2)//true,重写了equals方法,==通过equals()函数进行比较
}

4、copy()
当要复制一个对象,只改变一些属性,但其余不变,copy()

data class User(val name: String, val age: Int)

fun main(args: Array<String>) {
    val u = User(name = "lioil", age = 1)
    val u1 = u.copy("win")   //传递第一参数,第二参数默认
    val u2 = u.copy("win",2) //传递所有参数
    val u3 = u.copy(age = 3) //命名参数,传递指定参数
    println(u1)//User(name=win, age=1)
    println(u2)//User(name=win, age=2)
    println(u3)//User(name=lioil, age=3)
}

2.2 密封类

作用:用来表示受限的类继承结构。

要声明一个密封类,需要在类名前面添加 sealed 修饰符。虽然密封类也可以有子类,但是所有子类都必须在与密封类自身相同的文件中声明。(在 Kotlin 1.1 之前, 该规则更加严格:子类必须嵌套在密封类声明的内部)。

sealed class SealedExpr{
	data class Person(val num1 : Int, val num2 : Int) : SealedExpr()
	
	object Add : SealedExpr()   // 单例模式
	object Minus : SealedExpr() // 单例模式
}

注意:

一个密封类是自身抽象的,它不能直接实例化并可以有抽象(abstract)成员。

密封类不允许有非private构造函数(其构造函数默认为 private)。

请注意,扩展密封类子类的类(间接继承者)可以放在任何位置,而无需在同一个文件中。

使用密封类的关键好处在于使用 when 表达式 的时候,如果能够验证语句覆盖了所有情况,就不需要为该语句再添加一个 else 子句了。当然,这只有当你用 when 作为表达式(使用结果)而不是作为语句时才有用。

package six

//密封类
sealed class LicenseStatus2 {
    object UnQualified: LicenseStatus2()
    object Learning: LicenseStatus2()
    class Qualified(val licenseId: String): LicenseStatus2()
}

class Driver2(var status: LicenseStatus2){
    fun checkLicense(): String{
        //不用使用else语句,且编译器会帮你检查代码处理是否有遗漏
        return when (status){
            is LicenseStatus2.UnQualified -> "没资格"
            is LicenseStatus2.Learning -> "在学"
            is LicenseStatus2.Qualified -> "有资格,驾驶证编号:"+
                    "${(this.status as LicenseStatus2.Qualified).licenseId}"
        }
    }
}

fun main() {
    println(Driver2(LicenseStatus2.Qualified("1245")).checkLicense())//有资格,驾驶证编号:1245
}

密封类的原理:实际上就是在一个私有的抽象类内部再声明多个 Java 中嵌套类也就是 static class .

2.3 嵌套类

  • 调用嵌套类的属性或方法的格式为:外部类.嵌套类().嵌套类方法/属性。在调用的时候嵌套类是需要实例化的
  • 嵌套类不能使用外部类的属性和成员

例:

class Other{           // 外部类
    val numOuther = 1

    class Nested {      // 嵌套类
        fun init(){
            println("执行了init方法")
        }
    }
}

fun main(args: Array<String>) {
	// 调用格式为:外部类.嵌套类().嵌套类方法/属性
    Other.Nested().init()      //执行了init方法
}

2.4 内部类

声明一个内部类使用inner关键字。
声明格式:inner class 类名(参数){}

  • 调用内部类的属性或方法的格式为:外部类().内部类().内部类方法/属性。在调用的时候嵌套类是需要实例化的
  • 标记为 inner 的嵌套类能够访问其外部类的成员。 内部类会带有一个对外部类的对象的引用

例:

class Other{            // 外部类
    val numOther = 1

    inner class InnerClass{     // 嵌套内部类
        val name = "InnerClass"
        fun init(){
            println("我是内部类,访问外部类的成员 $numOther")//结果:我是内部类,访问外部类的成员 1
        }
    }
}

fun main(args: Array<String>) {
    Other().InnerClass().init()  // 调用格式为:外部类().内部类().内部类方法/属性
}

以上是关于kotlin学习总结——object关键字数据类密封类嵌套类和内部类的主要内容,如果未能解决你的问题,请参考以下文章

Kotlin学习手记——单例内部类数据类枚举类密封类内联类

Kotlin中继承类型转换Any超类object关键字详解

Kotlin中继承类型转换Any超类object关键字详解

Kotlin学习与实践 数据类类委托“object”

区别Kotlin中的object和companion object关键字

Kotlin 关键字 object 的用法