kotlin构造函数

在 Kotlin 中,构造函数是用于创建类对象的核心部分。Kotlin 提供了 主构造函数(Primary Constructor)和 次构造函数(Secondary Constructor)两种方式来定义构造函数。它还支持初始化代码块和默认参数等特性。

1. 主构造函数 (Primary Constructor)

主构造函数是直接定义在 类头部 的构造函数,它可以为类的属性提供默认初始化。

class Person(val name: String, var age: Int)

示例 1:带有属性的主构造函数

class Person(val name: String, var age: Int)

fun main() {
    val person = Person("Alice", 25)
    println("Name: ${person.name}, Age: ${person.age}")
}

解释:
name 和 age 是类的属性,并且通过构造函数初始化。
主构造函数的参数自动成为 类的成员变量(带 val 或 var)。
示例 2:添加初始化逻辑
你可以使用 init 初始化块 来编写初始化逻辑。

class Person(val name: String, var age: Int) {
    init {
        println("A person named $name is created.")
    }
}

fun main() {
    val person = Person("Bob", 30)
}

解释:
init 块在对象创建时自动执行,可以用于执行初始化逻辑。
示例 3:默认参数
构造函数中的参数可以有 默认值。

class Person(val name: String = "Unknown", var age: Int = 0)

fun main() {
    val person1 = Person()
    val person2 = Person("Alice", 25)

    println("${person1.name}, ${person1.age}") // 输出: Unknown, 0
    println("${person2.name}, ${person2.age}") // 输出: Alice, 25
}

解释:
当参数有默认值时,可以选择在创建对象时不传入该参数。

2. 次构造函数 (Secondary Constructor)

次构造函数用于提供多种初始化方式。如果主构造函数不能满足你的需求,可以定义多个次构造函数。

class Person {
    var name: String
    var age: Int

    // 次构造函数
    constructor(name: String, age: Int) {
        this.name = name
        this.age = age
    }
}

示例:带次构造函数的类

class Person {
    var name: String
    var age: Int

    // 次构造函数
    constructor(name: String, age: Int) {
        this.name = name
        this.age = age
    }
}

fun main() {
    val person = Person("Charlie", 28)
    println("Name: ${person.name}, Age: ${person.age}")
}

解释:
使用 this 引用当前对象的属性,并通过次构造函数初始化它们。

3. 主构造函数与次构造函数的结合

当类有主构造函数时,次构造函数必须调用主构造函数。你可以通过 this 调用来实现。

class Person(val name: String, var age: Int) {

    // 次构造函数
    constructor(name: String) : this(name, 0) {
        println("Created a person named $name with default age.")
    }
}

fun main() {
    val person = Person("Alice")
    println("Name: ${person.name}, Age: ${person.age}")
}

解释:
次构造函数通过 this 调用了主构造函数,并为 age 赋予默认值 0。

4. 无构造函数的类

Kotlin 允许类没有构造函数,这在一些简单数据模型中很常见。

class EmptyClass

5. 初始化顺序

init 块 总是在 主构造函数之后执行。
次构造函数 调用 主构造函数 后,才会执行次构造函数的内容。
示例:初始化顺序

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

    init {
        println("Initialized with name: $name")
    }

    constructor(name: String, age: Int) : this(name) {
        this.age = age
        println("Initialized with age: $age")
    }
}

fun main() {
    val person = Person("Alice", 25)
}

输出:

csharp
Initialized with name: Alice
Initialized with age: 25

6.多个次构造函数

在 Kotlin 中,如果一个类有 多个次构造函数,每个次构造函数都必须最终调用到 主构造函数(如果存在)。
多个次构造函数之间可以相互调用,但最终它们都必须连接到主构造函数。

1. 多个次构造函数的调用顺序

每个 次构造函数 可以通过 this 调用同类中的其他次构造函数。
但所有次构造函数最终必须通过链条式调用(直接或间接)到 主构造函数。
示例1:多个次构造函数相互调用

class Person(val name: String, var age: Int) {

    constructor(name: String) : this(name, 0) {
        println("Called constructor1 with name: $name")
    }

    constructor() : this("name", 0) {
        println("Called constructor2 with default values.")
    }
}

fun main() {
    val person1 = Person("Alice")
    println("Person1 -> Name: ${person1.name}, Age: ${person1.age}")

    val person2 = Person()
    println("Person2 -> Name: ${person2.name}, Age: ${person2.age}")
    var person3 = Person("haha", 12)
    println("person3->Name:${person3.name},Age:${person3.age}")
}

输出:

Called constructor1 with name: Alice
Person1 -> Name: Alice, Age: 0
Called constructor2 with default values.
Person2 -> Name: name, Age: 0
person3->Name:haha,Age:12
构造函数解析
  1. 主构造函数Person(val name: String, var age: Int)

    • 这是类的主构造函数,接受两个参数 nameage
  2. 次构造函数 1constructor(name: String) : this(name, 0)

    • 这个构造函数只接受 name 参数,并调用主构造函数,默认将 age 设置为 0。会打印 "Called constructor1 with name: $name"
  3. 次构造函数 2constructor() : this("name", 0)

    • 这个构造函数没有参数,调用主构造函数,将 name 默认设置为 "name"age 默认设置为 0。会打印 "Called constructor2 with default values."
