Kotlin 使用 class 关键字声明类。类声明由 类修饰符、类名、类头(指定其类型参数、主题构造函数等)以及花括号的类体构成。修饰符、类头与类体都是可选的。

  1. 类修饰符 class 类名 主构造函数 {
  2. init{}
  3. 次构造函数(参数列表)
  4. /******/
  5. }
  6. // 简单的类可以像这样的声明
  7. class Person{
  8. /******/
  9. }

注意:

  1. 同样的参数列表,主构造函数(primary constructor)和次构造函数(secondary constructors)不能同时存在;
  2. 次构造函数可以有多个,但是参数列表不能一样;
  3. 直接使用 class 声明,没有使用任何修饰符的类,默认为 final 的,不能被继承。

构造函数

主构造函数

主构造函数是类头的一个部分,它跟在类名的后面。如果没有特殊指定其可见性,则表示该主构造函数是 public 的。

  1. class Human constructor(name: String){
  2. /** 类体 **/
  3. }
  4. // 或者
  5. class Human private constructor(name: String){
  6. /** 类体 **/
  7. }

如果主构造函数没有任何的注解或者可见性修饰符(public,private,protected,internal),则 constructor 可以省略。主构造函数默认为 public 修饰的。如果你不想让这个类有一个公有主构造函数,可以想下面的声明方式一样,将主构造函数声明为 pivate 的。

主构造函数内不能有任何的代码。初始化的代码可以放到 init 关键字作为前缀的初始化块(initializer blocks)中;也可以在类体的属性初始化器中使用。

  1. class Human(name: String) {
  2. // 在类体里,可以直接使用主构造函数传入的变量,这里叫做属性初始化器
  3. val upperName = name.toUpperCase()
  4. val printName1 = "First print name : $name".also(::println)
  5. init{
  6. println("initializer blocks 1 print name : $name")
  7. println("initializer blocks 1 print Upper name : $upperName")
  8. }
  9. val printName2 = "Second print name : $name".also(::println)
  10. init {
  11. println("initializer blocks 2 print name : $name")
  12. }
  13. }

由上面的代码可以看出来, init 初始化块可以多次使用。上面的代码会按照出现在类体中的顺序执行,其输出结果为:

  1. First print name : Ricky
  2. initializer blocks 1 print name : Ricky
  3. initializer blocks 1 print Upper name : RICKY
  4. Second print name : Ricky
  5. initializer blocks 2 print name : Ricky

由输出结果可以发现, init 初始化块和属性初始化器交织在一起。他们按照在代码中出现的顺序执行

将上面的代码转换为 Java 代码:

  1. public final class Human {
  2. @NotNull
  3. private final String upperName;
  4. @NotNull
  5. private final String printName1;
  6. @NotNull
  7. private final String printName2;
  8. @NotNull
  9. public final String getUpperName() {
  10. return this.upperName;
  11. }
  12. @NotNull
  13. public final String getPrintName1() {
  14. return this.printName1;
  15. }
  16. @NotNull
  17. public final String getPrintName2() {
  18. return this.printName2;
  19. }
  20. public Human(@NotNull String name) {
  21. Intrinsics.checkParameterIsNotNull(name, "name");
  22. super();
  23. String var10000 = name.toUpperCase();
  24. Intrinsics.checkExpressionValueIsNotNull(var10000, "(this as java.lang.String).toUpperCase()");
  25. String var7 = var10000;
  26. this.upperName = var7;
  27. String var2 = "First print name : " + name;
  28. System.out.println(var2);
  29. this.printName1 = var2;
  30. var2 = "initializer blocks 1 print name : " + name;
  31. System.out.println(var2);
  32. var2 = "initializer blocks 1 print Upper name : " + this.upperName;
  33. System.out.println(var2);
  34. var2 = "Second print name : " + name;
  35. System.out.println(var2);
  36. this.printName2 = var2;
  37. var2 = "initializer blocks 2 print name : " + name;
  38. System.out.println(var2);
  39. }
  40. }

