三、kotlin的类和对象(二)


theme: channing-cyan


继承★

kotlin 沿用了 java 的单继承系统, 不允许 c++ 的多继承出现, 但允许 kotlin 接口的多实现

open class Base(val p: Int)
class Derived(p: Int) : Base(p)

(1) 子类需要继承父类, 子类有责任负责父类字段的初始化

class Derived(p: Int) : Base(p)

(2) 子类最终都需要调用到父类的构造函数以初始化父类的字段, 子类构造函数如果要调用父类构造函数需要使用上 super

(3) 子类构造函数如果要调用到子类的其他构造函数则需要使用到 this

open class Base(val p: Int) {
    // name 可以不在这里直接初始化成 "", 可以选择在 init 代码块中, 前面可知, init 中的代码. 最后会合并到主构造函数中
    var name: String = ""
    constructor(p: Int, name: String): this(p) {
        this.name = name
    }
}

class Derived : Base {
    // 如果子类没有显示的写出主构造函数, 那么次构造函数需要使用 super 主动调用父类的构造函数(主, 次皆可)
    constructor(p: Int) : super(p) {}
    constructor(p: Int, name: String) : super(p, name) {}
    constructor(a: Int, p: Int, name: String) : this(p, name)
}

类如果需要被继承, 这需要显示的写出 open class 类名, 否则默认是 public final 类名

覆盖方法(重写方法)

kotlin 所有的类、函数和字段默认都添加上了 final

如果我们需要重写这些功能,可以使用 open

open class Shape {
    open fun draw() {}
}
open class Circle : Shape() {
    // 如果class Circle是 final 类型(final open Circle), 则函数也是 final类型, 可以不用显示的写出来
    final override fun draw() {}
}

open只针对 final,添加 open 会删除掉 final

覆盖属性

子类与父类有相同的属性名称

open class Shape {
    open val vertexCount: Int = 0
}

class Rectangle : Shape() {
    override var vertexCount: Int = 1
}

在字段上: (字段可见性默认是 private)

① 使用 val 只生成 public final get

② 使用 var 生成 public final get/set

如果字段加上 open , 影响的是 get/set 函数的 final , 会直接消失

(2) 在父子类间, 子类可以用 var 覆盖父类 val 的同名属性

  • 为什么?

父类 val 属性只有 getter 方法, 子类使用 var 属性后, 将会生成 getter/setter 方法, 没有任何问题

如果 父类是 var 那么有 getter/setter 方法, 此时 子类使用上 val , 只能有 getter , 可是父类的 setter 子类仍然可以调用, 这不行

open class Person constructor(open val nickName: String) {
}

class Child(_nickName: String) : Person(_nickName) {
    override var nickName: String = _nickName
}

子类初始化顺序▲

open class Base(val name: String) {
   init {
      println("2. 父类 init 代码块")
   }
   
   open val size: Int = this.name.length.also { println("3. 父类构造函数执行 size 对象初始化") }
}

class Derived(name: String, val lastName: String) :
   Base(name.capitalize().also { println("1. Derived的构造函数的初始化代码块 执行初始化") }) {
   init {
      println("4. Derived 的init代码块执行")
   }
   
   override val size: Int =
      (super.size + lastName.length).also { println("5. 初始化  Derived 的 size 字段") }
}

fun main(args: Array<String>) {
   val derived = Derived("zhazha", "xixi")
}

具体顺序是:

1. Derived的构造函数的初始化代码块 执行初始化
2. 父类 init 代码块
3. 父类构造函数执行 size 对象初始化
4. Derived 的init代码块执行
5. 初始化  Derived 的 size 字段

调用父类的东西★

open class Rectangle {
    open fun draw() { println("Drawing a rectangle") }
    val borderColor: String get() = "black"
}
class FillRectangle : Rectangle() {
    override fun draw() {
       // super 调用父类的函数
        super.draw()
        println("Filling the rectangle")
    }
   
   // 调用父类的属性, 这里调用了 getBorderColor() 函数
    val fillColor: String get() = this.borderColor
}
super/this和标签配合选择调用父类函数

调用父类的属性或者方法一般使用 super

open class Rectangle {
   open fun draw() {
      println("Drawing a rectangle")
   }
   
   val borderColor: String get() = "black"
}

class FilledRectangle : Rectangle() {
   override fun draw() {
      // super 调用父类的函数
      super.draw()
      println("Filling the rectangle")
   }
   
