1.对象表达式与对象声明

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

2.对象表达式

  要创建一个继承自某个(或某些)类型的匿名类的对象,我们会这么写:

window.addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) { /*......*/
} override fun mouseEntered(e: MouseEvent) { /*......*/
}
})

  如果超类型有一个构造函数,则必须传递适当的构造函数参数给它。多个超类型可以由跟在冒号后面的 逗号分隔的列表指定:

open class A(x: Int) {
public open val y: Int = x
} interface B { /*......*/ } val ab: A = object : A(1), B {
override val y = 15
}

  任何时候,如果我们只需要“一个对象而已”,并不需要特殊超类型,那么我们可以简单地写

fun foo() {
val adHoc = object {
var x: Int = 0
var y: Int = 0
}
print(adHoc.x + adHoc.y)
}

   请注意,匿名对象可以用作只在本地和私有作用域中声明的类型。如果你使用匿名对象作为公有函数 的返回类型或者用作公有属性的类型,那么该函数或属性的实际类型会是匿名对象声明的超类型,如果 你没有声明任何超类型,就会是 Any 。在匿名对象中添加的成员将无法访问。

class C {
// 私有函数,所以其返回类型是匿名对象类型
private fun foo() = object {
val x: String = "x"
} // 公有函数,所以其返回类型是 Any
fun publicFoo() = object {
val x: String = "x"
} fun bar() {
val x1 = foo().x // 没问题
val x2 = publicFoo().x // 错误:未能解析的引用“x”
}
}

  对象表达式中的代码可以访问来自包含它的作用域的变量

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++
}
})
// ......
}

2.对象声明

  单例模式在一些场景中很有用,而 Kotlin(继 Scala 之后)使单例声明变得很容易:

object DataProviderManager {
fun registerDataProvider(provider: DataProvider) {
// ......
} val allDataProviders: Collection<DataProvider>
get() = // ......
}

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

  对象声明的初始化过程是线程安全的并且在首次访问时进行。 如需引用该对象,我们直接使用其名称即可:

DataProviderManager.registerDataProvider(......)

  这些对象可以有超类型:

object DefaultListener : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {
......
} override fun mouseEntered(e: MouseEvent) {
......
}
}

  注意:对象声明不能在局部作用域(即直接嵌套在函数内部),但是它们可以嵌套到其他对象声明或非内 部类中。

3.伴生对象

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

class MyClass {
companion object Factory {
fun create(): MyClass = MyClass()
}
}

  该伴生对象的成员可通过只使用类名作为限定符来调用:

val instance = MyClass.create()

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

class MyClass {
companion object {}
} val x = MyClass.Companion

  其自身所用的类的名称(不是另一个名称的限定符)可用作对该类的伴生对象(无论是否具名)的引用:

class MyClass1 {
companion object Named {}
} val x = MyClass1 class MyClass2 {
companion object {}
} val y = MyClass2

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

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 互操作性一节 。

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

对象表达式和对象声明之间有一个重要的语义差别:
— 对象表达式是在使用他们的地方立即执行(及初始化)的;
— 对象声明是在第一次被访问到时延迟初始化的;
— 伴生对象的初始化是在相应的类被加载(解析)时,与Java静态初始化器的语义相匹配。

5.类型别名:

  类型别名为现有类型提供替代名称。如果类型名称太⻓,你可以另外引入较短的名称,并使用新的名称 替代原类型名。

  它有助于缩短较⻓的泛型类型。

  5.1 例如,通常缩减集合类型是很有吸引力的:

typealias NodeSet = Set<Network.Node>
typealias FileTable<K> = MutableMap<K, MutableList<File>>

  5.2 你可以为函数类型提供另外的别名

typealias MyHandler = (Int, String, Any) -> Unit
typealias Predicate<T> = (T) -> Boolean

  5.3 你可以为内部类和嵌套类创建新名称:

class A {
inner class Inner
} class B {
inner class Inner
}
typealias AInner = A.Inner
typealias BInner = B.Inner

  5.4 类型别名不会引入新类型。它们等效于相应的底层类型。当你在代码中添加 typealias Predicate<T> 并使用 Predicate<Int> 时,Kotlin 编译器总是把它扩展为 (Int) -> Boolean 。因此,当你需要泛型函数类型时,你可以传递该类型的变量,反之亦然:

