Kotlin专题「十八」:object(对象表达式和对象声明)

前言:现实会告诉你,不努力就会被生活踩死,无需找什么借口,一无所有就是拼的理由。

一、概述

  有时候我们需要创建一个对某些类稍加修改的对象,而不必显式地成为它声明一个新的子类。Kotlin 用对象表达式和对象声明来处理这种情况。使用 object 关键字能实现类似 Java 中的匿名内部类功能。Kotlin 提供 object 关键字的用意就是,在生成在一个对当前类进行轻微修改的类对象,且不需要声明一个新的子类的时候使用。

下面将从 object 作为表达式和作为声明两部分阐述一下 object 的用法。

二、对象表达式

创建一个匿名类的对象,object 实际上是作为表达式存在,产生的是 object 类型的匿名对象:

    interface OnClickListener {
        fun onClick(v: View?)
    }

    private fun setOnClickListener(listener: OnClickListener?) {}

	//调用
	fun test() {
	   setOnClickListener(object : OnClickListener {//匿名内部类
            override fun onClick(v: View?) {
            }
        })
	}

如果超类有一个构造函数,生成的匿名对象则必须将适当的构造函数参数传递给它。如果有多个超类型可以在冒号后面指定一个逗号分隔:

    open class People(name: String) {}//父类有构造函数
    
    fun test() {
        val people: OnClickListener = object : People("Android"), OnClickListener {
            override fun onClick(v: View?) {
                //TODO
            }
        }
    }

注意:如果父类构造方法有入参,在生成匿名对象时必须按照父类构造方法的定义传参;如果生成的匿名函数有多个父实现,在声明 object 时必须指定类型,如 val people: OnClickListener

如果在任何情况下,我们需要的仅仅是一个对象,并没有特别的超类,可以简单为:

    fun test() {
        val add = object {//没有任何超类,只是作为一个对象存在
            val numA: Int = 1
            val numB: Int = 2
        }

         println("numA + numB = ${add.numA + add.numB}") //打印数据:3
    }

匿名对象只能在本地和私有声明中作为类型使用。如果使用匿名对象作为公共函数的返回类型或公共属性的类型,该函数或属性的实际类型将是匿名对象声明的超类型,如果没有声明任何超类型,则为 Any 。添加到匿名对象中的成员将不可访问。

    //私有函数,返回类型为匿名对象类型
    private fun privateFoo() = object {
        val str: String = "private"
    }

    //公有函数,返回对象为超类型,如果没有超类则为Any
    public fun publicFoo() = object {
        val str: String = "public"
    }
    
    fun test() {
        val str1 = privateFoo().str // compile success,private修饰的匿名 object 返回的类型是 object 类型
        val str2 = publicFoo().str // compile error,public 修饰的匿名 object 返回的类型是其超类,如果没有超类则是 Any,而 Any 类中没有 str 字段
    }

匿名 object 作为函数或属性存在时有一定的限制:

  • 匿名 object 为 private 修饰时,其表达式返回的类型就是 object 类型;
  • 匿名 object 为 public 修饰时,其表达式返回的类型是其超类的类型,如果没有超类则为 Kotlin 中顶层的 Any类型。

Kotlin 允许在匿名类对象内部访问外部的成员,与 Java 不同的是此时外部成员不必再声明为 final (Kotlin 的val)。

    var name: String = "Kotlin"
    
    fun test() {
        setOnClickListener(object : OnClickListener {
            override fun onClick(v: View?) {
                println("匿名object对象访问外部成员:name = $name")//打印:Kotlin
            }
        })
    }

此时使用外部的 name 属性,也不需要声明为不可变。

三、对象声明

单例可能在一些情况下有用,Kotlin(在Scala之后)使声明单例变得简单。在 Kotlin 中,可以不需要像 Java 那样自己去实现单例,而是提供关键字来保证单例,这个关键字就是 object。当 object 修饰一个 class 时,这个类就只有一个对象。

    object DataProviderManager {//使用object来声明一个单例对象
        fun registerDataProvider(str: String) {
            //TODO
        }
    }

这称为对象声明,它总是在 object 关键字后面有一个名称。就像变量声明一样,对象声明也不是表达式,不能在赋值语句的右侧使用。

对象声明的初始化是线程安全的,并在第一次访问时完成。不需要像 Java 那样考虑多线程的情况。

如果要引用对象,我们直接使用它的名称:

	DataProviderManager.registerDataProvider("")

这样的对象可以有超类型:

    abstract class MyAdapter {
        open fun mouseClicked() { }//open 修饰,子类可重写
    }
    
    fun test() {
        object : MyAdapter() {
            override fun mouseClicked() { }
        }
    }

注意:对象声明不能是本地的(即直接嵌套在函数中),但可以嵌套到其他对象声明或非内部类中。

object 作为声明时需要注意一下几点:

  • object 作为声明时没有右值,即无法作为表达式那样赋值给其他变量;
  • object 作为声明时无法声明本地变量,但可以作为类成员存在;
  • object 作为声明时可以继承超类,即单例可以有超类。

伴生对象

类内的对象声明可以用 companion 关键字进行标记:

    class Student {
        companion object newInstance {//伴生对象
            fun create(): People = People("ALL")
        }
    }

可以简单地使用类名作为限定符来调用伴生对象的成员:

val instance = Student.create()

伴生对象的名称可以省略,在这种情况下,将使用名称 Companion 来调用:

    class Student {
        companion object { }
    }

	val companion = Student.Companion

类自身使用的名称(不作为另一名称的限定符)作为类的伴生对象的引用(无论命名与否)。即无论伴生对象有没有命名,只要使用包含伴生对象的类名进行赋值时,此值实际上就是该类所持有的其内部伴生对象的引用地址:

    class Student {
        companion object Named{ }
    }
    
  	val student1 = Student
  	
    class Student {
        companion object { }
    }
    
	val student2 = Student

注意:即使伴生对象的成员在其他语言中看起来像静态成员,在运行时它们仍然是真实对象的实例成员,例如,可以实现接口:

    interface Factory<T> {
        fun create(): T
    }

    class MyClass {
        companion object : Factory<MyClass> {//实现接口
            override fun create(): MyClass = MyClass()
        }
    }

	val factory: Factory<MyClass> = MyClass

但是,在 JVM 上,如果使用 @JvmStatic 注释,可以将伴生对象的成员生成为真正的静态方法和字段,即使用 @JvmStatic 和 Java 中的 static 成员一样。比如,我们使用的 main 方法都是使用 @JvmStatic 注释的,因为 Java 的 main 方法必须是 static 的,而伴生对象的成员不是静态的,所以需要额外添加 static 修饰符告诉编译器,这是一个静态方法。

对象表达式和声明之间的语义差异

对象表达式和对象声明之间有一个重要的语义区别:

  • 在使用对象表达式的地方,立即执行(并初始化)对象表达式;
  • 第一次访问时,对象声明被惰性地初始化;
  • 在加载(解析)相应的类时初始化一个伴生对象,与 Java 静态初始化器一样。

源码地址:https://github.com/FollowExcellence/KotlinDemo-master

点关注,不迷路


好了各位,以上就是这篇文章的全部内容了,能看到这里的人呀,都是人才

我是suming,感谢各位的支持和认可,您的点赞就是我创作的最大动力,我们下篇文章见!

如果本篇博客有任何错误,请批评指教,不胜感激 !

要想成为一个优秀的安卓开发者,这里有必须要掌握的知识架构,一步一步朝着自己的梦想前进!Keep Moving!

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值