   // 调用父类的属性, 这里调用了 getBorderColor() 函数
   val fillColor: String get() = this.borderColor
   
   inner class Filler {
      fun fill() {
         println("Filling ")
      }
      
      fun draw() {
         println("Filler draw...")
      }
      
      fun drawFill() {
         // idea 智能提示对这个支持不太好, 需要用户手写完毕, 提示都没有
         // 只能支持内部类访问外部类的方法, 访问 FilledRectangle 类的父类方法 draw
         super@FilledRectangle.draw()
         fill()
         println("fillColor = $fillColor")
         // 调用 Filler 的 draw
         draw()
         // 调用 FilledRectangle 的  draw
         this@FilledRectangle.draw()
         println("class rectangle color: ${super@FilledRectangle.borderColor}")
      }
   }
}

fun main(args: Array<String>) {
   val rectangle = FilledRectangle()
   rectangle.draw()
   rectangle.Filler().drawFill()
}

super@AAA类.BB方法(), 表示调用 AAA类的父类BB方法

this@AAA类.BB方法(), 表示调用 AAA类BB方法

覆盖规则

open class Rectangle {
   open fun draw() {
      println("rectangle  draw...")
   }
}

interface Polygon {
   fun draw()
}

class Square() : Rectangle(), Polygon {
   // 虽然两个父类都存在 draw 方法, 但是其中一个是接口, 一个已经存在函数体, 所以直接调用的实现了的方法
}

fun main(args: Array<String>) {
   val square = Square()
   square.draw()
}

如果是两个类的话, 就需要直接重写一个自己想要的方法了

interface Rectangle {
    fun draw() {
        println("Rectangle draw...")
    }
}
interface Polygon {
    fun draw() {
        println("Polygon draw...")
    }
}
class Square : Rectangle, Polygon {
    override fun draw() {
        super<Rectangle>.draw()
        super<Polygon>.draw()
        println("Square draw...")
    }
}
fun main(args: Array<String>) {
    val square = Square()
    square.draw()
}

两个接口的默认函数重名, 子类就需要手动重写该方法, 定义自己的 draw 函数

super<Rectangle>.draw()
super<Polygon>.draw()
println("Square draw...")

多继承问题

多继承会导致继承关系语义上的混乱, 比如下面的情况

骡子的多继承困惑

interface Horse {
   fun run()
}

interface Donkey {
   fun run()
}

class Mule : Horse, Donkey {
   override fun run() {
      TODO("Not yet implemented")
   }
   override fun run() {
      TODO("Not yet implemented")
   }
}

我们在定义 Mule 类的时候需要纠结, 到底实现 马 的 run 还是 实现 驴 的 run 函数, 这两个函数存在区别

马一般跑的比较快, 驴 耐力特别好

到底选择 马 还是 驴 的能力呢?

这就是 棱形继承问题 也叫 钻石问题 , 继承关系将在语义上产生歧义: 骡子到底继承了马的run功能还是驴的run功能呢?

接口实现多继承

在前面已经有过这个案例了

kotlin中的类可以实现多个接口

interface Flyer {
   fun fly()
   fun kind() = "flying animals"
}

interface Animal {
   val name: String
   fun eat()
   fun kind() = "flying animals"
}

private class Bird : Flyer, Animal {
   override fun fly() {
      println("I can fly")
   }
   
   override val name: String
      get() = "燕子"
   
   override fun eat() {
      println("I can eat")
   }
   
   override fun kind(): String {
      println(super<Animal>.kind())
      println(super<Flyer>.kind())
      return "my name is ${this.name}"
   }
}

如果存在多个同名的默认方法, 需要通过 super 这种方式指定调用

实现接口的属性和方法都必须加上 override 关键字, 且不能省略(和 java 不同)

上面的源码还可以这么搞:

private class Bird(override val name: String) : Flyer, Animal {}

将接口的属性放到子类的主构造函数中重写

其实接口中的属性只有 setter/getter 函数, 没有 field 字段

Bird 中实现的 name 可以有 field 字段

final class Bird implements Flyer, Animal {
   @NotNull
   private final String name;
}

当然我们还可以让 name 字段在 Bird 中不存在, 只保留 getter/setter

private class Bird : Flyer, Animal {
   
   override val name: String
      get() = "xiaobai"
}

三、kotlin的类和对象(二)_第1张图片

同样的, 将上面的 FlyAnimal 代替成 HorseDonkey 还是会存在 到底 实现哪个 run 的问题

内部类解决多继承问题

