kotlin 实战之面向对象特性全方位总结

Posted 工匠若水

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了kotlin 实战之面向对象特性全方位总结相关的知识,希望对你有一定的参考价值。

工匠若水可能会迟到,但是从来不会缺席,最终还是觉得将自己的云笔记分享出来吧 ~

特别说明,kotlin 系列文章均以 Java 差异为核心进行提炼,与 Java 相同部分不再列出。随着 kotlin 官方版本的迭代,文中有些语法可能会发生变化,请务必留意,语言领悟精髓即可,差异只是语法层面的事情,建议不要过多关注语法。

类的定义

kotlin 的类定义比 java 简单了很多,一般类的定义如下(特殊类下面小节有总结):

//kotlin 类默认修饰符是 public 的,不用再显式指定 public
class MyClass 

//kotlin中一个类的类体没有任何内容则花括号可以省略,如下:
class MyClass

kotlin 中实例化一个类不需要 new 关键字,这点与 java 很不一样。

构造方法及初始化过程

在 kotlin 中一个类可以有一个 primary 构造方法以及一个或多个 secondary 构造方法。

//primary 构造方法是类头的一部分,位于类名后面,可以有若干个参数,缺省修饰符是 public
class TestClass constructor(name: String, age: Int) 
    private val name = name.toLowerCase()
    //初始化代码块中可以直接使用构造方法的参数,给类对象属性赋初值
    init 
        println(age)
    


//私有构造方法的类定义
class TestClass1 private constructor(name: String)

//如果 primary 构造方法没有任何注解或是可见性关键字修饰,则 constructor 关键字可以省略
class TestClass2 (name: String)

多个构造方法声明的写法案例(包含代码执行顺序):

//【工匠若水 加微信 yanbo373131686 联系我,关注微信公众号:码农每日一题  未经允许严禁转载 https://blog.csdn.net/yanbober】

class Person constructor(name: String) 
    private var name: String
    private var age: Int
    private var address: String

    init 
        println(name)   //name 是 constructor 构造方法的参数
        this.name = name
        this.age = 16
        this.address = "zhuhai"
    
    //secondary 构造方法必须要直接或者间接调用其 primary 构造方法
    constructor(name: String, age: Int): this(name) 
        println("$name, $age")
        this.name = name
        this.age = age
        this.address = "hanzhong"
    
    //secondary 构造方法间接调用其 primary 构造方法
    constructor(name: String, age: Int, address: String): this(name, age) 
        this.address = address
    

    fun print() 
        println("name: $this.name, age: $age, address: $address")
    


/**
 运行输出值为:
 ruoshui
 gongjiang
 gongjiang, 18
 android
 android, 19
 name: ruoshui, age: 16, address: zhuhai
 name: gongjiang, age: 18, address: hanzhong
 name: android, age: 19, address: guangdong
 */
fun testRun() 
    var person0 = Person("ruoshui")
    var person1 = Person("gongjiang", 18)
    var person2 = Person("android", 19, "guangdong")
    person0.print()
    person1.print()
    person2.print()

kotlin 类语法糖简写方式对上面案例的简写实现:

//简写:直接构造方法里声明类的成员变量属性
class Person1 (private val name: String,
                private var age: Int,
                private val address: String) 
    fun print() 
        println("name: $this.name, age: $age, address: $address")
    


/**
 运行输出值为:
 name: yan, age: 18, address: guangdong
 */
fun testRun() 
    var person = Person1("yan", 18, "guangdong")
    person.print()

kotlin 的构造方法可以具备默认参数值,这点和 java 很不一样。如果 primary 构造方法的所有参数都有默认值,则编译器还会生成一个额外新的无参数构造方法,并且使用这个构造方法的默认值。具体用法类似如下:

//如果 primary 构造方法的所有参数都有默认值,则编译器还会生成一个额外新的无参数构造方法,并且使用这个构造方法的默认值
class Person2 (private val name: String = "ruoshui",
               private var age: Int = 20,
               private var address: String = "zhuhai") 
    fun print() 
        println("name: $this.name, age: $age, address: $address")
    


class Person3 (private val name: String = "ruoshui",
               private var age: Int) 
    fun print() 
        println("name: $this.name, age: $age")
    


