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 源码总结