我们可以使用内部类模拟实现多继承的方案, 我们知道, 类内可以定义多个内部类, 每个内部类都可以继承自各个不同的父类, 且每个内部类都是独立存在的, 使用这种特性解决多继承问题

java 中只要在一个类内写上另一个类, 我们都可以认为另一个类是内部类

但是在 kotlin 中, 就不一样了

直接在 kotlin 类中写上另一个类A, 此时类 A 却是 外部类的 嵌套类, 而不是内部类

三、kotlin的类和对象(二)_第2张图片

name 无法被识别, 说明ErrorInnerKotlin 类不持有外部类的 this, 否则不会报错

使用嵌套类方便序列化和反序列化, 不过这是后话

kotlin中的内部类是这样的:

三、kotlin的类和对象(二)_第3张图片

需要使用 inner 修饰内部类

嵌套类 vs 内部类

java 中如果需要实现一个嵌套类需要配合 static 关键字, 而 kotlin 则相反, 默认是嵌套类, 需要加上 inner 才能变成内部类

嵌套类和内部类的区别在于: 是否持有外部类this 引用, 就如前面的代码显示, 如果不持有 外部类this, name无法被访问, 而嵌套类就是这样的…

使用内部类解决骡子问题
interface Horse {
	fun run()
}

interface Donkey {
	fun run()
}

class Mule {
	fun run() {
		HorseC().run()
		print(" and ")
		DonkeyC().run()
		println()
	}
	
	private inner class HorseC : Horse {
		override fun run() {
			print("I can run fast")
		}
	}
	
	private inner class DonkeyC : Donkey {
		override fun run() {
			print("I can run long time")
		}
	}
}

通过这个案例改造后我们发现:

  1. 可以在一个类内部定义多个内部类, 每个内部类实例都有自己独立的状态, 它们与外部对象的信息相互独立
  2. 通过内部类HorseCDonkeyC分别继承HorseDonkey这两个外部类, 我们就可以在Mule类中定义他们的实例对象, 从而获得他们两者不同的状态和行为(那为什么不直接定义两个属性到 Mule 中??? 因为是接口么?)
  3. 我们可以使用 private 修饰内部的 HorseCDonkeyC 让外部类不至于直接访问到内部类. 提供了更好的封装性

因此在某些场景下, 内部类是解决多继承问题非常好的思路

这种方式我们可以在很多框架的源码中发现

使用委托代替多继承

委托在 kotlin 用的还是比较多的, 比如: 委托的观察者, 可以观察属性的修改和读取行为

现在来试试 委托如何代替多继承

interface CanFly {
   fun fly()
}

interface CanEat {
   fun eat()
}

open class Flyer : CanFly {
   override fun fly() {
      println("I can fly")
   }
}

open class Animal : CanEat {
   override fun eat() {
      println("I can eat")
   }
}

class Bird(flyer: Flyer, animal: Animal) : CanFly by flyer, CanEat by animal {
   
}

fun main() {
   val flyer = Flyer()
   val animal = Animal()
   val bird = Bird(flyer, animal)
   bird.fly()
   bird.eat()
}

这种方式和前面接口实现多继承的方式相似, 那有什么有点呢?

  1. 接口是无状态的, 所以即使它提供了默认方法实现也是很简单的, 不能实现复杂的逻辑, 也不推荐在接口默认方法中实现复杂的逻辑, 我们可以使用委托的方式, 虽然他也是接口委托, 但是它用一个具体的类去实现方法逻辑, 可以拥有更强大的能力
  2. 在A类组合 委托BC对象的时候, 不是组合 ABC 对象的方法, 而是 A.method 这种形式, 更加的只管, 虽然背后也是通过委托对象来执行具体的方法逻辑的

数据类★

数据类就跟javaBean相关类中加上 lombok 注解 @Data 的功能差不多

data class Bird(var weight: Double, var age: Int, var color: String)

反编译成 java :

public final class Bird {
   private double weight;
   private int age;
   @NotNull
   private String color;

   public final double getWeight() {
      return this.weight;
   }

   public final void setWeight(double var1) {
      this.weight = var1;
   }

   public final int getAge() {
      return this.age;
   }

   public final void setAge(int var1) {
      this.age = var1;
   }

   @NotNull
   public final String getColor() {
      return this.color;
   }

   public final void setColor(@NotNull String var1) {
      this.color = var1;
   }

   public Bird(double weight, int age, @NotNull String color) {
      super();
      this.weight = weight;
      this.age = age;
      this.color = color;
   }