/**
 运行输出值为:
 name: ruoshui, age: 20, address: zhuhai
 name: ruoshui, age: 12
 */
fun testRun() 
    var person = Person2()
    person.print()
    //因为 primary 构造方法参数默认值不全,所以不能使用无参构造
    var person1 = Person3(age = 12)
    person1.print()

继承与重写特性

与 java 类默认修饰符不同的另一点是,kotlin 中所有类默认都是无法被继承的,即 kotlin 中所有类默认都是 final 的。如下案例展示了类的继承:

//【工匠若水 加微信 yanbo373131686 联系我,关注微信公众号:码农每日一题  未经允许严禁转载 https://blog.csdn.net/yanbober】

//默认 class 是 final 的,需要显式添加 open 以便可以被继承
open class Anim (name: String, age: Int)

//kotlin 通过 : 实现 java 的 extends 等关键字
class Dog (name: String, age: Int): Anim(name, age)

//kotlin 中如果一个类没有 primary 构造方法,
//则这个类的每个 secondary 构造方法就需要通过 super 关键字来初始化父类型,
//或是通过其他 secondary 构造方法完成这个任务。
//不同的 secondary 构造方法可以调用父类型不同的构造方法。
open class Anim1 (name: String) 
    init 
        println("Anim1--$name")
    


class Dog1 : Anim1 
    constructor(name: String): super(name) 
        println("dog1-0--$name")
    

    constructor(name: String, age: Int): this(name) 
        println("dog1-2--$name")
    


/**
运行输出值为:
Anim1--666
dog1-0--666
dog1-2--666
 */
fun testRun() 
    var anim = Dog1("666", 66)

接着我们再来看看 kotlin 中类方法的重写,这点与 java 也很不相同。kotlin 中如果一个方法想被重写,则必须显式指定 open 关键字,否则无法编译通过;子类中重写方法必须显示指定 override 关键字,否则无法编译通过。案例如下:

open class Developer 
    //kotlin 中如果一个方法想被重写,则必须显式指定 open 关键字,否则无法编译通过
    open fun skill() 
        println("developer")
    

    //子类默认具备该能力,但无法重写
    fun money() 
        println("666")
    

    //可以被子类继承
    open fun wife() 
        println("haha")
    


open class Android: Developer() 
    //kotlin 中重写方法必须显示指定 override 关键字,否则无法编译通过
    override fun skill() 
        println("android")
    

    //Android 类的 wife 方法重写了父类,但是无法被子类重写,因为声明了 final
    final override fun wife() 
        super.wife()
        println("green")
    


/**
运行输出值为:
android
666
haha
green
 */
fun testRun() 
    var dev = Android()
    dev.skill()
    dev.money()
    dev.wife()

看完类方法的重写,我们再来看看 kotlin 的类属性重写。val 的属性可以被重写成 var,var 的属性不能被重写成 val。因为一个 val 属性相当于一个 get 方法,var 相当于一个 get 和 set 方法,所以增加范围是可以的,但是缩小范围是不行的(一旦缩小如果父类中有对 var 的 set 操作则没重写方法的情况下就会异常,所以缩小范围不被允许)。如下是使用案例:

open class PaPaPa 
    open val name: String = "pa"

    open fun print() 
        println("name=$name, this.name=$this.name")
    


open class XoXoXo: PaPaPa() 
    override val name: String = "xo"
    //val 只读属性默认只有 get 方法
    open val address: String get() = "beijing"


//属性重写简写方式
class NcNcNc (override var name: String = "nc"): XoXoXo() 
    override fun print() 
        super.print()
        println("address is $address")
    

    //重写父类属性并调用父类属性
    override val address: String
        get() = "6666" + super.address


/**
运行输出值为:
name=nc, this.name=nc
address is 6666beijing
 */
fun testRun() 
    var oo = NcNcNc()
    oo.print()

和 java 一样,类的继承有一个特殊的,那就是接口,kotlin 中接口的实现和 java 比较类似,如下案例:

//【工匠若水 加微信 yanbo373131686 联系我,关注微信公众号:码农每日一题  未经允许严禁转载 https://blog.csdn.net/yanbober】