通过 Java 代码,我们可以看出来,无论是在 init 初始化块还是属性初始化器,最终都会到同一个构造函数里,它们按照代码出现的顺序执行。

在 Kotlin 声明类时,还有一个简洁的语法,可以在主构造函数中声明属性。

  1. class Person(val firstName: String, val lastName: String, var age: Int) { /*……*/ }

与普通属性一样,主构造函数中声明的属性可以是可变的(var)或只读的(val)。

接下来我们根据转换来的 Java 代码来看。首先我们不在主构造函数中声明变量:

  1. class Dog(name: String) {
  2. init {
  3. println(name)
  4. }
  5. }

转换成 Java 代码:

  1. public final class Dog {
  2. public Dog(@NotNull String name) {
  3. Intrinsics.checkParameterIsNotNull(name, "name");
  4. super();
  5. boolean var2 = false;
  6. System.out.println(name);
  7. }
  8. }

可以看到,并没有一个 叫做 name 的属性。

然后我们在主构造函数中把 name 声明成一个属性 :

  1. class Dog(var name: String) {
  2. init {
  3. println(name)
  4. }
  5. }

然后转换成 Java 代码:

  1. public final class Dog {
  2. @NotNull
  3. private String name;
  4. @NotNull
  5. public final String getName() {
  6. return this.name;
  7. }
  8. public final void setName(@NotNull String var1) {
  9. Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
  10. this.name = var1;
  11. }
  12. public Dog(@NotNull String name) {
  13. Intrinsics.checkParameterIsNotNull(name, "name");
  14. super();
  15. this.name = name;
  16. String var2 = this.name;
  17. boolean var3 = false;
  18. System.out.println(var2);
  19. }
  20. }

可以很明显的看到,Java 代码中多出了一个 name 的全局变量,并且有 gettersetter 方法。

在 JVM 上,如果主构造函数的所有的参数都有默认值,编译器会生成一个额外的无参构造函数,它将使用默认值。比如下面的 Kotlin 类:

  1. // 这里的 name 的默认值为 ""
  2. class Customer(val name: String = "") {
  3. }

将其转换为 Java 代码:

  1. public final class Customer {
  2. @NotNull
  3. private final String name;
  4. @NotNull
  5. public final String getName() {
  6. return this.name;
  7. }
  8. public Customer(@NotNull String name) {
  9. Intrinsics.checkParameterIsNotNull(name, "name");
  10. super();
  11. this.name = name;
  12. }
  13. // $FF: synthetic method
  14. public Customer(String var1, int var2, DefaultConstructorMarker var3) {
  15. if ((var2 & 1) != 0) {
  16. // 编译器生成了一个额外的无参构造函数,它使用默认值
  17. var1 = "";
  18. }
  19. this(var1);
  20. }
  21. // 编译器生成了一个额外的无参构造函数,它使用默认值
  22. public Customer() {
  23. this((String)null, 1, (DefaultConstructorMarker)null);
  24. }
  25. }

如果没有显示的声明主构造函数,默认的也会有一个空的主构造函数。就像 Java 中的类,如果没有声明构造函数,其实它也是会默认有一个空的无参构造。

次构造函数

除了上面的主构造函数,Kotlin 还提供了 次构造函数。使用 constructor 关键字修饰。

  1. class Human {
  2. val children: MutableList<Human> = mutableListOf()
  3. constructor(parent: Human){
  4. parent.children.add(this)
  5. }
  6. }

如果类有一个主构造函数,每个次构造函数都需要委托给主构造函数,可以直接委托或者通过别的次构造函数间接委托。委托到同一个类的另一个构造函数使用 this 关键字:

  1. class Human(name: String) {
  2. val children: MutableList<Human> = mutableListOf()
  3. // 通过 :this(name) 调用主构造函数,将主构造函数委托给次构造函数
  4. constructor(name: String, parent: Human) : this(name) {
  5. parent.children.add(this)
  6. }
  7. }