   public final double component1() {
      return this.weight;
   }

   public final int component2() {
      return this.age;
   }

   @NotNull
   public final String component3() {
      return this.color;
   }

   @NotNull
   public final Bird copy(double weight, int age, @NotNull String color) {
      return new Bird(weight, age, color);
   }

   // $FF: synthetic method
   public static Bird copy$default(Bird var0, double var1, int var3, String var4, int var5, Object var6) {
      if ((var5 & 1) != 0) {
         var1 = var0.weight;
      }

      if ((var5 & 2) != 0) {
         var3 = var0.age;
      }

      if ((var5 & 4) != 0) {
         var4 = var0.color;
      }

      return var0.copy(var1, var3, var4);
   }

   @NotNull
   public String toString() {
      return "Bird(weight=" + this.weight + ", age=" + this.age + ", color=" + this.color + ")";
   }

   public int hashCode() {
      int var10000 = (Double.hashCode(this.weight) * 31 + Integer.hashCode(this.age)) * 31;
      String var10001 = this.color;
      return var10000 + (var10001 != null ? var10001.hashCode() : 0);
   }

   public boolean equals(@Nullable Object var1) {
      if (this != var1) {
         if (var1 instanceof Bird) {
            Bird var2 = (Bird)var1;
            if (Double.compare(this.weight, var2.weight) == 0 && this.age == var2.age && Intrinsics.areEqual(this.color, var2.color)) {
               return true;
            }
         }

         return false;
      } else {
         return true;
      }
   }
}

这比 javaBean 舒服多了

很多人会说, 还javaBean??? 为什么不用 lombok 呢? 深有同感, kotlindata class 其实不太方便

copy函数

copy 方法的主要作用是: 从已有的数据类对象中拷贝一个新的数据类对象

需要注意这是浅拷贝, 如果 data class 中有引用对象, 需要注意了

既然都 copy 了? 那我为什么不用 MapStruct???

componentN与解构

说到底, component 就是以 data class 声明的顺序定义的对象, 从 1 开始, 到最高的 5

这有什么用呢? 解构

如果在 java 我们可能会这样使用一个对象接收一个函数的返回值对象, 然后再分别赋值给各个变量

val b1 = Bird(20.0, 1, "blue")
val weight = b1.weight
val age = b1.age
val color = b1.color

kotlin 中:

val (weight, age, color) = b1

解构的方式还可以用于下面这样:

val birdInfo = "20.0,1,bule"
val (weight, age, color) = birdInfo.split(",")
println("weight = $weight, age = $age, color = $color")

这种神器的方法就是别的语言常说的解构, 一种通过与编译器达成约定的功能

自定义解构
data class Bird(var weight: Double, var age: Int, var color: String) {
   var sex = 1
   operator fun component4(): Int = this.sex
}
元组

kotlin还提供了 元组, 比如 PairTriple 其中 Pair 是二元组, Triple 是三元组

其底层都是 data class, 而且每个元素可以是任意类型

数据类的约定与使用

使用数据类必须满足以下条件:

  1. 数据类必须有一个构造方法, 该方法至少包含一个参数
  2. 数据类构造函数的方法的参数必须使用 var/val
  3. 数据类前面不能有 abstract open sealed 或者 inner 进行修饰
  4. 数据类可以实现接口和继承类

如果代码写成这样: (显示指定默认值)

data class Bird(var weight: Double = 0.0, var age: Int = 0, var color: String = "red")

他就会提供一个无参数构造函数

data class 只使用主构造函数中的字段作为模板生成各种代码, 比如 copy equals 等, 如果属性不在主动构造函数中, 则不会被使用作为生成代码的元素

data class Person(val name: String) {
    val age: Int = 0
}

这样 age 不会被用来生成代码

data class 目前还是不太好用, 比较局限, 对于很多框架还有兼容性问题, 我看很多人都没用上, 连我也是

抽象类★

abstract class Polygon {
    abstract fun draw()
}

class Rectangle : Polygon() {
    override fun draw() {
        println("Rectangle draw...")
    }
}

还可以用抽象成员函数覆盖非抽象的open成员函数

open class Polygon {
    open fun draw() {
        println("Polygon draw...")
    }
}
abstract class Rectangle : Polygon() {
    // 子类将父类的open方法重写成 abstract
    abstract override fun draw()
}

可见性修饰符

