Kotlin数据类、密封类、嵌套类、内部类、枚举类、内联类

一、数据类

数据类标记为 data,类似java POJO。

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

为了确保生成的代码的⼀致性以及有意义的行为,数据类必须满足以下要求:

  • 主构造函数需要至少有⼀个参数;
  • 主构造函数的所有参数需要标记为 val 或 var ;
  • 数据类不能是抽象、开放、密封或者内部的;
  • 数据类只能实现接口。(在1.1之前,1.1之后可以扩展其它类)

编译器自动从住构造函数中声明的所有属性成员导出以下成员:

  • equals() / hashCode() 对
  • toString() 格式是 "User(name=John, age=42)"
  • componentN() 函数 按声明顺序对应于所有属性
  • copy() 函数

成员生成遵循关于成员继承的这些规则:

  • 如果在数据类体中有显式实现 equals() 、hashCode() 或者 toString() ,或者这些函数在父类中有 final 实现,那么不会生成这些函数,而会使用现有函数;
  • 如果超类型具有 open 的 componentN() 函数并且返回兼容的类型,那么会为数据类⽣成相应的函数,并 覆盖超类的实现。如果超类型的这些函数由于签名不兼容或者是 Mnal 而导致无法覆盖,那么会报错。
  • 不允许从⼀个已具 copy(……) 函数且签名匹配的类型派生⼀个数据类。
  • 不允许为 componentN() 以及 copy() 函数提供显式实现。

1.在类体中声明的属性

自动生成的函数,编译器只使用在主构造函数内部定义的属性。如需在生成的实现中排除⼀个属性,需要声明在类体中。

2.复制

需要复制⼀个对象改变它的⼀些属性,但其余部分保持不变

fun copy(name: String = this.name, age: Int = this.age) = User(name, age)

val jack = User(name = "Jack", age = 1) 
val olderJack = jack.copy(age = 2)

3.数据类与解构声明

为数据类生成的 Component 函数 使它们可在解构声明中使用:

val jane = User("Jane", 35) 
val (name, age) = jane 
println("$name, $age years of age") // 输出 "Jane, 35 years of age"

4.标准数据类

标准库提供了 Pair 与 Triple 。尽管在很多情况下具名数据类是更好的设计选择,因为它们通过为属性提供有意义的名称使代码更具可读性。

二、密封类

密封类用来表式受限的类继承结构:当⼀个值为有限几种的类型、而不能有任何其他类型时。在某种意义上,是枚举类的扩展:枚举类型的值集合也是受限的,但每个枚举常量只存在⼀个实例,而密封类的⼀个子类可以有可包含状态的多个实例。

  • 声明一个密封类,在类名前⾯添加 sealed 修饰符。所有子类都必须在与密封类相同的文件中声明。
  • ⼀个密封类是自身抽象的,它不能直接实例化,可以有抽象(abstract)成员。
  • 密封类不允许有非-private 构造函数(其构造函数默认为 private)
  • 扩展密封类子类的类(间接继承者)可以放在任何位置,无需在同⼀个⽂件中
sealed class Expr 
data class Const(val number: Double) : Expr()   //数据类继承密封类
data class Sum(val e1: Expr, val e2: Expr) : Expr() 
object NotANumber : Expr()

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

private fun sealedClassAndWhen(expr: Expr) = when (expr) {
    is Const -> expr.number
    is Sum -> eval(expr.e1) + eval(null)
    NotANumber -> Double.NaN // 不再需要 `else` ⼦句,因为我们已经覆盖了所有的情况 }
}

三、嵌套类与内部类

类可以嵌套在其他类中:

class Outer {
    private val bar:Int =1

    class Nested {
        fun foo() =2
    }
}
val demo = Outer.Nested().foo() // == 2,调用嵌套类方法和java内部类一样

1.内部类

标记为 inner 的嵌套类能够访问其外部类的成员。内部类会带有⼀个对外部类的对象的引用:

class Outer {
    private val bar: Int = 1 
    inner class Inner {
        fun foo() = bar
    }
}
val demo = Outer().Inner().foo() // == 1

2.匿名内部类

使用对象表达式创建匿名内部类实例:

window.addMouseListener(object : MouseAdapter() {
    override fun mouseClicked(e: MouseEvent) {
        ……
    }

    override fun mouseEntered(e: MouseEvent) {
        ……
    }
})

对象是函数式接口(当个抽象方法的接口),可以使用拉姆达表达式进行SAM转换,使⽤带接口类型前缀的lambda表达式创建。

val listener = ActionListener { println("clicked") }

四、枚举类