这样我们在调用的时候,Human 类就有了两个构造函数,一个要传入一个 name ,另一个需要传入 nameHuman ,我们可以这样调用:

  1. fun main() {
  2. val human = Human("Ricky")
  3. val human2 = Human("Jim", human)
  4. }

注意:

  1. 如果主构造函数中有参数,次构造函数的个数必须大于等于主构造函数中的参数个数
  2. 次构造函数必须包含主构造函数的参数
  3. 如果同时存在主构造函数和次构造函数,那么主构造函数必须直接或者间接的委托给次构造函数

关于初始化块

  1. 初始化块中的代码实际上会成为主构造函数的一部分;
  2. 初始化块委托给主构造函数会作为次构造函数的第一条语句;
  3. 所有的初始化块中的代码都会在次构造函数体之前执行;
  4. 即使该类没有主构造函数,这种委托仍然会隐式发生,并且仍然会执行初始化块。
  1. class Constructors{
  2. init {
  3. println("Init block")
  4. }
  5. constructor(i:Int){
  6. println("Secondary constructor")
  7. }
  8. }

上面的代码的输出结果为:

  1. Init block
  2. Secondary constructor

可以看出,初始化块在次构造函数前执行,这里我们证明了第3点:所有的初始化块中的代码都会在次构造函数体之前执行 。然后我们将其转换成 Java 代码:

  1. public final class Constructors {
  2. public Constructors(int i) {
  3. // 1. 初始化块中的代码实际上会成为主构造函数的一部分;
  4. // 2. 初始化块委托给主构造函数会作为次构造函数的第一条语句;
  5. // 4. 即使该类没有主构造函数,这种委托仍然会隐式发生,并且仍然会执行初始化块。
  6. String var2 = "Init block";
  7. boolean var3 = false;
  8. System.out.println(var2);
  9. var2 = "Secondary constructor";
  10. var3 = false;
  11. System.out.println(var2);
  12. }
  13. }

通过 Java 代码就可以很直观的看到, 初始化块中的代码跟次构造函数中的代码都在 Java 类Constructors 的构造函数中,初始化块的代码在其第一行,我们并没有声明主构造函数,但是初始化块中的代码仍然会次构造函数之前。

关于 构造函数 的总结

  1. 主构造函数没有函数体,它是类统一的参数的入口,它用来接收参数,我们可以属性初始化时,使用传入的变量,也可以在 init 初始化块中使用传入的变量。主构造函数时可选的,但是如果你写了,并且还需要次构造函数,那么就需要在次构造函数调用主构造函数。因为我们需要在创建实例的时候,初始化入口传入的参数。
  2. 主构造函数会参入到所有次构造函数的初始化过程中。如果存在多个构造函数,我们一般把包含最基本,最通用的参数作为主构造函数。

创建类的实例

Kotlin 中没有 new 关键字,我们如果要创建一个类的实例,可以像普通的函数调用一个样调用构造函数:

  1. val constructors = Constructors(1)
  2. val human = Human("Ricky")
  3. val human2 = Human("Ricky", human)

继承

在 Kotlin 中,所有的类都一个共同的父类 Any ,这里的 Any 类似于 Java 中的 Object 类 。如果没有显式的去继承 Any ,它就会隐式继承 Any

  1. class Example // 它的父类就是 Any,这里是隐式的继承 Any

Any 有三个方法:equals()hashCode()toString() 。因此,所有的 Kotlin 类都默认定义了这些方法。

如果要继承一个类,只需要在类头中把父类放到冒号 : 的后面即可。作为父类的类,在声明时必须使用 open 修饰,因为使用 class 声明的类,默认为 final 的,是不能被继承的。

如果父类有主构造函数,对应的子类也必须有对应的主构造函数,并且在继承时必须带上对应的参数。

  1. open class Animal(name: String)
  2. class Dog(name: String):Animal(name)

子类在继承父类时,父类必须使用主构造函数就地初始化,即在子类的类头中初始化。在 Kotlin 中,如果显示的声明主构造函数,它会默认生成一个空的无参的主构造函数。

  1. open class Animal
  2. // 子类 Dog 中有一个主构造,但是父类并没有,这父类还是需要初始化
  3. class Dog(name: String) : Animal() {
  4. }