kotlinjava 的可见性修饰符差不多, 但也有区别:

  1. kotlinjava 的默认修饰符不同, java 默认是包(default), 而 kotlin 默认是 public
  2. kotlin有独特的修饰符 internal
  3. kotlin可以在一个文件内单独声明方法及产量, 同样支持可见性修饰符
  4. Java中除了内部类可以用private修饰以外, 其他类都不允许private修饰, 而 kotlin可以.
  5. kotlinjava中的 protected 的访问范围不同, java中是包, 类以及子类可访问, 而 kotlin 只允许类及子类

kotlin 省掉了 java 每次写个类都需要加上 public 的问题, 虽然每次它都会自动加上

java 中 默认类都是 default 包访问范围, kotlin中也有 internal 跟其配对, 但还是有不同, internal模块内可见

那么什么是模块内可见呢?

  1. 一个 maven 项目
  2. 一个 gradle 项目
  3. 一个 eclipse 项目
  4. 一个 idea 项目
  5. 一组由 Ant任务执行编译的代码

这些都被 kotlin 看作的 模块

一个文件可以看作一组编译的文件组成的集合

为什么要有 internal 而不是包内访问 default?

java 默认包类存在的问题:

假如有一个包类 String, 包路径是 java.lang, 并被打包成库src.jar

package java.lang.String;

/* public */ class String {
    public void print() {}
    /**
    内容略
    */
}

此时我们要在库外部使用 String 类的功能print , 只有两种方式

  1. 导入 src.jar 库, 然后 copy 库中的String 类的源码, 放在自己的包路径中
  2. 导入src.jar, 在我们的项目中创建 java.lang包, 此时我们可以在该包路径中定义类, 并直接使用 String

上一次我这么用还是在探讨 jvm双亲委派机制

kotlin 中 没有选择 java 的包内机制, 而是模块内机制, 只和 该类 一起编译的其他 kotlin 文件可见

而开发工程和第三方类库不属于同一个模块, 此时还要使用该类的话, 只能拷贝源码了

java中没有文件内私有类, kotlin

Java 中没有文件内私有类(private class) 而 kotlin中有

主要的问题是 java 中一个文件只能有一个大类(public类), 可以有private 的子类

kotlin 中, 可以有一个大类, 而大类下面还可以写顶层函数或属性

class BMWCar(val name: String) {
   private val bMWEngine: BMWEngine = BMWEngine()
}

private class BMWEngine {
   fun engineName() {
      println("BMWEngine")
   }
}
kotlin中没有包作用域概念

kotlin中没有包作用域, 所以 javaprotected 到了 kotlin 中没有访问包作用域的功能, 或者说没有 internal 的功能(internal模块作用域)

所以kotlin 的 protected 标记的类在同一个包的其他文件中无法使用

三、kotlin的类和对象(二)_第4张图片

构造函数

要指定⼀个类的的主构造函数的可⻅性,使⽤以下语法(注意你需要添加⼀个显式 constructor 关键字):

class C private constructor(a: Int) { …… }

这⾥的构造函数是私有的。默认情况下,所有构造函数都是 public ,这实际上等于类可⻅的地⽅它就可⻅

局部变量

局部变量, 函数和类不支持可见性修饰符

模块

可⻅性修饰符 internal 意味着该成员只在相同模块内可⻅, 而一个模块是编译在一起的一堆kotlin文件, 比如:

  • 一个 idea 模块
  • 一个maven 项目
  • 一个 gradle 源集(例外是 test 源集可以访问 maininternal 声明)
  • ⼀次 Ant 任务执⾏所编译的⼀套⽂件
internal class Button : View {
    private fun yell() = println("Hey!")

    protected fun whisper() = println("Let's go!")
}

fun Button.giveSpeech() { // 错误, 企图把public 类 转成 internal 类
    println(this::class.java)
}

internal 比较尴尬, 翻译成 Java 字节码后被认为是 public

密封类☆

密封类和枚举类对应, 密封类是尽量罗列出所有的有限子类(类型多), 而枚举类是尽量罗列出所有的对象(就是对象多), 而他比枚举类好的地方在于, 密封类子类的属性 在 数量 和 类型上可以不同

使用关键字 sealed 修饰符作为声明密封类的方法

所有继承密封类的子类都必须放在同一个文件中

sealed class Expr

data class Const(val number: Double) : Expr() {
}
data class Sum(val e1: Expr, val e2: Expr) : Expr()

object NotANumber : Expr()

fun main(args: Array<String>) {
   // 应用场景
   fun eval(expr: Expr): Double = when(expr) {
      is Const -> expr.number
      is Sum -> eval(expr.e1) + eval(expr.e2)
      NotANumber -> Double.NaN
   }
}

