• 三、kotlin的类和对象(一)


    类 ★

    属性★

    属性 = 字段 + setter/getter

    声明属性

    class Address {
       var name: String = "Holmes, Sherlock"
       var street: String = "Baker"
       var city: String = "London"
       var state: String? = null
       var zip: String = "123456"
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    var 和 val

    var 定义一个可读可写的属性, val 定义一个只读的属性

    this.name 就相当调用了 getName 函数, this.name = "嘿嘿", 就相当于调用了 setName("嘿嘿")

    kotlin中所有的字段都需要初始化, 不像 java 那样字段有默认值

    • var allByDefault: Int? 错误:需要显式初始化器,隐含默认 getter 和 setter

    • var initialized = 1 类型 Intkotlin生成 getter 和 setter

    • val inferredType = 1 类型 Int 、默认 getter

    • val simple: Int? 错误: 需要在构造函数中初始化或者添加初始化值, 类型 Intkotlin生成 getter

    自定义访问器

    给字段下面加上自定义的 getset

    class Address {
       val name: String
          get() {
              println("zhazha")
              return field
          }
       
       var age: Int = 0
          get() = field
          set(value) {
             field = value
          }
    }
    
    var setterVisibility: String = "abc"
        private set // 此 setter 是私有的并且有默认实现
    
    var setterWithAnnotation: Any? = null
        @Inject set // ⽤ Inject 注解此 setter
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    幕后字段

    无限递归问题:

    class Teacher {
       var name: String
          get() = this.name
          set(value) {
             this.name = value
          }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    error: get() = this.namethis.name = value

    get() = this.name 会调用 namegetter 函数, 然后又遇到 this.name 再次调用 getter 无限循环

    同理: this.name = valuethis.name = xxx 会调用 this.namesetter 函数, 接着无限递归

    解决办法是: field 幕后字段

    class Teacher {
       var name: String = ""
          get() = field
          set(value) {
             field = value
          }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    关于 延迟属性 的研究

    属性未必已经需要主动初始化, 比如 spring bean 注入就是, 但 kotlin 强制要求程序员初始化属性, 这导致很多二次赋值的问题, 虽然影响不大, 但对于有强迫症的人来说简直不共戴天

    这里推荐几种方式进行属性延迟赋值

    lateinit延迟

    非基本数据类型推荐使用

    class ServiceImpl : Service {
        @Resource
        private lateinit var dao: DaoImpl
    }
    
    • 1
    • 2
    • 3
    • 4

    但这种方式只能用于 非基础数据类型var 标记的属性

    使用可空类型

    如果该属性可能为 null, 推荐使用

    class ServiceImpl : Service {
        @Resource
        private var dao: DaoImpl? = null
    }
    
    • 1
    • 2
    • 3
    • 4

    lazy

    需要延迟调用的 val 推荐使用

    class Bird(val weight: Double, val age: Int, val color: String) {
        val sex: String by lazy {
            if (color == "yellow") "male" else "female"
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    lazy 的原理
    public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
    
    • 1

    lazy 函数, 该函数返回Lazy<T>对象, lazy还有一个 lambda表达式 的参数 该表达式 返回一个 T 类型的对象

    Lazy<T> 这个 T 就是 lambda 表达式返回的

    访问sex会调用 getValue 返回这个 T(仅在第一次调用时才会调用)

    这是委托 by 的能力, 读取就固定调用 getValue函数, 写入会固定调用setValue函数

    public inline operator fun <T> Lazy<T>.getValue(thisRef: Any?, property: KProperty<*>): T = value
    
    • 1

    而这个 valueget 函数将会被执行

    override val value: T
        get() {
            val _v1 = _value
            if (_v1 !== UNINITIALIZED_VALUE) {
                @Suppress("UNCHECKED_CAST")
                return _v1 as T
            }
    
            return synchronized(lock) {
                val _v2 = _value
                if (_v2 !== UNINITIALIZED_VALUE) {
                    @Suppress("UNCHECKED_CAST") (_v2 as T)
                } else {
                    val typedValue = initializer!!()
                    _value = typedValue
                    initializer = null
                    typedValue
                }
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    如果没有对 lazy 函数添加一个同步属性, 始终都会执行到

    val typedValue = initializer!!()
    _value = typedValue
    initializer = null
    typedValue
    
    • 1
    • 2
    • 3
    • 4

    这段代码

    这里的 initializer 就是下面的返回值 T

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N6Mp9dvc-1655378804210)(https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2b8e8c7d6ef54be5b058c611ac407141~tplv-k3u1fbpfcp-watermark.image?)]

    然后返回 typedValue 也就是 lambda 的返回值

    by lazy {} 方式需要注意

    image.png

    1. 只能用于 val 修饰的变量, 不能通过 var 来声明
    2. 只有在首次被调用时才会初始化

    让基本数据类型也拥有延迟效果

    基本数据类型, 推荐使用委托

    使用委托的形式延迟初始化基本数据类型

    by Delegates.notNull<T>()

    class Bird(private val color: String) {
       val sex: String by lazy {
          if (color == "yellow") "male" else "female"
       }
       lateinit var name: String
       var age: Int by Delegates.notNull<Int>()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    kotlin 中, 不推荐 java 的那种方式生成构造函数, 而是使用默认参数配合构造函数

    幕后属性

    下面这段代码实现了一个延迟属性功能

    private var _table: Map<String, Int>? = null
    public val table: Map<String, Int>
        get() {
            if(_table == null) {
                _table = HashMap()
            }
            return _table ?: throw AssertionError("Set to null by another thread")
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    个人认为 幕后属性 可以使用在 data class 中配合使用

    构造函数 ★

    ★ 在kotlin中构造函数有主构造函数, 初始化代码块(init)次构造函数

    主构造函数 ★

    class Person constructor(val name: String) {}
    
    • 1

    换成 java 源码 类似于:

    public final class Person {
        private final String name;
        
        public Person(String name) {
            this.name = name;
        }
        
        public final String getName() {
            return this.name;
        }
        
        // public final void setName(String name) {
        //     this.name = name;
        // } 
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    为什么需要主构造函数?

    kotlin设计主构造函数的可能是简化代码吧

    class Foo { 
        val bar: Bar
        constructor(barValue: Bar) {
            bar = barValue
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    class Foo(val bar: Bar)

    原型是这样: class Foo constructor(val bar: Bar) 只不过constructor 关键字在没有注解, 类似private 这样的访问修饰符可以省略

    class Person private /* @Inject */ constructor(name: String) {
       val name: String = name.uppercase()
    }
    
    • 1
    • 2
    • 3

    主构造函数带来的问题

    1. 主函构造函数增加了新手入门的难度( 很奇葩的设计
    2. 添加了主构造函数, 还需要考虑构造函数的顺序 ( 奇葩
    3. 主构造函数内部不能有别的操作, 只有赋值操作, 如果还有别的操作还需要使用 init 代码块, 在 init 代码块中初始化 (超级奇葩
    4. 如果类的属性增多, 你会发现一部分属性在主构造函数的小括号内, 一部分属性在类的作用域内, 阅读性变低 (更加奇葩
    5. 看截图

    image.png

    主构造函数必须最先执行, 次构造函数次之, 所以在主构造函数中无法初始化 age1age2 无法被初始化, 所以报错

    解决方法是:

    1. age1age2 放入主构造函数的函数参数列表中
    open class A(
        var name: String
        val age1: Int
        val age2: Int
    ) {
        constructor(_age: Int) : this("name", _age, _age) {
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    1. init 代码块
    open class A(var name: String) {
        val age1: Int
        val age2: Int
        init {
            // 这里的 0 可以改成在主构造函数传入参数 比如: open class A(var name: String, _age: Int) , 这样次构造函数的 this("name") 就需要更改了 this("name", _age), 次构造函数后面的函数代码块就不需要了
            this.age1 = 0
            this.age2 = 0
        }
        constructor(_age: Int) : this("name") {
            this.age1 = _age
            this.age2 = _age
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    有新手会问为什么不用 lateinit, 那是因为 lateinit 只能用于 var 且非基本类型(Int, Double这种基础类型)上

    1. 删掉主构造函数

    image.png

    写好字段, 使用快捷键, 就可以创建上面的次构造函数

    image.png

    事情解决了!!!

    当然我们也可以灵活运用 主构造函数 + init代码块 + 参数的默认值

    image.png

    让你选你会选哪一种???

    我的结论: 怎么简单怎么来

    主构造函数和初始化语句块(init)

    1. 是什么?
    class Person(_nickName: String) {
        val nickName: String
        // 这就是 init 初始化代码块
        init {
            this.nickName = "${_nickName.lenght} - ${_nickName}"
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    注意1, constructor 修饰符省略掉了, 有前提:

    1. 没有注解.
    2. 没有可见修饰符.

    注意2: 主构造函数上 Person(val nickName: String)Person(_nickName: String) 的区别在于 带 val/var 的将变成nickName属性, 不带的变成_nickName构造函数参数

    1. 为什么需要初始化代码块?

    kotlin主构造函数除了 this.nickName = nickName 这些赋值操作外, 没有任何的操作, 所以需要初始化代码块进行其他操作 this.nickName = "${_nickName.lenght} - ${_nickName}", 所以初始化代码块诞生了

    初始化代码块属于主构造函数体的代码之一, 和 主构造函数类作用域内属性 属于同一个作用域

    将其换成 java 源码就知道了:

    class Person {
        private String nickName;
        
        Person(String _nickName) {
            // 主函数自己的代码 
            // balabala.....
            // init 代码块内部的代码 or 主函数之外的字段初始化代码 (按照定义先后顺序)
            this.nickName = "${_nickName.lenght} - ${_nickName} // 这行不是 java 代码
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    构造方法参数默认值

    class Person(val nickName: String , var isSubscribed: Boolean = false)
    
    • 1

    ★ 如果全部是 默认值 会生成一个无参数主构造函数

    class Person(val nickName: String = "", var isSubscribed: Boolean = false)

    子类初始化父类字段 ★

    ★ 子类有责任初始化 父类字段

    open class User(val nickName: String) {}
    class FacebookUser(nickName: String) : User(nickName) {}
    
    • 1
    • 2

    这非常的重要, 子类有责任将值给父类初始化, 父类未初始化的属性必须要子类来初始化(想要子类对象的话)

    继承和实现怎么看?

    interface View {}
    open class Button : View {}
    open class RadioButton : Button() {}
    
    • 1
    • 2
    • 3

    引号继承和实现区别一目了然, 实现直接写上 View , 继承则是调用 父类构造函数 Button() , 一个 没有 () 一个有 ()

    如果不声明任何构造函数, 它会生成一个无参数构造函数

    open class Button
    
    • 1

    定义 private 构造函数

    class Person private constructor(val nickName: String) {}
    
    • 1

    这种类可以使用伴生对象构建并使用, 伴生对象就是类的对象, 而该对象的函数未必是静态的哦, 以后会学到

    当然你还可以写个次构造函数, 在末尾(c++叫初始化成员列表的位置)调用主构造函数

    次构造函数 ★

    class Person(val name: String) {
        var age: Int = 0
        
        // 这个就是次构造函数
        constructor(name: String, age: Int) : this(name) {
            this.age = age
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    构造函数优先级 ★

    主构造函数优先级 高于 init初始化代码块和主构造函数外字段 高于 次构造函数

    class Person(var name: String = "1") {
       var age: Int
       
       init {
          if (this.name == "1") {
             println("主构造函数第一时间初调用了")
          }
          this.name = "3"
          println("init 代码块初调用了")
          this.age = 2
       }
       
       constructor(age: Int) : this() {
          println("次构造函数调用了")
          this.age = age
       }
       
       override fun toString(): String {
          return "Person(name='$name', age=$age)"
       }
       
    }
    
    fun main() {
       val person = Person(age = 4)
       println(person)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    在主构造函数中定义变量(注意不是属性是作为参数的变量), 则可以使用 _ 的方式在区别, 比如: _name 或者 _nickName 等等

    注意:

    init 代码块最后都会成为主构造函数的函数体内部的代码

    class Person(var name: String) {
        // init 和 下面 age 的初始化顺序优先级按照定义顺序判断优先级
        init {
            if (this.name == "haha") {
                this.name = "zhazha"
            }
        }
        val age: Int = 1
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    将会变成

    public final class Person {
        private final String name;
        private final int age;
        
        public Person() {
            this.name = name;
            if (Intrinsics.areEqual(this.name, "haha")) {
                this.name = "zhazha"
            }
            this.age = 1
        }
        
        // get/set ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    注意5: 只有主构造函数可以在小括号内声明成员属性, 次构造函数不允许

    里氏代替原则

    在父子类中, 子类最好只能实现父类抽象的方法, 非抽象的方法能不重写就别重写, 这很符合里氏代替原则

    什么是里氏代替原则?

    他有四种设计原则:

    1. 子类可以实现父类的所有抽象方法, 但非抽象方法子类最好不要再去重写
    2. 子类可以增加自己特有的方法
    3. 当子类的方法实现父类的方法时, 方法的前置条件(即方法的形参)要比父类方法的输入参数更加宽泛
    4. 当子类的方法实现父类的抽象方法时, 方法的后置条件(即方法的返回值)要比父类更严格.

    类的默认修饰符: final

    kotlin 很好的实现了这种原则, 所有默认没有被标记为 open类/方法都会被修饰为 final , 子类全部无法重写, 除非在 类/方法 上添加 open

    当然如果是字段的话, 需要看修饰字段的是 val 还是 var, 如果是 val 会携带 final

    private class TheBird(
       val weight: Double = 500.0,
       var color: String = "blue",
       var age: Int = 1
    ) {
       fun fly() {}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    final class Bird {
       private final double weight;
       @NotNull
       private String color;
       private int age;
    
       public final void fly() {
       }
    
       public final double getWeight() {
          return this.weight;
       }
    
       @NotNull
       public final String getColor() {
          return this.color;
       }
    
       public final void setColor(@NotNull String var1) {
          this.color = var1;
       }
    
       public final int getAge() {
          return this.age;
       }
    
       public final void setAge(int var1) {
          this.age = var1;
       }
    
       public Bird(double weight, @NotNull String color, int age) {
          super();
          this.weight = weight;
          this.color = color;
          this.age = age;
       }
    
       public Bird(double var1, String var3, int var4, int var5, DefaultConstructorMarker var6) {
          if ((var5 & 1) != 0) {
             var1 = 500.0;
          }
    
          if ((var5 & 2) != 0) {
             var3 = "blue";
          }
    
          if ((var5 & 4) != 0) {
             var4 = 1;
          }
    
          this(var1, var3, var4);
       }
    
       public Bird() {
          this(0.0, (String)null, 0, 7, (DefaultConstructorMarker)null);
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57

    类默认 final 真的好么?

    在 kotlin官方有讨论: Classes final by default - Language Design - Kotlin Discussions (kotlinlang.org)

    存在的问题:

    1. spring 框架可能存在问题, 需要重新实现 Spring 框架的部分功能. 比如 Spring 使用注解对一些类进行增强, 由于 kotlin 类不能被继承导致增强失败
    2. 使用 kotlin 编写第三方库, 有些时候需要增强这些库, 需要继承库内的类, 如果是 kotlin 则不可以直接继承

    优点:

    1. 默认 final 比较安全
    2. kotlin推荐你使用扩展而不是继承去增强类
    3. 还可以借助 val 配合 智能类型转换

    同时 kotlin 还提供 sealed 密封类对继承进行限制, 若要继承一个类必须在同一个文件

    image.png

    kotlin 初始化带来 bug 以及解决方案

    private class Demo01 {
        val name: String
        private fun first() = name[0]
        init {
            // first 还没初始化呢, 直接就调用了? 这时候只能 报错 NullPointerException
            println(first())
            name = "zhazha"
        }
    }
    
    fun main() {
        val demo01 = Demo01()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    class Demo02(_name: String) {
        val playerName: String = initPlayerName()
        val name: String = _name
        private fun initPlayerName(): String = name
    }
    
    fun main() {
        val demo02 = Demo02("zhazha")
        println(demo02.playerName) // 最终输出 null
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    解决方案任何属性都需要先初始化再使用

    接口★

    kotlin 中实现一个接口需要实现未实现的方法和未初始化的属性

    这里需要分清楚什么是字段? 什么是属性? 属性 = 字段 + getter/setter

    interface MyInterface {
       var name: String
       
       val age:Int
       
       fun bar()
       
       fun foo() {
          println(this::javaClass)
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    这里需要强调上面的 foo 方法, 该方法在接口中被认为是 默认方法

    默认方法在接口中的实现比较复杂, 这涉及到 kotlin 早期对标的是jdk1.6, 那时的类不允许有默认方法, 所以kotlin的实现方式比较有意思

    kotlin 并未沿用 jdk 8 的接口默认方法

    jdk8 之后, java的 接口可以存在默认方法静态方法

    下面是 java 源码

    public interface MyInterface {
       @NotNull
       String getName();
    
       void setName(@NotNull String var1);
    
       int getAge();
    
       void bar();
    
       void foo();
    
       @Metadata(
          mv = {1, 5, 1},
          k = 3
       )
       public static final class DefaultImpls {
          public static void foo(@NotNull MyInterface $this) {
             String var1 = "zhazha";
             boolean var2 = false;
             System.out.println(var1);
          }
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    kotlin编译器生成了个 DefaultImpls 内部静态类,

    然后以静态的方式写了个了和接口中的 foo 同名函数, 参数传递了个 MyInterface $this

    需要注意 this, 在 interface 中不存在 this 对象, 该 this 是在 object:MyInterface 的时候产生的匿名对象或者是实现该接口的子类 this 对象

    所以我们需要 object: MyInterface 接口:

    fun main() {
        val obj = object: MyInterface {
            override var name: String
                get() = TODO("xxxxx")
                set(value) {}
            override val age: Int
                get() = TODO("xxxxx")
            override fun bar() {
                // to do
            }
        }
        // 默认函数按照需要重写
        obj.foo()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    接口中可以有接口也可以有默认方法还可以有属性(不带字段的属性)

    interface Named {
       val name: String
       
       interface Name {
          val names: String
       }
    }
    
    interface Person : Named {
       val firstName: String
       val lastName: String
       override val name: String
          get() = "$firstName $lastName"
    }
    
    class NameClass(override val names: String) : Named.Name {
    }
    
    data class Employee(override val firstName: String, override val lastName: String) : Person {
       val position: Pair<Double, Double> = Pair(0.0, 0.0)
    }
    
    fun main(args: Array<String>) {
       val employee = Employee("zzz", "ddd")
       println(employee.name)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    接口不允许有记录数据的字段, 所以在接口中定义的字段被kotlin处理成 set/get 方法

    public interface MyInterface {
        @NotNull
        public String getName();
    
        public void setName(@NotNull String var1);
    
        public int getAge();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    接⼝继承

    interface Named {
       val name: String
    }
    
    interface Person : Named {
       val firstName: String
       val lastName: String
       override val name: String
          get() = "$firstName $lastName"
    }
    
    data class Employee(override val firstName: String, override val lastName: String) :Person {
       val position: Pair<Double, Double> = Pair(0.0, 0.0)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    接口的属性

    我在很多地方说都过了, 属性在 kotlin 中表示 getter/setter 至于 field 有没有都无所谓

    val 修饰的属性 默认 filedprivate 修饰 , val 保证只有 getter 没有setter

    如果我们在 setter 前面加上 private, kotlin将不会自动生成 setter

    getter 的可见性修饰符必须与声明字段的可见性修饰符相同

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wYLh8xwQ-1655378804222)(https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/94ebf6af482044f4aacb11b93c0ada70~tplv-k3u1fbpfcp-watermark.image?)]

    如果 getter 必须是 private 则可以这样:

    image.png

    接⼝中的属性默认是抽象属性

    接口不会有字段, 但是可以有抽象属性, 抽象属性是一种没有 field 幕后字段 只有 gettersetter 的属性

    private interface User {
        val nickName: String // 只有 getNickName 函数, 接口不允许有字段
    }
    
    // 主构造属性会被初始化, 所以需要添加字段和 get 函数
    class PrivateUser(override val nickName: String) : User {
    }
    
    class SubscribingUser(val email: String) : User {
        override val nickName: String
            // 重写了 get 访问器, 则不需要字段
            get() = email.substringBefore('@')
    }
    
    class FaceBookUser(val accountId: Int): User {
        // 初始化了 nickName, 生成 get访问器和 nickName字段
        override val nickName = "name: $accountId"
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    子类重写接口属性, 根据子类具体的情况判断是否定义字段, 如果子类重写字段的 get/set 函数没有涉及 field (或者说get/set函数不依赖重写的字段本身)则不会直接定义一个字段, 只有 get/set 函数, 例如:

    class SubscribingUser(var email: String) : User {
        override var nickName: String
            get() = email.substringBefore('@')
            set(value) {
                this.email = value.uppercase()
            }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    上面这段代码就不会产生字段, 直接生成 get/set 函数

    public final class SubscribingUser implements User {
       // 字段只有这一个
       private String email;
       public String getNickName() {}
       public void setNickName(@NotNull String value) {}
       public final String getEmail() {}
       public final void setEmail(@NotNull String var1) {}
       public SubscribingUser(@NotNull String email) {}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    接口属性未必一定需要重写

    interface User {
        val email: String
        val nickName: String
            get() = email.substringBefore('@')
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    上面第一个属性 email 子类必须要重写, 但下面一个 nickName 在子类可以被继承

    函数式接口

    单一抽象方法的接口, 叫函数式接口或者叫SAM接口

    fun interface KRunnable {
        fun invoke()
    }
    
    • 1
    • 2
    • 3

    注意前面的 fun 用来区分 普通接口 和 函数式接口

    java函数式接口需要写上 @FunctionInterface 注解, 来标注, 但不是强迫性的, 而 kotlin 中的函数式接口必须在 interface 之前加上 fun 才能代表函数式接口

    1. java的接口只要只有一个未实现的抽象方法, 都可以被 kotlin 编译器识别为 函数式接口(有多少默认函数无所谓)

    2. 函数式接口可以有函数式接口构造函数

    val kRunnable = KRunnable { println("函数式接口的特点") }
    
    • 1

    特点就是不需要 new , 直接写就行

    非函数式接口不能这样:

    interface IRunnable {
       fun invoke()
    }
    
    • 1
    • 2
    • 3

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TzjnLxyq-1655378804225)(https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8c42999cd2b148cca65c5e2e9b82be3d~tplv-k3u1fbpfcp-watermark.image?)]

    调用函数式接口的方法

    fun interface IntPredicate {
       fun accept(i: Int): Boolean
    }
    
    fun isInt(i: Int, funcType: IntPredicate): Boolean = funcType.accept(i)
    
    fun main(args: Array<String>) {
       val a = 19
       println(isInt(a){
          it is Int
       })
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    接口的多继承

    接口和java中的接口一样, 接口之间可以多继承, 实体类也可以多实现接口

    interface A {}
    interface B {}
    interface C : A, B {}
    class D : A, B {}
    
    • 1
    • 2
    • 3
    • 4

    如果接口 A接口 B 使用有一个相同的方法, fun Hello(): UnitD 发现也没事, 实现只有一个

    interface A {
       fun hello()
    }
    interface B {
       fun hello()
    }
    class D : A, B {
       override fun hello() {
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    还有这么个案例:

    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}"
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

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

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

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

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

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

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

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

    final class Bird implements Flyer, Animal {
       @NotNull
       private final String name;
    }
    
    • 1
    • 2
    • 3
    • 4

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

    private class Bird : Flyer, Animal {
       
       override val name: String
          get() = "xiaobai"
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    image.png

    还有这么个案例:

    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}"
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

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

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

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

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

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

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

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

    final class Bird implements Flyer, Animal {
       @NotNull
       private final String name;
    }
    
    • 1
    • 2
    • 3
    • 4

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

    private class Bird : Flyer, Animal {
       
       override val name: String
          get() = "xiaobai"
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    [外链图片转存中…(img-WzK3NKm4-1655378804227)]

  • 相关阅读:
    RecSysOps: 大规模推荐系统运维最佳实践
    C#学习相关系列之常用符号介绍
    AI在材料科学中的应用
    HTML+CSS篮球静态网页设计(web前端网页制作课作业)NBA杜兰特篮球运动网页
    找不到concrt140.dll无法继续执行此代码的解决方法总结,快速解决dll问题的5种方法
    揭秘OLED透明拼接屏的参数规格:分辨率、亮度与透明度全解析
    零基础自学游戏开发和软件开发先学什么知识点或课程?
    Ajax的基础知识
    2022-08-01
    SpringCloud: feign配置超时时间
  • 原文地址:https://blog.csdn.net/qq_30690165/article/details/125321909