上面的代码中子类 Dog 有一个主构造函数,但是父类 Animal 没有。但是当 Dog 继承 Animal 时,还是需要在采用默认的空的构造函数对 Animal 进行初始化。

如果子类没有主构造函数,父类也没有主构造函数,可以像下面一样进行声明:

  1. open class Animal
  2. class Dog : Animal()

如果子类和父类都不存在主构造函数,而存在次构造函数,那么每个次构造函数必须使用 super 关键字调用父类的构造,对父类进行初始化。这里不要求参数的一致,不同的次构造函数可以调用父类的不同的构造函数:

  1. open class Animal {
  2. constructor(name: String)
  3. constructor(name: String, age: Int)
  4. }
  5. class Dog : Animal {
  6. constructor(name: String) : super(name)
  7. constructor(name: String, age: Int) : super(name, age)
  8. // 也可以这样调用
  9. // constructor(name: String, age: Int) : super(name)
  10. }

如果父类同时存在主构造函数和次构造函数,那么子类只能通过其中的一个父类进行初始化。因为在 Kotlin 类如果同时存在主构造函数和次构造函数,次构造函数必须通过 this 调用主构造函数。这个时候如果子类在次构造函数中再次通过 super 对父类进行初始化,就会造成重复调用。

覆盖(重写)方法

只有使用了 open 修饰的方法才可以被覆盖。子类使用 override 修饰符来修饰覆盖的函数。

  1. open class Animal {
  2. open fun run() {
  3. println("Animal run ...")
  4. }
  5. }
  6. open class Dog : Animal() {
  7. override fun run() {
  8. println("Dog run ...")
  9. }
  10. }
  11. fun main() {
  12. val dog = Dog()
  13. dog.run()
  14. }

上面的代码输出:

  1. Dog run ...

我们在 Dog 类中覆盖了父类 Animal 中的 run 方法。

override 的方法默认是 open 的,如果不想让该方法再次覆盖,则需要在前面添加 final 关键字。

  1. open class Animal {
  2. open fun run() {
  3. println("Animal run ...")
  4. }
  5. }
  6. open class Dog : Animal() {
  7. // 这里使用final修饰,则其子类不可覆盖该方法
  8. final override fun run() {
  9. println("Dog run ...")
  10. }
  11. }
  12. class Corgi : Dog(){
  13. // Dog 的 run方法使用了 final 修饰,这里如果再次覆盖,就会报错。
  14. override fun run() {
  15. println("Corgi run ...")
  16. }
  17. }

覆盖属性

属性覆盖与方法覆盖类似;只有使用了 open 修饰的属性才可以被覆盖;子类通过 override 修饰重新声明的属性。当然,如果覆盖和被覆盖的类型必须是兼容的。可以使用 var 属性覆盖 val 属性,但是反过来却不行。因为一个 val 属性本质上声明了一个 get 方法, 而将其覆盖为 var 只是在子类中额外声明一个 set 方法。

  1. open class Animal {
  2. open val age: Int? = 10
  3. }
  4. open class Dog : Animal() {
  5. override var age: Int? = 20
  6. }
  7. class Corgi : Dog() {
  8. override var age: Int? = 30
  9. }

在主构造函数中override 关键字也可以作为属性声明的一部分。

  1. open class Animal {
  2. open val age: Int? = 10
  3. }
  4. open class Dog(override var age: Int? = 20) : Animal() {
  5. }
  6. class Corgi : Dog(){
  7. override var age: Int? = 30
  8. }

子类的初始化顺序