密封类可以看成是一个功能强大的枚举类

内部类和嵌套类的序列化问题 ★

前面说过的: kotlin默认是 嵌套类, 不持有外部类的 this引用, 类似于 static修饰的

内部类: 持有外部 this, 需要使用 inner 声明为内部类

kotlin 类内部可以有类, 而内外类的关系有两种, 一种叫嵌套类(默认的), 另一种叫内部类(需要使用 inner 修饰符)

kotlin的嵌套类反编译成 java 后显示是 static , 这样的好处在于下面这个案例

interface State : Serializable

interface View {
    fun getCurrentState(): State
    fun restoreState(state: State)
}

这时候如果是 java 代码的话, 实现方式:

public class Button02 implements View {
    @Override
    public State getCurrentState() {
        return new ButtonState(20, "name");
    }
    
    @Override
    public void restoreState(State state) {
    }
    
    class ButtonState implements State {
        private Integer age;
        private String name;
        
        public ButtonState(Integer age, String name) {
            this.age = age;
            this.name = name;
        }
    }
    
    
    public static void main(String[] args) throws Exception {
        Button02 button02 = new Button02();
        System.out.println(button02.getCurrentState());
        
        ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("D://test.txt"));
        // 他会在这里报错 NotSerializableException
        outputStream.writeObject(button02.getCurrentState());
        outputStream.flush();
        outputStream.close();
    }
}

在java中单独的 ButtonState 是可以序列化的, 但此处的 ButtonState 是内部类, 内部类偷偷的藏了个 外部类的 this 对象, 该对象不支持 序列化 , 所以报错了

只要把 ButtonState 改成 static 后就不会报错了

static class ButtonState implements State

而在 kotlin 中就不会出现这种问题

kotlin写在类内部的类, 默认是 嵌套类, 同时也是 static 类, 这样内部不会存放 外部类的 this

private class Button01 : View {
    lateinit var buttonState: State

    override fun getCurrentState(): State = ButtonState(12, "haha")

    override fun restoreState(state: State) {
        this.buttonState = state
    }

    private class ButtonState(_age: Int, _name: String) : State {
        val age: Int = _age
        val name: String = _name
    }
}

fun main() {
    val button01 = Button01()
    with(ObjectOutputStream(File("""D:\test.txt""").outputStream())) {
        writeObject(button01.getCurrentState())
        flush()
        close()
    }
//    ObjectOutputStream(File("""D:\test.txt""").outputStream()).apply {
//        writeObject(button01.getCurrentState())
//        flush()
//        close()
//    }
}

而 kotlin 使用 inner 修饰符定义内部类

inner class ButtonState02 {
}

image.png

类委托 ★

委托有很多, 这里只讲类委托, 后续再整个讲一遍

是什么?

一个类借助另一个类的对象实现接口的函数, 说白了就是借鸡生蛋

下面是不委托的情况下:

class DelegateCollectionDemo01<T> : Collection<T> {
    private val innerList = arrayListOf<T>()

    override val size: Int
        get() = innerList.size

    override fun contains(element: T): Boolean = innerList.contains(element)

    override fun containsAll(elements: Collection<T>): Boolean= innerList.containsAll(elements)

    override fun isEmpty(): Boolean = innerList.isNotEmpty()

    override fun iterator(): Iterator<T> = innerList.iterator()
}

下面的委托的情况:

class DelegateCollectionDemo02<T>(private val innerList: ArrayList<T> = arrayListOf()) : Collection<T>  by innerList { }

fun main() {
    val collection01 = DelegateCollectionDemo01<Int>()
    val collection02 = DelegateCollectionDemo02<Int>()
}

核心代码:

class DelegateCollectionDemo02(private val innerList: ArrayList = arrayListOf()) : Collection by innerList

而它实现的函数都是 Collection 接口的函数, 而不是 innerListArrayList的函数

有什么好处? (使用场景)

  1. 源码变少了
  2. 类的函数和 接口 Collection 的函数一致, 如果Collection 被更改或者新增了新的函数, DelegateCollection 也不用修改, 代码自动生成的
  3. 如果有些函数不想用 innerList 对象的函数, 那么可以 override 重写改函数, 添加自己的方法
class DelegateCollectionDemo02<T>(private val innerList: ArrayList<T> = arrayListOf()) : Collection<T>  by innerList {
    override fun isEmpty(): Boolean = innerList.isNotEmpty()
}

