三、kotlin的类和对象(二)

1,255 阅读15分钟

继承 ★

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

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

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

(2) 子类最终都会调用到父类的主构造函数用于初始化, 子类主构造函数默认都会调用到父类主构造函数, 所以子类没有主构造函数, 那么次构造函数可以用 super 调用父类的主/次构造函数, 如果子类有构造函数就用 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) {}
}

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

覆盖方法(重写方法)

要继承用 open 标记父类

函数要被重写, 父类函数要标记 open

子类重写父类函数, 要使用 override 标记函数

子类不再让open的函数再次被重写, 添加 final 标记该函数

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

覆盖属性

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

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

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

在字段上: (字段可见性永远是 private)

① 使用 val 的话, 字段默认是 private final, 且get/set方法只生成 public final get

② 使用 var 修饰的话, 则是 private 且生成 public final get/set 函数

val ==> private final 字段 ==> public final get/set函数
var ==> private 字段 ==> public final get/set函数

如果在字段上加上 open 的话 , 影响的是 get/set 函数, 使用上 open 函数 get/setfinal 直接消失

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

  • 为什么?

因为父类使用 val 属性的话, 该属性只有 get 函数, 而子类默认也可以使用父类该属性的 get 函数, 如果子类使用 var 重写该属性的话, 该属性有了 get/set 函数, 此时不会有任何问题, 子类顶多会重写掉父类的 get 函数, 并且添加 set 函数

如果父类使用 var 修饰属性的话, 该属性有 get/set 函数, 子类会继承使用权, 使用 get/set 函数, 但如果子类使用 val 重写该属性的话, 此时, 子类只想让该属性有 get 函数, 但实际上却有子类的 get 函数和 父类的 set 函数, 明显 val 字段加的没意义, 所以报错

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
}

(1) 调用父类的属性或者方法一般使用 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...")

抽象类★

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

可见性修饰符

类、对象、接⼝、构造函数、⽅法、属性和它们的 setter 都可以有 可⻅性修饰符(getter 总是与属性有着相同的可⻅性)

在 Kotlin 中有这四个可⻅性修饰符:private 、protected 、internal 和 public, 如果没有显示的指定可见性修饰符, 则默认为 public

  • 如果不指定可见性修饰符, 默认顶层声明都是public
  • 如果声明为 private , 那么只能在该文件内使用
  • 如果声明为 internal, 那么他将在该模块任何地方使用(如同java的 default)
  • protected 包内不支持该修饰符

类和接口

  • private: 表明该类内部可见, 包括其内部的所有成员 只能在该类内使用
  • protected: 表明该类内的成员在整个类族的子类下可见
  • internal: 在整个模块可见
  • public: 在任何地方可见

和java相同, 外部类不能访问内部类的 private

如果覆盖一个 protected 成员, 并且没有指定可见性修饰符, 则默认还是 protected 成员

open class Outer {
    private val a = 1
    protected open val b = 2
    internal val c = 3
    val d = 4  // 默认 public
    
    protected class Nested {
        public val e: Int = 5
    }
}

class Subclass : Outer() {
    // a 不可⻅
    // b、c、d 可⻅
    // Nested 和 e 可⻅
    override val b = 5   // “b”为 protected
}

class Unrelated(o: Outer) {
    // o.a、o.b 不可⻅
    // o.c 和 o.d 可⻅(相同模块)
    // Outer.Nested 不可⻅,Nested::e 也不可⻅
}

构造函数

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

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

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

局部变量

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

模块

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

  • 一个 idea 模块
  • 一个maven 项目
  • 一个 gradle 源集(例外是 test 源集可以访问 main 的 internal 声明)
  • ⼀次 <kotlinc> 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)
}

数据类★

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

data class User(val name: String, val age: Int)
public final class User {
   @NotNull
   private final String name;
   private final int age;

   @NotNull
   public final String getName() {
      return this.name;
   }

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

   public User(@NotNull String name, int age) {
      Intrinsics.checkNotNullParameter(name, "name");
      super();
      this.name = name;
      this.age = age;
   }

   @NotNull
   public final String component1() {
      return this.name;
   }

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

   @NotNull
   public final User copy(@NotNull String name, int age) {
      Intrinsics.checkNotNullParameter(name, "name");
      return new User(name, age);
   }

   // $FF: synthetic method
   public static User copy$default(User var0, String var1, int var2, int var3, Object var4) {
      if ((var3 & 1) != 0) {
         var1 = var0.name;
      }

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

      return var0.copy(var1, var2);
   }

   @NotNull
   public String toString() {
      return "User(name=" + this.name + ", age=" + this.age + ")";
   }

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