子类实例的构建过程分以下几步:

  1. 对父类构造函数的参数求值
  2. 执行父类的构造函数
  3. 初始化父类的属性
  4. 对子类构造函数的参数求值
  5. 执行子类的构造函数
  6. 初始化子类的属性
  1. // 父类
  2. open class Animal(name: String) {
  3. // 2.初始化父类的构造
  4. init {
  5. println("Animal init block")
  6. }
  7. // 3.初始化父类的属性
  8. open val nameSize: Int? = name.length.also {
  9. println("Initializing property nameSize in Animal :$it")
  10. }
  11. }
  12. // 子类
  13. open class Dog(name: String) :
  14. // 1.继承父类,对父类构造函数的参数求值
  15. Animal(name.capitalize().also { println("Argument for Animal:$it") }) {
  16. // 5.初始化子类的构造
  17. init {
  18. println("Dog init block")
  19. }
  20. // 6.初始化子类的属性
  21. override val nameSize: Int? = name.length.also {
  22. println("Initializing property nameSize in Dog :$it")
  23. }
  24. }
  25. fun main() {
  26. // 4.对子类构造函数的参数求值
  27. val dog = Dog("jack".also { println("Argument for Dog:$it") })
  28. }

上面代码的输出结果为:

  1. Argument for Dogjack
  2. Argument for AnimalJack
  3. Animal init block
  4. Initializing property nameSize in Animal :4
  5. Dog init block
  6. Initializing property nameSize in Dog :4

从上面的代码中,可以看出来,子类在初始化时会首先去找到父类,对父类进行初始化。然后才会对子类进行初始化。

调用父类实现

子类可以通过 super 关键字调用父类的函数与属性访问器的实现。

  1. open class Shape {
  2. val borderColor: String get() = "black"
  3. open fun draw() {
  4. println("Shape draw")
  5. }
  6. }
  7. class Rectangle : Shape() {
  8. val fillColor: String get() = super.borderColor
  9. override fun draw() {
  10. super.draw()
  11. println("Rectangle draw")
  12. }
  13. }

如果存在内部类,则内部类可以通过 super@外部类名 调用外部类。

  1. class Rectangle : Shape() {
  2. val fillColor: String get() = super.borderColor
  3. override fun draw() {
  4. super.draw()
  5. println("Rectangle draw")
  6. }
  7. inner class Filler {
  8. fun fill() {}
  9. fun drawAndFill() {
  10. // 内部类可以通过 `super@外部类名` 调用外部类。
  11. super@Rectangle.draw()
  12. fill()
  13. }
  14. }
  15. }

抽象类

使用 abstract 声明的类被称为抽象类 ,使用 abstract 声明的方法被称为抽象方法。抽象类有以下几个特点:

  1. 抽象方法在抽象类中可以不用实现,其默认为 open 的;
  2. 抽象方法必须存在于抽象类中;
  3. 抽象类中可以存在已经实现的方法;
  4. 子类如果不是抽象类,去继承抽象类,必须实现抽象类的所有的抽象方法;
  5. 抽象方法去覆盖非抽象的方法。
  1. abstract class Bird {
  2. // 抽象方法在抽象类中可以不用实现,其默认为 `open` 的;
  3. abstract fun fly()
  4. // 抽象类中可以存在已经实现的方法;
  5. open fun chirp() {
  6. println("abstract Bird chirp")
  7. }
  8. }
  9. abstract class Blackbird : Bird() {
  10. override fun fly() {
  11. println("Blackbird fly")
  12. }
  13. // 抽象方法去覆盖非抽象的方法。
  14. abstract override fun chirp()
  15. }

对象

这里对象是指 object 关键字,object 的作用是在声明一个类的同时创建这个类实例。

有时候我们不想通过继承的方式来对一个类的功能进行微调,那么我们就可以通过 object 来实现。

对象表达式

通过 object 关键字我们可以定义匿名类,通过继承的方式来实现一些特定功能:

  1. window.addMouseListener(object : MouseAdapter() {
  2. override fun mouseClicked(e: MouseEvent?) {/* ... */}
  3. override fun mouseEntered(e: MouseEvent?) {/* ... */}
  4. })