interface Nb 
    //接口声明
    fun test1()
    //接口可以既有声明也可以有实现
    fun test() 
        println("nb")
    


class ChildNb: Nb 
    override fun test1() 
        println("test1")
    

    override fun test() 
        println("test")
        super.test()
    

kotlin 为我们提供了一种混用冲突的解决方案,当继承的类和接口拥有相同签名方法时子类必须重写父类相同签名方法,否则无法编译通过,此外需要显式通过<>语法指定使用哪个基类的方法。案例如下:

interface Nb 
    fun test() 
        println("nb")
    


open class Tt 
    open fun test() 
        println("tt")
    


class ChildNb: Nb, Tt() 
    //当继承的类和接口拥有相同签名方法时子类必须重写父类相同签名方法,否则无法编译通过
    //此外需要显式通过<>指定使用哪个基类的方法
    override fun test() 
        super<Tt>.test()
        println("test")
        super<Nb>.test()
    


/**
运行输出值为:
tt
test
nb
 */
fun testRun() 
    var oo = ChildNb()
    oo.test()

对于抽象类的声明与继承基本和 java 没啥区别,具体案例如下:

open class Base 
    open fun method() 
        println("base")
    


abstract class Child: Base() 
    //父类的实现方法可以被抽象子类重写成抽象方法供当前抽象类的子类实现
    override abstract fun method()


class Child1: Child() 
    override fun method() 
        println("child1")
    

对象声明及伴生对象

kotlin 的对象声明你可以理解成一种单例的语言层面支持实现能力。定义一个类用 class,声明一个对象用 object,全局声明的对象就是一个对象实例,且全局唯一。案例如下:

//定义了一个名为 TestObject 的对象实例
object TestObject 
    fun method() 
        println("666")
    


/**
运行输出值为:
666
 */
fun testRun() 
    TestObject.method()

kotlin 的伴生对象相对于 java 来说也是一种新的特性,本质来说,kotlin 与 java 不同的是 kotlin 的类没有 static 方法。在大多数情况下 kotlin 推荐的做法是使用包级别的函数来充当静态方法角色,kotlin 会将包级别的函数当作静态方法看待。而 kotlin 中一个类最多只能有一个伴生对象,这个伴生对象也类似 java 的 static 成员。案例如下:

//【工匠若水 加微信 yanbo373131686 联系我,关注微信公众号:码农每日一题  未经允许严禁转载 https://blog.csdn.net/yanbober】

class Yan 
    //TestObj 名字可以省略,编译器默认名字是 Companion
    //一个类中最多只能有一个伴生对象
    companion object TestObj 
        var name: String = "666"
        fun method() = println("$this.name -- method")
    


/**
运行输出值为:
666 -- method
777 -- method
class cn.yan.test.Yan$TestObj
 */
fun testRun() 
    Yan.TestObj.method()
    //简写,kotlin 语法糖,没有 @jvmStatic 情况下本质还是转为了 Yan.TestObj 静态成员调用方式
    Yan.name = "777"
    Yan.method()
    println(Yan.TestObj.javaClass)

虽然伴生对象的成员看起来像是 java 的静态成员,但在运行期他们依旧是真实对象的实例成员。在 jvm 实现上可以将伴生对象的成员真正生成为类的静态方法与属性,具体通过@JvmStatic注解实现。

伴生对象的本质原理是在编译后生成一个静态内部类来实现的,我们对上面的案例代码进行反编译(javap)结果如下:

