Kotlin学习手记——单例、内部类、数据类、枚举类、密封类、内联类
Kotlin单例:
只需要类前面添加object
关键字即可,object
定义类等价于java的恶汉式的单例模式
object Singleton { var x: Int = 2 fun y(){ } init { //object不能定义构造函数,但可以定义init块 }}
object不能定义构造函数,但可以定义init
块
使用:
fun main() { Singleton.x Singleton.y()}
@JvmStatic
和 @JvmField
object Singleton { @JvmField var x: Int = 2 //生成java的静态成员 不会生成getter和setter方法 @JvmStatic fun y(){ } //生成java的静态方法}
普通kotlin类(非单例)中使用使用JvmField
和JvmStatic
:
class Foo { //普通类中使用JvmField和JvmStatic companion object { @JvmField var x: Int = 2 @JvmStatic fun y(){ } }}
注意,加的注解@JvmField
和@JvmStatic
只针对java平台的可用,其他平台并不可行。
单例的object类仍可以继承类:
object Singleton: Runnable{ override fun run() { }}
内部类:
在kotlin中类内部的class前面不写修饰符默认就是静态内部类,class前面写 inner修饰符才是java中的普通内部类,与java一样,普通内部类会持有外部类的对象引用。
class Outer { //普通内部类 与java一样会持有外部类的引用 inner class Inner //默认静态内部类 class StaticInner}
fun main() { val inner = Outer().Inner() val staticInner = Outer.StaticInner()}
object类内部的object类默认是静态的 不存在非静态的情况 不能定义成inner
object OuterObject { //内部object默认是静态的 不存在非静态的情况 不能定义成inner object StaticInnerObject}
匿名内部类:
fun main() { //匿名内部类 object : Cloneable { }}
其实就是object 省略类名直接实现接口。
匿名内部类 可用继承多个接口,java不行:
fun main() { // 类型是混合类型:Cloneable & Runnable val runnableCloneable = object : Cloneable, Runnable { override fun run() { } } }
实现多个接口时,它的类型是多个接口类型的混合类型。
Local class :
fun main() {//本地函数 fun localFunc(){ println("Hello") } //对应Java的local class class LocalClass: Cloneable, Runnable{ override fun run() {} }}
可以对比java的local class实现,其实就是在静态函数的内部定义一个类:
public class JavaInnerClasses { public static void main(String... args) { class LocalClass implements Cloneable, Runnable { @Override public void run() { } } }}
说实话,写了这么多年代码,未曾这么干过。。
数据类:
kotlin中提供一个data
关键字,data
修饰的类就是一个数据类,对标java的bean类:
data class Book(val id: Long, val name: String, val author: Person)data class Person(val id: Long, val name: String, val age: Int)
与java的bean类相比,kotlin的data类不能被继承,并且属性要全部写到构造函数当中,没有无参的构造函数。确实简便了许多!
并且编译器会为data类生成了一些好用的方法:
val book = Book(0, "Kotlin in Action", Person(1, "Dmitry", 40)) //编译器生成的方法 copy component1 book.copy() val id = book.component1() val name = book.component2() val author = book.component3()
其中copy()
和 component1()
等都是编译器自动生成的,component方法的意义是方便解构赋值的使用:
//解构赋值,对应的字段是通过component方法获取的 val (id, name, author) = book
关于解构:
//解构 val pair = "Hello" to "World" val (hello, world) = pair
data不能被继承,那么为啥不能有子类呢?
可以先看一下kotlin为data类生成的对应的java类是什么样的,查看方式,doule shift键,然后Actions中输入kotlin Bytecode显示:
点击Decompile即可查看对应生成的java代码
以下是Book的数据类对应生成的java代码
public final class Book { private final long id; @NotNull private final String name; @NotNull private final Person author; public final long getId() { return this.id; } @NotNull public final String getName() { return this.name; } @NotNull public final Person getAuthor() { return this.author; } public Book(long id, @NotNull String name, @NotNull Person author) { Intrinsics.checkNotNullParameter(name, "name"); Intrinsics.checkNotNullParameter(author, "author"); super(); this.id = id; this.name = name; this.author = author; } public final long component1() { return this.id; } @NotNull public final String component2() { return this.name; } @NotNull public final Person component3() { return this.author; } @NotNull public final Book copy(long id, @NotNull String name, @NotNull Person author) { Intrinsics.checkNotNullParameter(name, "name"); Intrinsics.checkNotNullParameter(author, "author"); return new Book(id, name, author); } // $FF: synthetic method public static Book copy$default(Book var0, long var1, String var3, Person var4, int var5, Object var6) { if ((var5 & 1) != 0) { var1 = var0.id; } if ((var5 & 2) != 0) { var3 = var0.name; } if ((var5 & 4) != 0) { var4 = var0.author; } return var0.copy(var1, var3, var4); } @NotNull public String toString() { return "Book(id=" this.id ", name=" this.name ", author=" this.author ")"; } public int hashCode() { long var10000 = this.id; int var1 = (int)(var10000 ^ var10000 >>> 32) * 31; String var10001 = this.name; var1 = (var1 (var10001 != null ? var10001.hashCode() : 0)) * 31; Person var2 = this.author; return var1 (var2 != null ? var2.hashCode() : 0); } public boolean equals(@Nullable Object var1) { if (this != var1) { if (var1 instanceof Book) { Book var2 = (Book)var1; if (this.id == var2.id && Intrinsics.areEqual(this.name, var2.name) && Intrinsics.areEqual(this.author, var2.author)) { return true; } } return false; } else { return true; } }}
除了类名方法名前面添加了final
关键字以外,生成了许多方法,其中有重写hashCode()
和equals()
方法,所以如果有一个类继承了data类,可能导致属性变化,从而导致hashCode()
和equals()
方法的结果不一致。
data类的属性最好全部为基本类型或者其他data类型,保持它的纯净性。
另外,有方法可以破除data
类的不可继承性,也有网友在吐槽kotlin的这个设计,觉得它不好,其实如果你想用一个可以继承到类,只需要把data
关键字去掉,建一个普通类就好了。kotlin这样设计肯定是想保持它的纯洁性,如果可继承,只会变的更复杂。
破除data
类的不可继承性需要额外添加两个插件:
plugins {... id 'org.jetbrains.kotlin.plugin.noarg' version '1.4.20' id 'org.jetbrains.kotlin.plugin.allopen' version '1.4.20'}noArg { invokeInitializers = true annotations "com.bennyhuo.kotlin.advancedtypes.dataclasses.PoKo"}allOpen { annotations "com.bennyhuo.kotlin.advancedtypes.dataclasses.PoKo"}
上面是参考学习资料当中的代码工程中的配置,然后在data类上加注解即可
@PoKodata class Book(val id: Long, val name: String, val author: Person)
这时同样去查看Book的数据类对应生成的java代码会发现,之前的类名和get方法名前面的final
关键字被移除了,也就是可以被继承使用了,同时会生成一个无参的构造函数。可见比较麻烦,非有必要,还是不要这么干。。
枚举类:
kotlin里面的枚举类跟java差不多
enum class State { Idle, Busy}//枚举定义构造函数 同javaenum class State1(val id: Int) { Idle(0), Busy(1)}enum class Color { White, Red, Green, Blue, Yellow, Black}
fun main() { State.Idle.name // Idle State.Idle.ordinal // 0 val state = State.Idle //枚举全部值 val value = when (state) { State.Idle -> { 0 } State.Busy -> { 1 } } //枚举创建区间 val colorRange = Color.White .. Color.Green val color = Color.Blue //Blue不在区间内 println(color in colorRange)}
枚举类不可继承其他类,因为枚举类有父类是enum
,但是可以实现接口:
enum class State: Runnable{ Idle, Busy{ override fun run() { println("For Busy State.") } }; override fun run() { println("For Every State.") }}
fun main() { State.Idle.run() State.Busy.run()}
枚举类可以定义扩展函数:
fun State.successor(): State? { return State.values().let { if (ordinal 1 >= it.size) null else it[ordinal 1] }}fun State.predecessor(): State? { return State.values().let { if (ordinal - 1 < 0) null else it[ordinal - 1] }}
fun main() { println(State.Idle.successor()) println(State.Busy.successor())}
密封类:
其实就是一个只能在同一个文件中定义子类的抽象类。不得不说kotlin玩的花样很多,会玩。。。
定义方式是在类的前面加sealed
关键字
sealed class PlayerStateobject Idle : PlayerState()class Playing(val song: Song) : PlayerState() { fun start() {} fun stop() {}}class Error(val errorInfo: ErrorInfo) : PlayerState() { fun recover() {}}
完整的示例:控制播放器播放状态的例子
data class Song(val name: String, val url: String, var position: Int)data class ErrorInfo(val code: Int, val message: String)object Songs { val StarSky = Song("Star Sky", "https://fakeurl.com/321144.mp3", 0)}sealed class PlayerStateobject Idle : PlayerState()class Playing(val song: Song) : PlayerState() { fun start() {} fun stop() {}}class Error(val errorInfo: ErrorInfo) : PlayerState() { fun recover() {}}class Player { var state: PlayerState = Idle fun play(song: Song) { this.state = when (val state = this.state) { Idle -> { Playing(song).also(Playing::start) } is Playing -> { state.stop() Playing(song).also(Playing::start) } is Error -> { state.recover() Playing(song).also(Playing::start) } } }}fun main() { val player = Player() player.play(Songs.StarSky)}
注意其中的when
表达式的使用,可见它可以用来替代枚举类做类似的功能,子类的个数也是全部可枚举的。跟枚举类有相似之处。
内联类:
简而言之,内联类是一种类型的包装类,类前面加inline
关键字,构造器只能有一个参数,不能继承类,不能被继承,不能作为内部类。类似于java的Integer、Double这种吧,但有不同。
内联类虽然不能继承类或被继承,但是可以实现接口。
////只能有方法不能有属性inline class BoxInt(val value: Int): Comparable<Int> { override fun compareTo(other: Int) = value.compareTo(other) operator fun inc(): BoxInt { return BoxInt(value 1) }}
BoxInt会做编译优化,只有在需要的时候才使用包装类 多数情况下直接使用Int
,可以反编译字节码生成的java代码查看。另外,还有个特点就是它只能有方法不能有属性。
内联类也可以用来模拟枚举类,与枚举相比内存占用小,但使用方式类似
inline class State(val ordinal: Int) { companion object { val Idle = State(0) val Busy = State(1) } fun values() = arrayOf(Idle, Busy) val name: String get() = when (this) { State.Idle -> "Idle" State.Busy -> "Busy" else -> throw IllegalArgumentException() }}inline class Color(val value: UInt) { companion object { val Red = Color(0xFFFF0000u) val Green = Color(0xFF00FF00u) val Blue = Color(0xFF0000FFu) } fun values() = arrayOf(Red, Green, Blue) val name: String get() = when (this) { Red -> "Red" Green -> "Green" Blue -> "Blue" else -> throw IllegalArgumentException() }}
fun main() { State.Busy Color.Blue var boxInt = BoxInt(5) if(boxInt < 10){ println("value is less than 10") } val newValue = boxInt.value * 200 println(newValue) boxInt println(boxInt)}
密封类的实例:递归整型列表的简单实现
sealed class IntList { object Nil: IntList() { override fun toString(): String { return "Nil" } } data class Cons(val head: Int, val tail: IntList): IntList(){ override fun toString(): String { return "$head, $tail" } } fun joinToString(sep: Char = ','): String { return when(this){ Nil -> "Nil" is Cons -> { "${head}$sep${tail.joinToString(sep)}" } } }}fun IntList.sum(): Int { return when(this){ IntList.Nil -> 0 is IntList.Cons -> head tail.sum() }}operator fun IntList.component1(): Int? { return when(this){ IntList.Nil -> null is IntList.Cons -> head }}operator fun IntList.component2(): Int? { return when(this){ IntList.Nil -> null is IntList.Cons -> tail.component1() }}operator fun IntList.component3(): Int? { return when(this){ IntList.Nil -> null is IntList.Cons -> tail.component2() }}fun intListOf(vararg ints: Int): IntList { return when(ints.size){ 0 -> IntList.Nil else -> { IntList.Cons( ints[0], //array前面加* 展开数组 intListOf(*(ints.slice(1 until ints.size).toIntArray())) ) } }}// [0, 1, 2, 3]fun main() { //val list = IntList.Cons(0, IntList.Cons(1, IntList.Cons(2, IntList.Cons(3, IntList.Nil)))) val list = intListOf(0, 1, 2, 3) println(list) println(list.joinToString('-')) println(list.sum()) val (first, second, third) = list println(first) println(second) println(third) //val (a, b, c, d, e) = listOf<Int>()}