# 对象表达式与对象声明
[TOC]
有时候,我们**需要创建一个对某个类做了轻微改动的类的对象,而不用为之显式声明新的子类。Kotlin 用对象表达式和对象声明处理这种情况**。
* 对象表达式(object expressions),
* 对象声明(object declarations),
* 伴生对象(companion object)
## 对象表达式(object expressions)
要创建一个继承自某个(或某些)类型的匿名类的对象,我们会这么写:
```kotlin
window.addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) { /*……*/ }
override fun mouseEntered(e: MouseEvent) { /*……*/ }
})
```
### 定义:
如果超类型(父类,比如接口,抽象类,可继承的一般类)有一个构造函数,则必须传递适当的构造函数参数给它。多个超类型可以由跟在冒号后面的逗号分隔的列表指定:
```kotlin
open class A(x: Int) {
public open val y: Int = x
}
interface B { /*……*/ }
// A(1) 子类必须传递参数给父类的构造函数
val ab: A = object : A(1), B {//这就是对象表达式
override val y = 15
}
```
任何时候,如果我们**只需要“一个对象而已”,并不需要特殊超类型**,那么我们可以简单地写:
```kotlin
fun foo() {
val adHoc = object {
var x: Int = 0
var y: Int = 0
}
print(adHoc.x + adHoc.y)
}
```
### 作用域:访问权限
#### 外部访问内部
>[success]请注意,**匿名对象可以用作只在本地和私有作用域中声明的类型**。如果你使用匿名对象作为公有函数的返回类型或者用作公有属性的类型,那么该函数或属性的实际类型会是匿名对象声明的超类型(父类型),如果你没有声明任何超类型,就会是 `Any`(没有父类,就是Any)。无法访问匿名对象的成员(在匿名对象中添加的成员将无法访问)。
```kotlin
class C {
// 私有函数,所以其返回类型是匿名对象类型
private fun foo() = object {
val x: String = "x"
}
// 默认是公有函数,所以其返回类型是 Any
fun publicFoo() = object {
val x: String = "x"
}
fun bar() {
val x1 = foo().x // 正确: 类型是object{..},可以访问x
val x2 = publicFoo().x // 错误: 类型是Any,无法访问x
}
}
```
#### 内部访问外部
像Java匿名内部类一样,对象表达式可访问<包含它的作用域>
对象表达式中的代码可以访问来自包含它的作用域的变量。
```kotlin
fun countClicks(window: JComponent) {
var clickCount = 0
var enterCount = 0
window.addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {
clickCount++
}
override fun mouseEntered(e: MouseEvent) {
enterCount++
}
})
// ……
}
```
## 对象声明/单例模式(object declarations)
单例模式在一些场景中很有用,而 Kotlin(继 Scala 之后)使单例声明变得很容易:
```kotlin
//定义单例对象(对象声明)
object DataProviderManager {
fun registerDataProvider(provider: DataProvider) {
// ……
}
val allDataProviders: Collection<DataProvider>
get() = // ……
}
```
这称为**对象声明**。并且它总是在 **object**关键字后跟一个名称。
* 就像变量声明一样,**对象声明不是一个表达式,不能用在赋值语句的右边**。
* **对象声明的初始化过程是线程安全的**。
* 如需引用该对象,我们直接使用其名称即可:
```kotlin
//使用单例对象
DataProviderManager.allDataProviders
DataProviderManager.registerDataProvider(……)
```
这些对象可以有超类型:
```kotlin
//单例对象继承超类(父类)
object DefaultListener : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) { …… }
override fun mouseEntered(e: MouseEvent) { …… }
}
```
>[warning]**注意**:对象声明不能在局部作用域(即直接嵌套在函数内部),但是它们可以嵌套到其他对象声明或非内部类中。
## 伴生对象(companion object)
* 1、类内部的伴生对象声明可以用 **companion**关键字标记,类似于Java类的static静态成员:
```kotlin
class MyClass {
companion object Factory {//Factory就是MyClass的伴生对象
fun create(): MyClass = MyClass()
}
}
```
~~~
伴随对象的调用不用创建包含伴随对象的实例,调用方式有两种,
一种是类名.伴随对象.XX,另外一种方式是类名.xx
~~~
* 2、该伴生对象的成员可通过只使用类名作为限定符来调用:
```kotlin
val instance = MyClass.create()
```
* 3、可以省略伴生对象的名称,在这种情况下将使用名称 `Companion`:
```kotlin
class MyClass {
//可省略伴生对象名
companion object { }
}
val x = MyClass.Companion
```
其自身所用的类的名称(不是另一个名称的限定符)可用作对该类的伴生对象(无论是否命名)的引用:
```kotlin
class MyClass1 {
companion object Named { }
}
val x = MyClass1
//可省略伴生对象名
class MyClass2 {
companion object { }
}
val y = MyClass2
```
* 一个类只有一个伴生对象,伴生对象所在的类被加载,伴生对象被初始化,与Java静态成员一样。
示例:
~~~
class MyClass {
companion object Obj {
val a = 1
fun crt(): MyClass = MyClass()
}
val b = 2
}
//直接类名调用伴生对象(类似于Java的静态成员)
fun main(args: Array<String>) {
println(MyClass.a) //输出1
println(MyClass.Obj.a) //输出1
println(MyClass.crt().b) //输出2
println(MyClass.Obj.crt().b) //输出2
}
~~~
>[danger]请注意,即使伴生对象的成员看起来像其他语言的静态成员,**在运行时他们仍然是真实对象的实例成员**,而且,例如还可以实现接口:
```kotlin
interface Factory<T> {
fun create(): T
}
class MyClass {
companion object : Factory<MyClass> {
override fun create(): MyClass = MyClass()
}
}
val f: Factory<MyClass> = MyClass
```
当然,在 JVM 平台,如果使用 `@JvmStatic` 注解,你可以将伴生对象的成员生成为真正的静态方法和字段。更详细信息请参见[Java 互操作性](https://www.kancloud.cn/alex_wsc/android_kotlin/1318356)一节。
### 对象表达式和对象声明之间的语义差异
对象表达式和对象声明之间有一个重要的语义差别:
* 对象表达式是在使用他们的地方**立即**执行(及初始化)的;
* 对象声明是在第一次被访问到时**延迟**初始化的;
* 伴生对象的初始化是在相应的类被加载(解析)时,与 Java 静态初始化器的语义相匹配。
- 前言
- Kotlin简介
- IntelliJ IDEA技巧总结
- idea设置类注释和方法注释模板
- 像Android Studion一样创建工程
- Gradle
- Gradle入门
- Gradle进阶
- 使用Gradle创建一个Kotlin工程
- 环境搭建
- Androidstudio平台搭建
- Eclipse的Kotlin环境配置
- 使用IntelliJ IDEA
- Kotlin学习路线
- Kotlin官方中文版文档教程
- 概述
- kotlin用于服务器端开发
- kotlin用于Android开发
- kotlin用于JavaScript开发
- kotlin用于原生开发
- Kotlin 用于数据科学
- 协程
- 多平台
- 新特性
- 1.1的新特性
- 1.2的新特性
- 1.3的新特性
- 开始
- 基本语法
- 习惯用法
- 编码规范
- 基础
- 基本类型
- 包与导入
- 控制流
- 返回与跳转
- 类与对象
- 类与继承
- 属性与字段
- 接口
- 可见性修饰符
- 扩展
- 数据类
- 密封类
- 泛型
- 嵌套类
- 枚举类
- 对象
- 类型别名
- 内嵌类
- 委托
- 委托属性
- 函数与Lambda表达式
- 函数
- Lambda表达式
- 内联函数
- 集合
- 集合概述
- 构造集合
- 迭代器
- 区间与数列
- 序列
- 操作概述
- 转换
- 过滤
- 加减操作符
- 分组
- 取集合的一部分
- 取单个元素
- 排序
- 聚合操作
- 集合写操作
- List相关操作
- Set相关操作
- Map相关操作
- 多平台程序设计
- 平台相关声明
- 以Gradle创建
- 更多语言结构
- 解构声明
- 类型检测与转换
- This表达式
- 相等性
- 操作符重载
- 空安全
- 异常
- 注解
- 反射
- 作用域函数
- 类型安全的构造器
- Opt-in Requirements
- 核心库
- 标准库
- kotlin.test
- 参考
- 关键字与操作符
- 语法
- 编码风格约定
- Java互操作
- Kotlin中调用Java
- Java中调用Kotlin
- JavaScript
- 动态类型
- kotlin中调用JavaScript
- JavaScript中调用kotlin
- JavaScript模块
- JavaScript反射
- JavaScript DCE
- 原生
- 并发
- 不可变性
- kotlin库
- 平台库
- 与C语言互操作
- 与Object-C及Swift互操作
- CocoaPods集成
- Gradle插件
- 调试
- FAQ
- 协程
- 协程指南
- 基础
- 取消与超时
- 组合挂起函数
- 协程上下文与调度器
- 异步流
- 通道
- 异常处理与监督
- 共享的可变状态与并发
- Select表达式(实验性)
- 工具
- 编写kotlin代码文档
- 使用Kapt
- 使用Gradle
- 使用Maven
- 使用Ant
- Kotlin与OSGI
- 编译器插件
- 编码规范
- 演进
- kotlin语言演进
- 不同组件的稳定性
- kotlin1.3的兼容性指南
- 常见问题
- FAQ
- 与Java比较
- 与Scala比较(官方已删除)
- Google开发者官网简介
- Kotlin and Android
- Get Started with Kotlin on Android
- Kotlin on Android FAQ
- Android KTX
- Resources to Learn Kotlin
- Kotlin样品
- Kotlin零基础到进阶
- 第一阶段兴趣入门
- kotlin简介和学习方法
- 数据类型和类型系统
- 入门
- 分类
- val和var
- 二进制基础
- 基础
- 基本语法
- 包
- 示例
- 编码规范
- 代码注释
- 异常
- 根类型“Any”
- Any? 可空类型
- 可空性的实现原理
- kotlin.Unit类型
- kotlin.Nothing类型
- 基本数据类型
- 数值类型
- 布尔类型
- 字符型
- 位运算符
- 变量和常量
- 语法和运算符
- 关键字
- 硬关键字
- 软关键字
- 修饰符关键字
- 特殊标识符
- 操作符和特殊符号
- 算术运算符
- 赋值运算符
- 比较运算符
- 逻辑运算符
- this关键字
- super关键字
- 操作符重载
- 一元操作符
- 二元操作符
- 字符串
- 字符串介绍和属性
- 字符串常见方法操作
- 字符串模板
- 数组
- 数组介绍创建及遍历
- 数组常见方法和属性
- 数组变化以及下标越界问题
- 原生数组类型
- 区间
- 正向区间
- 逆向区间
- 步长
- 类型检测与类型转换
- is、!is、as、as-运算符
- 空安全
- 可空类型变量
- 安全调用符
- 非空断言
- Elvis操作符
- 可空性深入
- 可空性和Java
- 函数
- 函数式编程概述
- OOP和FOP
- 函数式编程基本特性
- 组合与范畴
- 在Kotlin中使用函数式编程
- 函数入门
- 函数作用域
- 函数加强
- 命名参数
- 默认参数
- 可变参数
- 表达式函数体
- 顶层、嵌套、中缀函数
- 尾递归函数优化
- 函数重载
- 控制流
- if表达式
- when表达式
- for循环
- while循环
- 循环中的 Break 与 continue
- return返回
- 标签处返回
- 集合
- list集合
- list集合介绍和操作
- list常见方法和属性
- list集合变化和下标越界
- set集合
- set集合介绍和常见操作
- set集合常见方法和属性
- set集合变换和下标越界
- map集合
- map集合介绍和常见操作
- map集合常见方法和属性
- map集合变换
- 集合的函数式API
- map函数
- filter函数
- “ all ”“ any ”“ count ”和“ find ”:对集合应用判断式
- 别样的求和方式:sumBy、sum、fold、reduce
- 根据人的性别进行分组:groupBy
- 扁平化——处理嵌套集合:flatMap、flatten
- 惰性集合操作:序列
- 区间、数组、集合之间转换
- 面向对象
- 面向对象-封装
- 类的创建及属性方法访问
- 类属性和字段
- 构造器
- 嵌套类(内部类)
- 枚举类
- 枚举类遍历&枚举常量常用属性
- 数据类
- 密封类
- 印章类(密封类)
- 面向对象-继承
- 类的继承
- 面向对象-多态
- 抽象类
- 接口
- 接口和抽象类的区别
- 面向对象-深入
- 扩展
- 扩展:为别的类添加方法、属性
- Android中的扩展应用
- 优化Snackbar
- 用扩展函数封装Utils
- 解决烦人的findViewById
- 扩展不是万能的
- 调度方式对扩展函数的影响
- 被滥用的扩展函数
- 委托
- 委托类
- 委托属性
- Kotlin5大内置委托
- Kotlin-Object关键字
- 单例模式
- 匿名类对象
- 伴生对象
- 作用域函数
- let函数
- run函数
- with函数
- apply函数
- also函数
- 标准库函数
- takeIf 与 takeUnless
- 第二阶段重点深入
- Lambda编程
- Lambda成员引用高阶函数
- 高阶函数
- 内联函数
- 泛型
- 泛型的分类
- 泛型约束
- 子类和子类型
- 协变与逆变
- 泛型擦除与实化类型
- 泛型类型参数
- 泛型的背后:类型擦除
- Java为什么无法声明一个泛型数组
- 向后兼容的罪
- 类型擦除的矛盾
- 使用内联函数获取泛型
- 打破泛型不变
- 一个支持协变的List
- 一个支持逆变的Comparator
- 协变和逆变
- 第三阶段难点突破
- 注解和反射
- 声明并应用注解
- DSL
- 协程
- 协程简介
- 协程的基本操作
- 协程取消
- 管道
- 慕课霍丙乾协程笔记
- Kotlin与Java互操作
- 在Kotlin中调用Java
- 在Java中调用Kotlin
- Kotlin与Java中的操作对比
- 第四阶段专题练习
- 朱凯Kotlin知识点总结
- Kotlin 基础
- Kotlin 的变量、函数和类型
- Kotlin 里那些「不是那么写的」
- Kotlin 里那些「更方便的」
- Kotlin 进阶
- Kotlin 的泛型
- Kotlin 的高阶函数、匿名函数和 Lambda 表达式
- Kotlin协程
- 初识
- 进阶
- 深入
- Kotlin 扩展
- 会写「18.dp」只是个入门——Kotlin 的扩展函数和扩展属性(Extension Functions / Properties)
- Kotlin实战-开发Android