//反编译的 cn.yan.test.Yan 类
yandeMacBook-Pro:test yan$ javap -c Yan.class
Compiled from "Test2.kt"
public final class cn.yan.test.Yan 
  //Yan 这个 kotlin 类中的伴生对象名生成了一个静态成员变量,名字为 TestObj
  public static final cn.yan.test.Yan$TestObj TestObj;
  //Yan 这个 kotlin 类的构造函数
  public cn.yan.test.Yan();
    Code:
       0: aload_0
       1: invokespecial #8                  // Method java/lang/Object."<init>":()V
       4: return
  //Yan 这个 kotlin 类的 static 代码块实例化了Yan$TestObj静态内部类并赋值给当前类的静态成员属性TestObj
  static ;
    Code:
       0: new           #38                 // class cn/yan/test/Yan$TestObj
       3: dup
       4: aconst_null
       5: invokespecial #41                 // Method cn/yan/test/Yan$TestObj."<init>":(Lkotlin/jvm/internal/DefaultConstructorMarker;)V
       8: putstatic     #43                 // Field TestObj:Lcn/yan/test/Yan$TestObj;
      11: ldc           #45                 // String 666
      13: putstatic     #20                 // Field name:Ljava/lang/String;
      16: return
  //在 Yan 这个类中新增了伴生对象里面属性 var name 的 get 操作
  public static final java.lang.String access$getName$cp();
    Code:
       0: getstatic     #20                 // Field name:Ljava/lang/String;
       3: areturn
  //在 Yan 这个类中新增了伴生对象里面属性 var name 的 set 操作
  public static final void access$setName$cp(java.lang.String);
    Code:
       0: aload_0
       1: putstatic     #20                 // Field name:Ljava/lang/String;
       4: return

如下是 Yan 这个 kotlin 类中伴生对象生成的静态内部类反编译代码:

//反编译的 cn.yan.test.Yan 类中伴生对象生成的静态内部类 Yan$TestObj
yandeMacBook-Pro:test yan$ javap -c Yan\\$TestObj.class
Compiled from "Test2.kt"
public final class cn.yan.test.Yan$TestObj 
  //伴生对象内部属性 var name 的 get 方法
  public final java.lang.String getName();
    Code:
       0: invokestatic  #12                 // Method cn/yan/test/Yan.access$getName$cp:()Ljava/lang/String;
       3: areturn
  //伴生对象内部属性 var name 的 set 方法
  public final void setName(java.lang.String);
    Code:
       0: aload_1
       1: ldc           #18                 // String <set-?>
       3: invokestatic  #24                 // Method kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V
       6: aload_1
       7: invokestatic  #27                 // Method cn/yan/test/Yan.access$setName$cp:(Ljava/lang/String;)V
      10: return
  //伴生对象内部定义的方法
  public final void method();
    Code:
       0: new           #32                 // class java/lang/StringBuilder
       3: dup
       4: invokespecial #35                 // Method java/lang/StringBuilder."<init>":()V
       7: aload_0
       8: checkcast     #2                  // class cn/yan/test/Yan$TestObj
      11: invokevirtual #37                 // Method getName:()Ljava/lang/String;
      14: invokevirtual #41                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      17: ldc           #43                 // String  -- method
      19: invokevirtual #41                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      22: invokevirtual #46                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      25: astore_1
      26: iconst_0
      27: istore_2
      28: getstatic     #52                 // Field java/lang/System.out:Ljava/io/PrintStream;
      31: aload_1
      32: invokevirtual #58                 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
      35: return
  //伴生对象生成的静态内部类的构造方法
  public cn.yan.test.Yan$TestObj(kotlin.jvm.internal.DefaultConstructorMarker);
    Code:
       0: aload_0
       1: invokespecial #61                 // Method "<init>":()V
       4: return

接着我们将上面 kotlin 案例代码添加@JvmStatic注解,代码如下:

class Yan 
    companion object TestObj 
        @JvmStatic
        var name: String = "666"
        @JvmStatic
        fun method() = println("$this.name -- method")
    

上面代码反编译结果如下:

//【工匠若水 加微信 yanbo373131686 联系我,关注微信公众号:码农每日一题  未经允许严禁转载 https://blog.csdn.net/yanbober】

yandeMacBook-Pro:test yan$ javap -c Yan.class
Compiled from "Test2.kt"
public final class cn.yan.test.Yan 
  //与上面没注解类似,静态属性
  public static final cn.yan.test.Yan$TestObj TestObj;
  //与上面没注解类似,构造方法
  public cn.yankotlin 实战之 letwithrunapplyalsotakeIftakeUnlessrepeat 源码总结

kotlin 实战之函数与 lambda 表达式总结

kotlin 实战之泛型与逆变协变总结

Groovy 动态面向对象复盘总结

Kotlin——从无到有系列之中级篇:面向对象的特征与类(class)继承详解

python学习笔记之面向对象编程特性