上面的代码使用 object 创建了一个抽象类 MouseAdapter 的实例,并实现了其中的两个方法。将其转换成 Java 代码:

  1. window.addMouseListener((MouseListener)(new MouseAdapter() {
  2. public void mouseClicked(@Nullable MouseEvent e) {
  3. }
  4. public void mouseEntered(@Nullable MouseEvent e) {
  5. }
  6. }));

如果父类有一个构造函数,则必须传递指定类型的参数给它。如果有父类和多个接口可以在使用逗号分隔。

  1. open class A(x: Int) {
  2. public open val y: Int = x
  3. }
  4. interface B { /*……*/ }
  5. val ab: A = object : A(1), B {
  6. override val y = 15
  7. }

有些时候,我们并不想创建一个类,只想要一个对象,这个对象不需要特殊的类型,那么我们可以这样写:

  1. fun foo() {
  2. val operand = object {
  3. var x: Int = 0
  4. var y: Int = 0
  5. }
  6. println(operand.x + operand.y)
  7. }

或者在类中这样写:

  1. class ObjectSample {
  2. object Operand {
  3. var x: Int = 2
  4. var y: Int = 4
  5. }
  6. }
  7. fun main() {
  8. println(ObjectSample.Operand.x + ObjectSample.Operand.y)
  9. }

object 生成的对象作用在私有属性或者方法上,它的返回类型是匿名对象的类型;如果作用在在公有的方法或者属性上,它返回的内容是Any 类型,我们没办法在外边访问它的内部。

  1. class ObjectSample {
  2. // 私有属性,返回类型为匿名的对象的类型,可以在外边使用
  3. private val privateProperty = object {
  4. val abc = "abc"
  5. }
  6. // 公有属性,返回类型为 Any ,不能在外边使用
  7. val publicProperty = object {
  8. val abc = "abc"
  9. }
  10. // 私有方法,返回类型为匿名的对象的类型,可以在外边使用
  11. private fun privateMethod() = object {
  12. val x = "X"
  13. }
  14. // 公有方法,返回类型为 Any,不能在外边使用
  15. fun publicMethod() = object {
  16. val x = "X"
  17. }
  18. fun ownedMethod() {
  19. // 在方法内部,返回类型为匿名的对象的类型,可以在方法内部使用
  20. val o = object {
  21. val x = "X"
  22. }
  23. val xo = o.x
  24. }
  25. fun bar() {
  26. val p1 = privateProperty.abc
  27. // 公有属性,返回类型为 Any ,不能在外边使用
  28. // val p2 = publicProperty.abc
  29. val m1 = privateMethod().x
  30. // 公有方法,返回类型为 Any,不能在外边使用
  31. // val m2 = publicMethod().x
  32. }
  33. }

对象声明

  1. 如果在 `object` 的后面加上一个名称,就像变量声明一样,我们就声明了一个类,同时用创建了一个类的引用了;这就是对象声明。我们可以通过类名直接调用里面的方法。我们不能自己新建其它的实例,有且只有当前一个实例。这就是 Kotin 中单例。
  1. object DataProvider{
  2. fun provide(){
  3. println("provide datas")
  4. }
  5. }
  6. fun main() {
  7. DataProvider.provide()
  8. }

对象声明的初始化过程是线程安全的。

当然声明对象时,也可以给它指定父类:

  1. abstract class Provider {
  2. abstract fun provide()
  3. }
  4. object DataProvider : Provider() {
  5. override fun provide() {
  6. println("provide datas")
  7. }
  8. }
  9. fun main() {
  10. DataProvider.provide()
  11. }

对象声明不能嵌套在函数内部,但是可以嵌套到其它对象声明或者非内部类中。

  1. class DatabaseProvider{
  2. fun provide(){
  3. // 函数内部是不能进行对象声明的
  4. object InnerProvider{
  5. }
  6. }
  7. }

伴生对象

在类的内部使用 companion 关键字标记的对象声明叫做半生对象。比如下面的代码:

  1. class DatabaseProvider {
  2. companion object Type {
  3. val type1 = 1
  4. val type2 = 2
  5. }
  6. }