主函数中的调用
  1. 创建 person1val person1 = Person("Alice")

    • 调用次构造函数 1,因为它接受一个参数。构造函数内部调用了主构造函数 this(name, 0),所以 name 被设置为 "Alice"age 被设置为 0。
    • 输出:
      Called constructor1 with name: Alice
      Person1 -> Name: Alice, Age: 0
      
  2. 创建 person2val person2 = Person()

    • 调用次构造函数 2,因为它没有参数。构造函数内部调用了主构造函数 this("name", 0),所以 name 被设置为 "name"age 被设置为 0。
    • 输出:
      Called constructor2 with default values.
      Person2 -> Name: name, Age: 0
      
  3. 创建 person3var person3 = Person("haha", 12)

    • 调用主构造函数,因为它接受两个参数。name 被设置为 "haha"age 被设置为 12。
    • 这个调用没有触发任何构造函数的打印语句,因为它直接使用主构造函数。
    • 输出:
      person3->Name: haha, Age: 12
      
总结
  • person1 调用了次构造函数 1。
  • person2 调用了次构造函数 2。
  • person3 调用了主构造函数。

示例2:多个次构造函数相互调用

class Person(val name: String, var age: Int) {

    constructor(name: String) : this(name, 0) {
        println("Called constructor1 with name: $name")
    }

    constructor() : this("name") {
        println("Called constructor2 with default values.")
    }
}

fun main() {
    val person1 = Person("Alice")
    println("Person1 -> Name: ${person1.name}, Age: ${person1.age}")

    val person2 = Person()
    println("Person2 -> Name: ${person2.name}, Age: ${person2.age}")
    var person3 = Person("haha", 12)
    println("person3->Name:${person3.name},Age:${person3.age}")
}

输出:

Called constructor1 with name: Alice
Person1 -> Name: Alice, Age: 0
Called constructor1 with name: name
Called constructor2 with default values.
Person2 -> Name: name, Age: 0
person3->Name:haha,Age:12
构造函数调用过程
  1. 主构造函数Person(val name: String, var age: Int)

    • 这是类的主构造函数,接受两个参数 nameage
  2. 次构造函数 1constructor(name: String) : this(name, 0)

    • 该构造函数接受一个参数 name,并调用主构造函数,默认将 age 设置为 0。
  3. 次构造函数 2constructor() : this("name")

    • 该构造函数没有参数,调用次构造函数 1,this("name") 会传递 "name" 作为参数,然后触发次构造函数 1。
实际调用 Person()

当调用 val person2 = Person() 时,以下步骤会被执行:

  1. 次构造函数 2 被调用:由于 Person() 是无参数的构造函数,首先进入次构造函数 2。
  2. 次构造函数 2 调用次构造函数 1:在次构造函数 2 的实现中,this("name") 会调用次构造函数 1,将 "name" 作为参数传递。
  3. 次构造函数 1 调用主构造函数:this(name, 0) 会被执行,将 name 设置为 "name",并将 age 设置为 0。
  4. 完成初始化:所有构造函数的打印语句会依次执行。
打印输出

所以,当调用 val person2 = Person() 时,实际的调用顺序是:

  • 进入次构造函数 2,打印 "Called constructor2 with default values."
  • 进入次构造函数 1,打印 "Called constructor1 with name: name"

最终,person2name"name"age 为 0。

总结

val person2 = Person() 先进入次构造函数 2,然后通过 this("name") 进入次构造函数 1,最后调用主构造函数,形成了一个调用链。这个设计使得构造函数可以方便地进行重用和扩展。

2. 如何在多个次构造函数间传递参数

你可以灵活地在次构造函数间传递不同的参数。以下例子展示了多个次构造函数之间的调用。

示例:动态传递参数

class Person(val name: String, var age: Int) {

    // 次构造函数 1:只有 name
    constructor(name: String) : this(name, 0) {
        println("Initialized with name: $name, default age: 0")
    }

    // 次构造函数 2:传入 name 和 age
    constructor(name: String, birthYear: Int, currentYear: Int) : this(name, currentYear - birthYear) {
        println("Calculated age for $name: ${currentYear - birthYear}")
    }
}

fun main() {
    val person1 = Person("Bob")
    println("Person1 -> Name: ${person1.name}, Age: ${person1.age}")

    val person2 = Person("Charlie", 1995, 2024)
    println("Person2 -> Name: ${person2.name}, Age: ${person2.age}")
}

输出:

Initialized with name: Bob, default age: 0
Person1 -> Name: Bob, Age: 0
Calculated age for Charlie: 29
Person2 -> Name: Charlie, Age: 29

解释:
Person(“Bob”) 调用了次构造函数 1,并最终到达主构造函数。
Person(“Charlie”, 1995, 2024) 通过次构造函数 2 动态计算了年龄,并最终调用了主构造函数。

3. 总结

多个次构造函数可以通过 this 相互调用,但必须最终到达 主构造函数。
如果需要灵活处理不同的初始化参数,多个次构造函数之间的链式调用是很有用的。
调用顺序:
从被调用的次构造函数开始;每个次构造函数通过 this 逐级调用到主构造函数;一旦调用链到达主构造函数,开始执行初始化逻辑和 init 块。

7. 构造函数的可见性修饰符

你可以为构造函数设置 访问修饰符(如 private、protected 等)。
这对于控制类的实例化很有用。

class Person private constructor(val name: String)

解释:
使用 private 构造函数后,该类只能在内部创建实例,外部无法实例化。

8. 数据类 (Data Class) 构造函数

数据类自动生成 equals()、hashCode()、toString() 等函数,主构造函数中的参数会自动成为类的属性。

data class Person(val name: String, val age: Int)

fun main() {
    val person1 = Person("Alice", 25)
    val person2 = Person("Alice", 25)

    println(person1 == person2) // 输出: true
    println(person1) // 输出: Person(name=Alice, age=25)
}

9. 总结

主构造函数:定义在类头部,通常用于初始化属性,支持 init 块和默认参数。
次构造函数:用于提供多种初始化方式,必须调用主构造函数(若存在)。
init 块:用于在构造函数完成之后执行初始化逻辑。
访问修饰符:控制构造函数的访问权限。

你可能感兴趣的:(kotlin,kotlin,开发语言)