注意: 委托类不能委托接口默认函数, 因为 kotlin 接口默认没有默认函数, 而是使用静态类实现类似效果

object 关键字 ★

kotlin中没有 staitc关键字, 它引入了新的方法 object 关键字, 它可以完美的代替 static 的所有场景, 并且添加了很多新的功能

三、kotlin的类和对象(二)_第5张图片

上面的代码虽然没什么问题, 但是 static 和 普通 方法的代码混在在一起, 比较难以区分, 虽然它有 static 作为标记

static 方法是 类 的方法, 而普通方法是对象的方法, 这两有着本质的区别

所以在 kotlin中做了区分

伴生对象

表示跟随着类对象而出现, 它是属于这个类的Class对象所拥有, 因此它是个单例对象, 伴生对象需要声明在类的内部, 在类被加载时初始化

class Prize(
    val name: String,
    val count: Int,
    val type: Int
) {
   companion object {
      val TYPE_REDPACK = 0
      val TYPE_COUPON = 1
      fun isRedpack(prize: Prize): Boolean {
         return prize.type == TYPE_REDPACK
      }
   } 
}

fun main() {
   val prize = Prize("红包", 10, Prize.TYPE_REDPACK)
   println(Prize.isRedpack(prize))
}

可以看到静态的属性和方法都被存储到 companion object 中了, 代码变得更加的清晰

并且我们在调用 companion object 内的 isRedpack方法也非常的方便Prize.isRedpack(prize), 不需要Prize.Companion.isRedpack(prize)

伴生对象还可以实现工厂方法模式
class Prize(val name: String, val count: Int, val type: Int) {
   companion object {
      val TYPE_COMMON = 1
      val TYPE_REDPACK = 2
      val TYPE_COUPON = 3
      val defaultCommonPrize = Prize("ptjp", 19, Prize.TYPE_COMMON)
      fun newRedpackPrize(name: String, count: Int) = Prize(name, count, Prize.TYPE_REDPACK)
      fun newCouponPrize(name: String, count: Int) = Prize(name, count, Prize.TYPE_COUPON)
      fun defaultCommonPrize() = defaultCommonPrize
   }
}

fun main() {
   val redpackPrize = Prize.newRedpackPrize("红包", 10)
   val couponPrize = Prize.newCouponPrize("十元代金券", 10)
   val commonPrize = Prize.defaultCommonPrize()
}
伴生对象实现接口▲
class CompanionObjectDemo02 {

    interface ObjectFactory<T> {
        fun writeObject(t: T)
        fun loadObject(text: String = ClassLoader.getSystemResource("").path + "person02.txt"): T
    }

    open class Person(val name: String) : Serializable {
        companion object : ObjectFactory<Person> {
//            override fun loadObject(text: String): Person = File(text).let { it ->
//                if (!it.exists()) it.createNewFile()
//                ObjectInputStream(it.inputStream()).use {
//                    it.readObject() as? Person ?: throw Exception("文件加载失败")
//                }
//            }

            override fun loadObject(text: String): Person = File(text).run {
                myExists()
                ObjectInputStream(inputStream()).use {
                    it.readObject() as? Person ?: throw Exception("文件加载失败")
                }
            }

            private fun File.myExists() {
                if (!exists()) createNewFile()
            }

            override fun writeObject(t: Person) = with(File(ClassLoader.getSystemResource("").path + "person02.txt")) {
                myExists()
                ObjectOutputStream(outputStream()).use {
                    it.writeObject(t)
                }
            }

//            override fun writeObject(t: Person) = File(ClassLoader.getSystemResource("").path + "person02.txt").run {
//                myExists()
//                ObjectOutputStream(outputStream()).use {
//                    it.writeObject(t)
//                }
//            }
        }
    }
}

fun main() {
    CompanionObjectDemo02.Person.writeObject(CompanionObjectDemo02.Person("haha"))
    val person = CompanionObjectDemo02.Person.loadObject()
    println(person)
}

@JvmStatic, 但该注解不能添加到 objects 或者 companion object 之外的地方, ① 如果注解到字段上, 该字段的 get/set 函数变成静态函数; ② 如果修饰到函数上, 函数将变成静态的

@JvmField, 添加了该注解, ①字段访问修饰符将从 private 变成 public; ② 该注解可以在伴生对象以外的地方使用

伴生对象的扩展▲
class CompanionObjExtensionDemo01 {
    class Person(val name: String) : Serializable {
        companion object {
        }
    }
}