枚举类的最基本的用法是实现类型安全的枚举。每个枚举常量都是⼀个对象。枚举常量用逗号分隔。

enum class Direction { 
    NORTH, SOUTH, WEST, EAST 
}

1.初始化

因为每⼀个枚举都是枚举类的实例,所以他们可以是这样初始化过的:

enum class Color(val rgb: Int) {
    RED(0xFF0000),
    GREEN(0x00FF00),
    BLUE(0x0000FF)
}

2.匿名类

枚举常量还可以声明其带有相应方法以及覆盖了基类方法的匿名类

enum class ProtocolState {
    WAITING {
        override fun signal() = TALKING
    },
    TALKING {
        override fun signal() = WAITING
    };

    abstract fun signal(): ProtocolState
}

3.在枚举类中实现接口

⼀个枚举类可以实现接口(但不能从类继承),可以为所有条目提供统⼀的接口成员实现,也可以在相应匿名类中为每个条目提供各自的实现。只需将接口添加到枚举类声明中即可。

enum class IntArithmetics : BinaryOperator<Int>, IntBinaryOperator {
    PLUS {
        override fun apply(t: Int, u: Int): Int = t + u
    },
    TIMES {
        override fun apply(t: Int, u: Int): Int = t * u
    };

    override fun applyAsInt(t: Int, u: Int) = apply(t, u)
}

4.使用枚举常量

Kotlin中的枚举类也有合成⽅法允许列出定义的枚举常量以及通过名称获取枚举常量:

EnumClass.valueOf(value: String): EnumClass 
EnumClass.values(): Array<EnumClass>

可以使用 enumValues<T>() 与 enumValueOf<T>() 函数以泛型的方式访问枚举类中的常量 :

enum class RGB { RED, GREEN, BLUE }

inline fun <reified T : Enum<T>> printAllValues() {
    print(enumValues<T>().joinToString { it.name })
}

printAllValues<RGB>()// 输出 RED, GREEN, BLUE

每个枚举常量都具有在枚举类声明中获取其名称与位置的属性:

val name: String 
val ordinal: Int

枚举常量还实现了 Comparable 接口,其中⾃然顺序是它们在枚举类中定义的顺序。

五、内联类

围绕某种类型创建包装器,会由于额外的堆内存分配问题,引入运行时的性能开销。此外,如果被包装的类型是原生类型,性能的损失是很糟糕的,因为原生类型通常在运行时就进行了大量优化,然而他们的包装器却没有得到任何特殊的处理。为了解决这类问题,Kotlin 引⼊了⼀种被称为内联类的特殊类,它通过在类的前⾯定义⼀个 inline 修饰符来声明.
内联类必须含有唯⼀的⼀个属性在主构造函数中初始化。在运行时,将使用这个唯⼀属性来表示内联类的实例。这就是内联类的主要特性,类的数据被 “内联”到该类使用的地方。

1.成员

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

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 代码块
  • 内联类不能含有幕后字段,所以内联类只能含有简单的计算属性(不能含有延迟初始化/委托属性)

2.继承

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

3.表示方式

Kotlin 编译器为每个内联类保留⼀个包装器。内联类的实例可以在运行时表示为包装器或者基础类型。这就类似于 Int 可以表示为原生类型 int 或者包装器Integer。内联类既可以表式为基础类型有可以表示为包装器,引用相等对于内联类而言毫⽆意义,因此这也是被禁止的。

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)
}

4.名字修饰

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

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) {}

会通过在函数名后面拼接⼀些稳定的哈希码来重命名函数。因此,fun compute(x: UInt) 将会被表示为 public final void compute-<hashcode>(int x) ,以此来解决冲突的问题。“-””是⼀个无效的 符号,也就是说在 Java 中不能调用使用内联类作为形参的函数。

5.内联类与类型别名

内联类似乎与类型别名非常相似。关键的区别在于类型别名与其基础类型(以及具有相同基础类型的其他类型别名)是赋值兼容的,而内联类却不是这样。 换句话说,内联类引入了⼀个真实的新类型,与类型别名正好相反,类型别名仅仅是为现有的类型取了个新的替代名称(别名):

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)   // 错误: 不能传递基础类型的实参替代函数中内联类类型的形参 
}

6.内联类的 alpha 状态

处于alpha状态,使用会有警告,必须通过指定编译器参数 -Xinline-classes 来选择使用这项特性。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,014评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,796评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,484评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,830评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,946评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,114评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,182评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,927评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,369评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,678评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,832评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,533评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,166评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,885评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,128评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,659评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,738评论 2 351

推荐阅读更多精彩内容