Kotlin 类与对象之构造函数(四)

本文深入探讨Kotlin中的构造函数概念,包括主构造函数、次构造函数及其使用场景,通过实例解析构造函数如何与初始化块配合工作,以及在继承中的应用。

关键字:Class

构造函数
  • 主构造函数
  • 次构造函数
主构造函数

只能有一个主构造函数,主构造函数为类头的一部分:在类名之后

class Person constructor(firstName: String) { ... }

主构造函数不能包含任何代码,初始化的代码可以放到以 init 关键字作为前缀的初始化块

class test(arg : Int){
    init{
        println("init Data,data is $arg")
    }
}
  • 若初始化代码块和属性赋值同时交叉出现,则按顺序执行
  • 构造函数有注解或可见性修饰符,则 constructor 关键字是必需的
次构造函数

每个次构造函数都需要委托给主构造函数

class Person (name : String) {    
    var pName = name   // 赋值
    var area = "GD"    // 初始化
    
    // 次构造函数
    constructor(name: String,area : String):this(name){ 
        this.area = area 
    }   
    
    // 初始化代码块 是主构造函数的一部分,
    //所有初始化块中的代码都会在次构造函数体之前执行
    init {  
        println("Init Data,data is $name") 
    }  
    
    fun printName(){  //函数一
        println("Name is $pName")  
    }  
    fun printArea(){ //函数二
      println("Area is $area")  
    }
}

测试:

fun main(args:Array<String>){  
    var person = Person("long","GX")    
    var person2 = Person("integer") 
    println("***************************")   
    person.printName()  
    person.printArea()  
    println("***************************")   
    person2.printName() 
    person2.printArea() 
    println("***************************") 
    println("Get Area : ${person2.area}")
    // var 默认实现了getter和 setter,直接调用/赋值
}

输出:

Init Data,data is long
Init Data,data is integer
***************************
Name is long
Area is GX
***************************
Name is integer
Area is GD
***************************
Get Area : GD

继承

kotlin中所有的类都有一个共同的超类 Any,但是Any不同于Object,他只有equals()、hashCode() 与 toString()

允许定义的类可以被继承:open关键字

open class Person (name : String) {
       ...
       constructor(name: String,area : String):this(name){    
       this.area = area    
       printName()
}


 }
class Student(var grade:Int,name :String,area:String) : Person(name,area) { 
    init { 
        println("Grade is $grade") 
    }
}
  • kotlin 里面继承的关键字是“:”
  • 构造函数里面的参数带了 var、val 即为全局变量
  • 子类中的构造函数必须得调用父类中的构造函数< : Person(name,area) >
    代表调用父类Person的一个次构造函数
  • 没有主构造函数的时候,定义了次构造函数,次构造函数也需要调用父构造函数
class Student : Person {  
   constructor():super("zero","SZ"){}
   
   constructor(area:String):super("zero",area){}
  
}

简单来说,:就是类似于extends;后面的括号在继承是调用了父类的构造函数,当没有主构造函数时,可以省略

资料参考、学习