该伴生对象的成员可通过只使用类名作为限定符来调用,就像 java 类中静态成员,如下:

  1. fun getDataType(type: Int) {
  2. when (type) {
  3. DatabaseProvider.type1 -> {
  4. println("data is type1")
  5. }
  6. DatabaseProvider.type2 -> {
  7. println("data is type1")
  8. }
  9. else -> {
  10. println("data is unknown")
  11. }
  12. }
  13. }

如果半生对象没有定义名字,我们可以使用名称 Companion 来调用;比如:

  1. class MyClass {
  2. companion object { }
  3. }
  4. val x = MyClass.Companion

虽然像是 Java 的静态成员,但是在运行时伴生对象仍然是真实的实例成员,我们将其转换成 Java 代码:

  1. public final class DatabaseProvider {
  2. private static int type1 = 1;
  3. private static int type2 = 2;
  4. public static final DatabaseProvider.Type Type = new DatabaseProvider.Type((DefaultConstructorMarker)null);
  5. //半生对象会被转换成静态的final类
  6. public static final class Type {
  7. public final int getType1() {
  8. return DatabaseProvider.type1;
  9. }
  10. public final void setType1(int var1) {
  11. DatabaseProvider.type1 = var1;
  12. }
  13. public final int getType2() {
  14. return DatabaseProvider.type2;
  15. }
  16. public final void setType2(int var1) {
  17. DatabaseProvider.type2 = var1;
  18. }
  19. private Type() {
  20. }
  21. // $FF: synthetic method
  22. public Type(DefaultConstructorMarker $constructor_marker) {
  23. this();
  24. }
  25. }
  26. }

在 JVM 平台,如果使用 @JvmStatic 注解,我们可以将伴生对象的成员生成为真正的静态方法和字段。

对象表达式和对象声明的区别

  1. 对象表达式是在使用它们的地方立即执行的;
  2. 对象声明是在第一次被访问到时延迟初始化的;
  3. 半生对象的初始化是在相应的类被加载时,与Java静态初始化器的语义匹配。

总结

  1. Kotlin 的类有主构造和次构造之分
  2. 主构造和次构造同时存在时,次构造需要通过this调用主构造
  3. Kotlin 类的初始化顺序:
    1. 主构造参数的表达式
    2. 主构造函数
    3. 属性初始化器跟init初始化块是按出现的顺序执行的
    4. 次构造参数的表达式
    5. 次构造函数
  4. Kotlin 中不存在 new ,只需要像声明变量一样声明对象的引用 val p = Person()
  5. Kotlin 所有的类的最顶层父类是 Any
  6. 只有使用了 open 修饰的类才可以被继承
  7. 只有使用了 open 修饰的方法才可以被覆盖(重写)
  8. 只有使用了 open 修饰的属性才可以被覆盖
  9. override 的方法默认是open 的,如果不想被再次覆盖(重写),需要加 final
  10. 子类可以使用 super 调用父类的实现
  11. 如果存在内部类,则内部类可以通过 super@外部类名 调用外部类。
  12. 在继承时需要以下的规则:
    1. 如果父类有主构造函数,子类也一定也要有对应的主构造,并且在继承时必须带上对应的参数
    2. 子类在继承父类时,父类必须使用主构造函数就地初始化,即在子类的类头中初始化
    3. 如果子类和父类都不存在主构造函数,而存在次构造函数,那么每个次构造函数必须使用 super 关键字调用父类的构造,对父类进行初始化
    4. 如果父类同时存在主构造函数和次构造函数,那么子类只能通过其中的一个父类进行初始化
    5. 以上内容不需要记住,编译器会告诉你。。。。
  13. 抽象类默认是 open
  14. 抽象方法可以去覆盖非抽象的方法
  15. 在Kotlin object 指的就是对象本身,它可以在声明一个类的同时创建这个类实例
  16. 虽然 companion object 修饰的伴生对象很像 java 类中静态成员,但是它本质还是一个类
  17. 在 JVM 平台,如果使用 @JvmStatic 注解,我们可以将伴生对象的成员生成为真正的静态方法和字段。