   public boolean equals(@Nullable Object var1) {
      if (this != var1) {
         if (var1 instanceof User) {
            User var2 = (User)var1;
            if (Intrinsics.areEqual(this.name, var2.name) && this.age == var2.age) {
               return true;
            }
         }

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

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

  • equals() / hashCode()
  • toString() 格式是 "User(name=John, age=42)" ;
  • componentN() 函数 按声明顺序对应于所有属性;
  • copy() 函数。 为了确保⽣成的代码的⼀致性以及有意义的⾏为,数据类必须满⾜以下要求:
  • 主构造函数需要⾄少有⼀个参数;
  • 主构造函数的所有参数需要标记为 val 或 var ;
  • 数据类不能是抽象、开放、密封或者内部的;
  • (在1.1之前)数据类只能实现接⼝。 此外,成员⽣成遵循关于成员继承的这些规则:
  • 如果在数据类体中有显式实现 equals()hashCode() 或者 toString() ,或者这些函数在⽗类中有 final 实现,那么不会⽣成这些函数,⽽会使⽤现有函数;
  • 如果超类型具有 opencomponentN() 函数并且返回兼容的类型,那么会为数据类⽣成相应的函数,并覆盖超类的实现。如果超类型的这些函数由于签名不兼容或者是 final ⽽导致⽆法覆盖,那么会报错;
  • 从⼀个已具 copy(……) 函数且签名匹配的类型派⽣⼀个数据类在 Kotlin 1.2 中已弃⽤,并且在 Kotlin 1.3 中已禁⽤。
  • 不允许为 componentN() 以及 copy() 函数提供显式实现。

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

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

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

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

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

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

复制

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

fun main(args: Array<String>) {
   val user: User = User("zhazha", 1)
   val user1 = user.copy(name = "xixi")
   println(user)
   println(user1)
}

部分属性填写完毕后, 其他属性仍旧保持以前的值

数据类与解构声明

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

val j = User("zhazha", 22)
val (name, age) = j
println("name = $name, age = $age")

密封类☆

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

使用关键字 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
   }
}

内部类和嵌套类 ★

默认是 嵌套类, 内~: 持有外部 this, 需要使用 inner 声明为内部类 嵌~: 不持有, 是 static

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

单独的 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<T>(private val innerList: ArrayList<T> = arrayListOf()) : Collection<T> 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()
}

object 关键字 ★

定义一个类并创建类的实例

使用场景:

  • 对象声明是定义单例的一种方式.
  • 伴生对象可以持有工厂方法和其他与这个类相关, 但在调用时并不依赖类实例的方法. 它们的成员可以通过类名来访问.
  • 对象表达式用来替代 Java 的匿名内部类.

单例 ▲

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

伴生对象 ★

伴生对象其实就是一个静态的 公开的 final 的 对象, 而它的方法都不是静态的, 伴生对象之所以看起来像是直接用类名调用, 其实不是, 它底层是Companion的静态对象, 但编译器可以让它看起来好像不需要调用Companion一样, 在伴生对象作用域内定义的属性也是静态的

使用 companion object 作为标记伴生对象的关键字

private class _User private constructor(val nickName: String) {
    companion object {
        fun newSubscribingUser(email: String): _User {
            return _User(email.substringBefore('@'))
        }

        fun newFacebookUser(accountId: Int): _User {
            fun getFacebookById(accountId: Int) = accountId.toString()
            return _User(getFacebookById(accountId))
        }
    }
}

fun main() {
    val user = _User.newSubscribingUser("2033@qq.com")
    println(user.nickName)
}

★ 伴生对象是一个单例的静态对象, 伴生对象如果没有名字, 则编译器会生成一个叫 Companion 的静态对象, 并且定义了一个同名的静态对象 Companion, 类名和对象名一致, 代码如下:

private class User02 private constructor(val nickName: String) {
    companion object {
        fun newSubscribingUser(email: String): User02 {
            return User02(email.substringBefore('@'))
        }

        fun newFacebookUser(accountId: Int): User02 {
            fun getFacebookById(accountId: Int) = accountId.toString()
            return User02(getFacebookById(accountId))
        }
    }
}

反编译成 java 是这样:

private final String nickName;
public static final User02.Companion Companion = new User02.Companion((DefaultConstructorMarker)null);
public static final class Companion {
   public final User02 newSubscribingUser(@NotNull String email) {}
   public final User02 newFacebookUser(int accountId) {}
}

如果有伴生对象有名字, 则把 Companion 的类和相对应的对象全部改成那个名字, 比如下面这段代码就改成了 Loader 静态类 和 Loader 静态对象

open class Person(val name: String) : Serializable {
    fun toText(path: String = ClassLoader.getSystemResource("").path + "person.txt"): Person {
        val file = File(path)
        if (!file.exists()) file.createNewFile()
        return ObjectOutputStream(FileOutputStream(path)).use {
            it.writeObject(this)
            this
        }
    }

    companion object Loader {
        val path: String = ClassLoader.getSystemResource("").path
        fun fromText(objectPath: String = path + "person.txt") =
            ObjectInputStream(FileInputStream(objectPath)).use {
                it.readObject() as? Person ?: throw Exception("无法读取到对象")
            }
    }
}

记得看 path 对象, 它在底层也将被定义成静态字段, 不过它的字段不属于 Loader 类, 属于 Person 类, 但是它的 get/set 属于 Loader 类, 而且 path 属性的字段 和 接口的属性一样, 如果 get/set 函数和该对象没什么关系, 则不会创建path静态字段, 仅保留 get/set 函数在 Loader 静态类中

image.png

image.png

注意:

伴生对象的类是 final , 属性和方法也是 final 而且也无法用open修改伴生对象的 final 所以子类无法重写

注意:

★ 伴生对象里面的函数不是静态函数, 它仅仅提供了一个静态类和一堆静态对象, fromTextgetPath 都不是静态的

image.png

这些函数最后都会被 伴生对象(注意是 对象 对象 对象) 调用, 所以根本不需要静态, 伴生对象不可以简单的认为他是 java 的 static 静态代码块

如果需要使得伴生变量作为静态函数需要加上 @JvmStatic, 让函数变成Person的静态函数和非静态两个函数(在伴生对象函数的基础上多了个Person的静态函数)

注意:

val person03 = Person.fromText()

也会变成

Person person03 = Person.Loader.fromText$default(Person.Loader, (String)null, 1, (Object)null);

ClassLoader.getSystemResource("").path 将被写入到 static 静态代码块中

image.png

伴生对象实现接口▲

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

对象表达式:改变写法的匿名内部类★

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 了个单例