<think>我们被要求详细解释Kotlin中的主构造函数、次构造函数以及懒加载的概念和用法。我们将参考站内引用,并结合Kotlin语言特性进行说明。 ## 一、主构造函数(Primary Constructor) 主构造函数头部的一部分,紧跟在名之后。它用于初始化,可以包含参数。主构造函数不能包含任何代码,初始化代码可以放在`init`块中。根据引用[1]: > Kotlin在定义时不一定有主构造函数(例如密封,或想要实现单例用private修饰constructor),主构造函数是在创建对象时调用的函数,可以有参数也可以无参数。但如果有参数则必须在创建时初始化参数。 ### 基本语法: ```kotlin class Person constructor(firstName: String) { // 体 } ``` 如果主构造函数没有任何注解或者可见性修饰符,可以省略`constructor`关键字: ```kotlin class Person(firstName: String) { // 体 } ``` ### 初始化属性: 主构造函数的参数可以在体内用于初始化属性,也可以直接在参数中声明属性(使用`val`或`var`): ```kotlin class Person(val firstName: String, var age: Int) { // 此时firstName为只读属性,age为可变属性 } ``` ### `init`初始化块: 主构造函数不能包含代码,但可以使用`init`块来执行初始化逻辑。在对象创建时,`init`块会按照它们在体中出现的顺序执行: ```kotlin class Person(name: String) { val firstName: String init { firstName = name.capitalize() } } ``` ## 二、次构造函数(Secondary Constructor) 还可以声明次构造函数,使用`constructor`关键字。次构造函数必须直接或间接地委托给主构造函数(使用`this`关键字)。如果没有主构造函数,则次构造函数不需要委托。 ### 基本语法: ```kotlin class Person { constructor(name: String) { // 初始化代码 } } ``` ### 委托给主构造函数: 如果有主构造函数,那么每个次构造函数都必须委托给主构造函数(直接或通过其他次构造函数间接委托): ```kotlin class Person(val name: String) { var age: Int = 0 constructor(name: String, age: Int) : this(name) { this.age = age } } ``` 这里,次构造函数`constructor(name: String, age: Int)`通过`this(name)`调用了主构造函数。 ### 没有主构造函数的情况: 如果没有声明主构造函数,那么次构造函数就是主要的构造函数,此时不需要委托: ```kotlin class Person { val name: String var age: Int constructor(name: String) { this.name = name this.age = 0 } constructor(name: String, age: Int) { this.name = name this.age = age } } ``` ## 三、懒加载(Lazy Initialization) 懒加载是一种延迟初始化的技术,即只有在第一次访问属性时才进行初始化。Kotlin提供了两种主要的懒加载方式:`lateinit`和`by lazy`。 ### 1. `lateinit`(延迟初始化) `lateinit`用于修饰非空型且可变(`var`)的属性,它告诉编译器该属性会在稍后初始化(通常在构造函数之外,如`onCreate`方法中)。根据引用[5]: ```kotlin class MainActivity : AppCompatActivity() { private lateinit var mBtn: Button // 延迟初始化 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) mBtn = findViewById(R.id.button) // 初始化 mBtn.setOnClickListener { ... } } } ``` **注意**: - 只能用于`var`属性,且不能是可空型(即不能用`?`)。 - 在使用前必须初始化,否则会抛出`UninitializedPropertyAccessException`。 ### 2. `by lazy`(惰性初始化) `by lazy`用于只读属性(`val`),它接受一个lambda表达式,该表达式在第一次访问属性时执行,并将结果存储起来,后续访问直接返回存储的值。根据引用[3]和引用[4]: ```kotlin val lazyValue: String by lazy { println("computed!") // 只在第一次访问时执行 "Hello" } ``` **线程安全**: `lazy`函数可以指定线程安全模式: - `LazyThreadSafetyMode.SYNCHRONIZED`(默认):线程安全,使用锁保证只有一个线程初始化。 - `LazyThreadSafetyMode.PUBLICATION`:允许个线程同时初始化,但只有第一个完成的值会被使用。 - `LazyThreadSafetyMode.NONE`:非线程安全,仅在单线程环境下使用。 示例(引用[3]): ```kotlin class KotlinLazy { val lazyName: String by lazy(mode = LazyThreadSafetyMode.PUBLICATION) { println("init ${Thread.currentThread().name}") "kotlin ${Thread.currentThread().name}" } } ``` ## 、初始化顺序 在创建对象时,初始化顺序如下(引用[1]): 1. 主构造函数的参数初始化(如果存在)。 2. 体中的属性声明和初始化(按顺序)。 3. `init`块中的代码(按顺序)。 4. 次构造函数体中的代码。 ## 总结 - **主构造函数**:定义在头部,用于初始化参数,可以省略`constructor`关键字,初始化代码放在`init`块中。 - **次构造函数**:使用`constructor`定义,必须委托给主构造函数(如果存在)。 - **懒加载**: - `lateinit`:用于`var`属性,延迟初始化,需在使用前赋值。 - `by lazy`:用于`val`属性,线程安全,第一次访问时初始化。 这些机制为Kotlin提供了灵活且安全的初始化方式。 --- ### 相关问题 1. Kotlin中主构造函数和次构造函数可以同时存在吗?它们之间如何协作? 2. 使用`lateinit`时如何避免`UninitializedPropertyAccessException`异常? 3. `by lazy`的三种线程安全模式分别适用于什么场景? 4. 在Kotlin中,如果一个没有主构造函数,那么它的初始化顺序是怎样的? 5. 如何检查`lateinit`变量是否已经初始化?</think>### Kotlin构造函数、次构造函数懒加载详解 #### 一、主构造函数(Primary Constructor) 主构造函数头部的一部分,用于声明的基本初始化参数。特点: 1. **声明位置**:直接跟在名后 2. **语法**:使用 `constructor` 关键字(可省略) 3. **初始化逻辑**:通过 `init` 代码块实现 4. **属性声明**:可直接在参数中声明属性(使用 `val`/`var`) ```kotlin // 主构造函数声明 class Person( val name: String, // 只读属性 var age: Int // 可变属性 ) { init { println("主构造函数初始化:$name, $age") // [^1] } } // 使用 val person = Person("Alice", 30) ``` > 主构造函数在创建对象时立即执行,`init` 块按声明顺序执行[^1]。 --- #### 二、次构造函数(Secondary Constructor) 当需要种初始化方式时,使用次构造函数: 1. **委托机制**:必须直接或间接调用主构造函数(使用 `this`) 2. **语法**:在体内声明 `constructor` 3. **典型场景**:提供默认值或参数转换 ```kotlin class Person(val name: String, var age: Int) { // 次构造函数1:委托给主构造函数 constructor(name: String) : this(name, 0) { println("次构造函数1调用") } // 次构造函数2:基于出生年份计算年龄 constructor(name: String, birthYear: Int) : this(name) { age = Calendar.getInstance().get(Calendar.YEAR) - birthYear println("次构造函数2计算年龄:$age") } } // 使用示例 val p1 = Person("Bob") // 调用次构造函数1 val p2 = Person("Charlie", 1990) // 调用次构造函数2 ``` > 初始化顺序:主构造函数 → init块 → 次构造函数体[^1]。 --- #### 三、懒加载(Lazy Initialization) 延迟初始化资源密集型对象Kotlin 提供两种方式: ##### 1. `by lazy`(惰性初始化) - **适用场景**:只读属性(`val`) - **线程安全**:默认同步锁(`LazyThreadSafetyMode.SYNCHRONIZED`) ```kotlin class DatabaseService { val connection: Connection by lazy { println("创建数据库连接...") DriverManager.getConnection("jdbc:mysql://localhost/db") } } // 首次访问时初始化 val service = DatabaseService() service.connection // 此时触发初始化 ``` ##### 2. `lateinit`(延迟初始化) - **适用场景**:可变属性(`var`),如 Android 的 View 初始化 - **注意事项**:使用前需确保已初始化 ```kotlin class MainActivity : AppCompatActivity() { private lateinit var submitButton: Button // 延迟初始化[^5] override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) submitButton = findViewById(R.id.btn_submit) // 实际初始化 submitButton.setOnClickListener { // 安全使用 } } } ``` ##### 线程安全模式(`by lazy`专属) ```kotlin val safeValue: String by lazy(LazyThreadSafetyMode.PUBLICATION) { // 允许线程同时初始化,但仅第一个结果被保留[^3] "Thread: ${Thread.currentThread().name}" } ``` --- ### 关键对比 | 特性 | 主构造函数 | 次构造函数 | `by lazy` | `lateinit` | |---------------------|-------------------------------|-------------------------------|-------------------------|----------------------| | **初始化时机** | 创建对象时立即执行 | 显式调用时执行 | 首次访问属性时 | 显式赋值时 | | **适用属性型** | 所有属性 | 所有属性 | 仅 `val` | 仅 `var` | | **线程安全** | 不涉及 | 不涉及 | 可配置 | 无保障 | | **典型使用场景** | 基础属性初始化 | 提供种初始化方式 | 资源密集型对象 | Android View初始化 | > 完整初始化顺序:主构造函数参数 → 属性声明 → `init` 块 → 次构造函数体[^1]。 --- ### 相关问题 1. Kotlin 中如何通过次构造函数实现默认参数值? 2. `by lazy` 的三种线程安全模式有何具体区别? 3. 如何检查 `lateinit` 变量是否已完成初始化? 4. 主构造函数中的 `val`/`var` 参数和体内声明的属性有何区别? 5. 在 `init` 块中访问 `lateinit` 变量会导致什么问题?
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值