private fun File.myExists() {
    if (!exists()) createNewFile()
}

private fun CompanionObjExtensionDemo01.Person.Companion.loadObject(path: String = ClassLoader.getSystemResource("").path + "person03.txt"): CompanionObjExtensionDemo01.Person =
    with(File(ClassLoader.getSystemResource("").path + "person03.txt")) {
        myExists()
        ObjectInputStream(inputStream()).use {
            it.readObject() as? CompanionObjExtensionDemo01.Person ?: throw Exception("文件加载失败")
        }
    }

private fun CompanionObjExtensionDemo01.Person.Companion.writeObject(t: CompanionObjExtensionDemo01.Person) =
    with(File(ClassLoader.getSystemResource("").path + "person03.txt")) {
        myExists()
        ObjectOutputStream(outputStream()).use {
            it.writeObject(t)
        }
    }

fun main() {
    val person = CompanionObjExtensionDemo01.Person("haha")
    CompanionObjExtensionDemo01.Person.writeObject(person)
    val person1 = CompanionObjExtensionDemo01.Person.loadObject()
    println("person = ${person.name}, person01 = ${person1.name}")
}

单例 ▲

在 java 中实现单例的方法有很多:

public class Singleton {  
    private static Singleton instance = new Singleton();  
    private Singleton (){}  
    public static Singleton getInstance() {  
    return instance;  
    }  
}

public class Singleton {  
    private volatile static Singleton singleton;  
    private Singleton (){}  
    public static Singleton getSingleton() {  
    if (singleton == null) {  
        synchronized (Singleton.class) {  
            if (singleton == null) {  
                singleton = new Singleton();  
            }  
        }  
    }  
    return singleton;  
    }  
}

public class Singleton {  
    private static class SingletonHolder {  
    private static final Singleton INSTANCE = new Singleton();  
    }  
    private Singleton (){}  
    public static final Singleton getInstance() {  
        return SingletonHolder.INSTANCE;  
    }  
}

public enum Singleton {  
    INSTANCE;  
    public void whateverMethod() {  
    }  
}

而在 kotlin 中由于 object的存在, 我们可以直接用它来实现单例

object Singleton {
}

java源码:

public final class Singleton {
   @NotNull
   public static final Singleton INSTANCE;

   private Singleton() {
   }

   static {
      Singleton var0 = new Singleton();
      INSTANCE = var0;
   }
}

这种一种饿汉模式, 只要类被加载就会直接实例化单例

单例对象还可以继承别的接口或者类

object CaseInsensitiveFileComparator : Comparator<File> {
    override fun compare(o1: File, o2: File): Int = o1.path.compareTo(o2.path, true)
}
对象表达式:改变写法的匿名内部类★
private interface ObjectFactory<T> {
    fun writeObject(t: T)
    fun loadObject(text: String = ClassLoader.getSystemResource("").path + "person02.txt"): T
}

private class Person02(val name: String) : Serializable

private fun File.myExists() {
    if (!exists()) createNewFile()
}

fun main() {
    val obj: ObjectFactory<Person02> = object : ObjectFactory<Person02> {
        override fun writeObject(t: Person02) =
            with(File(ClassLoader.getSystemResource("").path + "person04.txt")) {
                myExists()
                ObjectOutputStream(outputStream()).use {
                    it.writeObject(t)
                }
            }

        override fun loadObject(text: String): Person02 =
            with(File(ClassLoader.getSystemResource("").path + "person04.txt")) {
                myExists()
                ObjectInputStream(inputStream()).use {
                    it.readObject() as? Person02 ?: throw Exception("文件加载失败")
                }
            }
    }
    val person02 = Person02("haha")
    obj.writeObject(person02)
    val person021 = obj.loadObject()
    println("person02 = ${person02.name}, person021 = ${person021.name}")
}

注意:

  1. 匿名内部类不是单例, 每次表达式完成都会产生一个新的对象
  2. 匿名内部类可以访问外部函数的变量, 并且访问并没有 final 限制, 可以直接在匿名内部类中修改其值

总结: object 关键字如果 借助 : 修饰表示给 object 加上该类型, 而整体来看就是 new 一个该接口类型的子类对象, 这样我们可以得出结论, object 如果确定了类型, 那功能是定义一个类, 并new出一个对象, 如果修饰类名则默认为定义了个类, 并 new 了个单例

你可能感兴趣的:(kotlin,android,kotlin,android,开发语言)