typealias Predicate<T> = (T) -> Boolean

fun foo(p: Predicate<Int>) = p(42)

fun main() {

    val f: (Int) -> Boolean = { it > 0 }
println (foo(f)) // 输出 "true"
val p: Predicate<Int> = { it > 0 }
println(listOf(1, -2).filter(p)) // 输出 "[1]" }

6.内联类:内联类仅在 Kotlin 1.3 之后版本可用,目前还是实验性的

  有时候,业务逻辑需要围绕某种类型创建包装器。然而,由于额外的堆内存分配问题,它会引入运行时的 性能开销。此外,如果被包装的类型是原生类型,性能的损失是很糟糕的,因为原生类型通常在运行时就 进行了大量优化,然而他们的包装器却没有得到任何特殊的处理。

  为了解决这类问题,Kotlin 引入了一种被称为 内联类 的特殊类,它通过在类的前面定义一个 inline 修饰符来声明:

inline class Password(val value: String)

  内联类必须含有唯一的一个属性在主构造函数中初始化。在运行时,将使用这个唯一属性来表示内联类的实例

// 不存在 'Password' 类的真实实例对象
// 在运行时,'securePassword' 仅仅包含 'String'
val securePassword = Password("Don't try this in production")

  这就是内联类的主要特性,它灵感来源于“inline”这个名称:类的数据被“内联”到该类使用的地方(类 似于内联函数中的代码被内联到该函数调用的地方)

7.成员

  内联类支持普通类中的一些功能。特别是,内联类可以声明属性与函数:

inline class Name(val s: String) {
val length: Int
get() = s.length fun greet() {
println("Hello, $s")
}
} fun main() {
val name = Name("Kotlin")
name.greet() // `greet` 方法会作为一个静态方法被调用
println(name.length) // 属性的 get 方法会作为一个静态方法被调用
}

  当然,内联类的成员也有一些限制:

— 内联类不能含有init代码块
— 内联类不能含有幕后字段
— 因此,内联类只能含有简单的计算属性(不能含有延迟初始化/委托属性)

8.继承

  内联类允许去继承接口

interface Printable {
fun prettyPrint(): String
} inline class Name(val s: String) : Printable {
override fun prettyPrint(): String = "Let's $s!"
} fun main() {
val name = Name("Kotlin")
println(name.prettyPrint()) // 仍然会作为一个静态方法被调用
}

  禁止内联类参与到类的继承关系结构中。这就意味着内联类不能继承其他的类而且必须是 final

9.表示方式

  在生成的代码中,Kotlin 编译器为每个内联类保留一个包装器。内联类的实例可以在运行时表示为包装 器或者基础类型。这就类似于 Int 可以表示为原生类型 int 或者包装器 Integer 。

  为了生成性能最优的代码,Kotlin 编译更倾向于使用基础类型而不是包装器。然而,有时候使用包装器 是必要的。一般来说,只要将内联类用作另一种类型,它们就会被装箱。

interface I
inline class Foo(val i: Int) : I fun asInline(f: Foo) {}
fun <T> asGeneric(x: T) {}
fun asInterface(i: I) {}
fun asNullable(i: Foo?) {} fun <T> id(x: T): T = x fun main() {
val f = Foo(42)
asInline(f) // 拆箱操作: 用作 Foo 本身
asGeneric(f) // 装箱操作: 用作泛型类型 T
asInterface(f) // 装箱操作: 用作类型 I
asNullable(f) // 装箱操作: 用作不同于 Foo 的可空类型 Foo?
// 在下面这里例子中,'f' 首先会被装箱(当它作为参数传递给 'id' 函数时)然后又被拆箱(当它从'id'函数 中被返回时)
// 最后, 'c' 中就包含了被拆箱后的内部表达(也就是 '42'), 和 'f' 一样
val c = id(f)
}

  因为内联类既可以表示为基础类型有可以表示为包装器,引用相等对于内联类而言毫无意义,因此这也 是被禁止的。

10.名字修饰

  由于内联类被编译为其基础类型,因此可能会导致各种模糊的错误,例如意想不到的平台签名冲突:

inline class UInt(val x: Int)

// 在 JVM 平台上被表示为'public final void compute(int x)'
fun compute(x: Int) { } // 同理,在 JVM 平台上也被表示为'public final void compute(int x)'!
fun compute(x: UInt) { }

  为了缓解这种问题,一般会通过在函数名后面拼接一些稳定的哈希码来重命名函数。

compute(x: UInt)
//将会被表示为
public final void compute-<hashcode>(int x)
//以 此来解决冲突的问题。
//请注意在 Java 中 - 是一个 无效的 符号,也就是说在 Java 中不能调用使用内联类作为形参的函数

11.内联类和类标别名

  初看起来,内联类似乎与类型别名非常相似。实际上,两者似乎都引入了一种新的类型,并且都在运行时表示为基础类型。

  然而,关键的区别在于类型别名与其基础类型(以及具有相同基础类型的其他类型别名)是 赋值兼容 的,而内联类却不是这样。

  换句话说,内联类引入了一个真实的新类型,与类型别名正好相反,类型别名仅仅是为现有的类型取了个新的替代名称(别名)

typealias NameTypeAlias = String

inline class NameInlineClass(val s: String)

fun acceptString(s: String) {}
fun acceptNameTypeAlias(n: NameTypeAlias) {}
fun acceptNameInlineClass(p: NameInlineClass) {}
fun main() {
val nameAlias: NameTypeAlias = ""
val nameInlineClass: NameInlineClass = NameInlineClass("")
val string: String = ""
acceptString(nameAlias) // 正确: 传递别名类型的实参替代函数中基础类型的形参
acceptString(nameInlineClass) // 错误: 不能传递内联类的实参替代函数中基础类型的形参
// And vice versa:
acceptNameTypeAlias(string) // 正确: 传递基础类型的实参替代函数中别名类型的形参
acceptNameInlineClass(string) // 错误: 不能传递基础类型的实参替代函数中内联类类型的形参
}

12.内联类的实验性状态

  内联类的设计目前是实验性的,这就是说此特性是正在 快速变化的,并且不保证其兼容性。在 Kotlin 1.3+ 中使用内联类时,将会得到一个警告,来表明此特性还是实验性的。

如需移除警告,必须通过指定编译器参数 -Xinline-classes 来选择使用这项实验性的特性。

13.在 Gradle 中启用内联类

compileKotlin {
kotlinOptions.freeCompilerArgs += ["-Xinline-classes"]
} tasks.withType<KotlinCompile> {
kotlinOptions.freeCompilerArgs += "-Xinline-classes"
}

14.在 Maven 中启用内联类

<configuration>
<args>
<arg>-Xinline-classes</arg>
</args>
</configuration>

kotlin类与对象——>对象表达式与对象声明、内联类的更多相关文章

  1. c++学习笔记(2)类的声名与实现的分离及内联函数

    一.类的声名与实现的分离: 和c函数声明与实现分离类似 有.h : 类的声明 .cpp : 类的实现 在在一个类的cpp中应该包含本类的.h文件 在cpp中类的使用:例: //Circle类 //Ci ...

  2. C++ 类 & 对象-C++ 内联函数-C++ this 指针-C++ 类的静态成员

    C++ 内联函数 C++ 内联函数是通常与类一起使用.如果一个函数是内联的,那么在编译时,编译器会把该函数的代码副本放置在每个调用该函数的地方. 对内联函数进行任何修改,都需要重新编译函数的所有客户端 ...

  3. SQL Server进阶(六)表表达式--派生表、公用表表达式(CTE)、视图和内联表值函数

    概述 表表达式是一种命名的查询表达式,代表一个有效地关系表.可以像其他表一样,在数据处理中使用表表达式. SQL Server支持四种类型的表表达式:派生表,公用表表达式,视图和内联表值函数. 为什么 ...

  4. SQL Server 表表达式--派生表、公用表表达式(CTE)、视图和内联表值函数

    概述 表表达式是一种命名的查询表达式,代表一个有效地关系表.可以像其他表一样,在数据处理中使用表表达式. SQL Server支持四种类型的表表达式:派生表,公用表表达式,视图和内联表值函数. 为什么 ...

  5. 【C++】类内函数与内联函数

    今天本来在休息,结果小伙伴问了我个问题,把我的三观都颠覆了.get到了新的知识点,这里记录一下. 内类的函数,都默认为是内联函数!! 这居然是真的.然后我就懵了.因为内联函数应该是定义在头文件里比较好 ...

  6. Kotlin 对象表达式和对象声明

    Kotlin 用对象表达式和对象声明来实现创建一个对某个类做了轻微改动的类的对象,且不需要去声明一个新的子类. 对象表达式 通过对象表达式实现一个匿名内部类的对象用于方法的参数中: window.ad ...

  7. 前端笔记之ES678&Webpack&Babel(中)对象|字符串|数组的扩展&函数新特性&类

    一.对象的扩展 1.1对象属性名表达式 ES6可以在JSON中使用[]包裹一个key的名字.此时这个key将用表达式作为属性名(被当做变量求值),这个key值必须是字符串. var a = 'name ...

  8. Java EE JSP内置对象及表达式语言

    一.JSP内置对象 JSP根据Servlet API规范提供了一些内置对象,开发者不用事先声明就可使用标准变量来访问这些对象. JSP提供了9种内置对象: (一).request 简述: JSP编程中 ...

  9. EL表达式获取对象属性的原理

    EL表达式获取对象属性的原理是这样的: 以表达式${user.name}为例 EL表达式会根据name去User类里寻找这个name的get方法,此时会自动把name首字母大写并加上get前缀,一旦找 ...

  10. C# 高性能对象映射(表达式树实现)

    前言 上篇简单实现了对象映射,针对数组,集合,嵌套类并没有给出实现,这一篇继续完善细节. 开源对象映射类库映射分析 1.AutoMapper 实现原理:主要通过表达式树Api 实现对象映射 优点: . ...

随机推荐

  1. Ubuntu 18.04.4 导入docker镜像,启动镜像,保存容器为镜像,导出镜像

    1.  查看  docker 版本 sudo docker version 2. 查看本地库中的镜像 sudo docker images 3.   查看  正在运行的  容器 sudo docker ...

  2. 国内的开源AI模型共享网站(AI模型的GitHub)—— mindscope —— 使用git lfs方式下载模型文件

    参考前文: 国内的开源AI模型共享网站(AI模型的GitHub)-- mindscope -- 对标外网的"huggingface",mindscope好用吗? 使用git lfs ...

  3. 【转载】Ubuntu20.04安装Bazel

    原文地址: https://zhuanlan.zhihu.com/p/311406177 ====================================== sudo apt install ...

  4. 读论文《Reinforced Attention for Few-Shot Learning and Beyond》

    2022年4月22日,实验室开组会,我讲了论文<Reinforced Attention for Few-Shot Learning and Beyond>,最近整理资料又再读了一遍,这里 ...

  5. 恭喜社区迎来新PMC成员!

    恭喜Apache SeaTunnel社区又迎来一位PMC Member@liugddx!在社区持续活跃的两年间,大家经常看到这位开源爱好者出现在社区的各种活动中,为项目和社区发展添砖加瓦.如今成为项目 ...

  6. spring创建 JavaWeb

  7. 100ASK_IMX6ULL arm板子如何移植刷卡器

    最近为了在arm板子上移植刷卡器,比较简单,但也遇到了坑,现在和大家分享下. 购买刷卡器 某宝很多,应该选哪一种呢? 一口君一共买了4种刷卡器,有2种可以用,还有2种不能用. 下图为最方便的一款,一口 ...

  8. 基于surging的产品项目-木舟开源了!

    一 . 概述 因为前段时间电脑坏了,导致代码遗失,踌躇满志马上上线的平台产品付之东流,现在熬夜在写代码希望能尽快推出企业正常使用的平台产品,而这次把代码开源,一是让大家对surging 使用有个深入的 ...

  9. Dockerfile介绍及常用保留指令

    从本文开始,咱们将介绍docker的另外一个技术点:dockerfile.我们来看看DockerFile相关的知识点,我们将怎么学习? 1:DockerFile是什么? 2:DockerFile构建过 ...

  10. 严蔚敏 数据结构 配套教材 PDF

    目录 严蔚敏 数据结构 配套教材 PDF 下载地址: 严蔚敏 数据结构 配套教材 PDF 配套教材包括: 严蔚敏<数据结构题集>(C语言版).pdf 严蔚敏<数据结构>(C语言 ...