8.Kotlin 类:类的基础:主构造函数与次构造函数

希望帮你在Kotlin进阶路上少走弯路,在技术上稳步提升。当然,由于个人知识储备有限,笔记中难免存在疏漏或表述不当的地方,也非常欢迎大家提出宝贵意见,一起交流进步。 ------ Android_小雨

整体目录:Kotlin 进阶不迷路:41 个核心知识点,构建完整知识体系

一、前言

1.1 类与构造函数的核心作用

在面向对象编程体系中, 是对一类事物共同属性和行为的抽象描述,相当于创建对象的"模板"。例如"Person"类可抽象人类的"姓名、年龄"等属性和"说话、行走"等行为。而对象则是类的具体实例,是模板的"实体化产物",比如通过"Person"类创建的"张三""李四"。

构造函数作为类的特殊成员,核心职责是初始化对象------在创建对象时为属性赋值、执行必要的初始化逻辑(如参数校验、资源加载),确保对象创建后处于可用状态。没有构造函数,对象的属性可能处于未定义的无效状态,无法正常使用。

1.2 Kotlin 构造函数的特点

相较于 Java 等传统面向对象语言,Kotlin 构造函数设计更简洁、灵活,核心特点体现在两方面:

  • 语法极致简洁:支持在类名后直接定义主构造函数,无需单独编写函数体;可通过"val/var"关键字将构造参数直接声明为类属性,省去手动定义属性和赋值的冗余代码。
  • 主/次构造分离:将构造函数分为"主构造函数"和"次构造函数",主构造承担核心初始化逻辑,次构造作为辅助补充,适配多样化实例化场景,同时通过委托机制保证初始化逻辑统一。

1.3 本文核心内容预告

本文将遵循"基础认知→核心详解→关联分析→实践应用→总结建议"的逻辑展开:首先解析主构造函数的语法、初始化方式及实例;接着讲解次构造函数的定义规则、适用场景及示例;然后深入分析主/次构造函数的委托关系、初始化顺序和参数传递逻辑;再结合实际开发场景说明两者的应用场景;最后总结核心知识点、避坑要点及选择技巧,帮助读者高效掌握 Kotlin 构造函数的使用。

二、主构造函数:类的核心初始化入口

主构造函数是 Kotlin 类的"默认初始化入口",承担类的核心初始化逻辑,是最常用的构造函数类型,其设计初衷是简化常规场景下的对象创建流程。

2.1 基本语法

Kotlin 主构造函数的语法极为简洁,直接在类名后通过"()"定义参数列表,无需像 Java 那样单独编写构造方法体。基本语法格式如下:

kotlin 复制代码
// 基础语法:类名后紧跟主构造参数列表
class 类名(参数1: 类型1, 参数2: 类型2, ...) {
    // 类体:包含属性、方法、初始化块等
}

// 含访问修饰符的语法(如私有主构造)
class 类名 private constructor(参数1: 类型1, 参数2: 类型2) {
    // 类体
}

语法说明:

  • 参数列表格式为"参数名: 类型",多个参数用逗号分隔,用于接收对象创建时传入的初始化数据;
  • 若类体无逻辑,花括号可省略,如"class Person(name: String, age: Int)";
  • 当主构造需要添加访问修饰符(如 private)时,必须显式添加"constructor"关键字,否则默认公开。

示例:定义"Person"类,主构造接收"姓名"和"年龄"参数:

kotlin 复制代码
// 简单主构造函数示例
class Person(name: String, age: Int) {
    // 类体可后续添加属性和方法
}

2.2 简洁初始化

主构造函数的核心优势是"简洁初始化",通过"val/var"修饰参数可直接将其声明为类成员属性,同时支持为参数设置默认值实现"可选参数",大幅简化代码。

2.2.1 直接声明属性

若构造参数需要作为类的属性被外部访问或内部使用,只需在参数前添加"val"(只读属性)或"var"(可读写属性),Kotlin 会自动完成属性定义和赋值。示例:

kotlin 复制代码
// 主构造参数直接声明为类属性
class Person(val name: String, var age: Int) {
    // 类方法使用属性
    fun introduce() {
        println("我叫$name,今年$age 岁")
    }
}

// 测试代码
fun main() {
    // 创建对象时直接传入参数初始化属性
    val zhangsan = Person("张三", 25)
    println(zhangsan.name) // 输出:张三(val修饰,只读)
    zhangsan.age = 26 // var修饰,可修改
    zhangsan.introduce() // 输出:我叫张三,今年26 岁
}

解析:通过"val name"和"var age"将构造参数直接声明为属性,创建对象时传入的参数会自动赋值给属性,无需像 Java 那样单独定义"private String name"再通过构造函数赋值,代码精简效果显著。

2.2.2 设置默认值

为构造参数设置默认值后,创建对象时可选择性传入该参数(传入则覆盖默认值,不传入则使用默认值),实现"按需初始化"。语法格式为"参数名: 类型 = 默认值",示例:

kotlin 复制代码
// 主构造参数设置默认值
class Person(val name: String, var age: Int = 18) {
    fun introduce() {
        println("我叫$name,今年$age 岁")
    }
}

// 测试代码
fun main() {
    // 1. 传入所有参数(覆盖默认值)
    val zhangsan = Person("张三", 25)
    zhangsan.introduce() // 输出:我叫张三,今年25 岁

    // 2. 只传入无默认值的参数(使用age默认值18)
    val lisi = Person("李四")
    lisi.introduce() // 输出:我叫李四,今年18 岁

    // 3. 按参数名传入,灵活调整顺序
    val wangwu = Person(age = 30, name = "王五")
    wangwu.introduce() // 输出:我叫王五,今年30 岁
}

解析:"age"参数默认值为 18,创建对象时可传可不传;按参数名传入时可打破参数顺序限制,尤其适合参数较多的场景,提升代码可读性。

2.3 初始化块

主构造函数本身没有函数体,无法编写复杂初始化逻辑(如参数校验、数据转换、资源加载)。Kotlin 提供**初始化块(Initializer Block)**解决这一问题,通过"init"关键字定义,用于补充主构造函数的初始化逻辑。

2.3.1 基本语法与执行时机

kotlin 复制代码
class 类名(参数列表) {
    // 初始化块:补充初始化逻辑
    init {
        // 初始化代码(参数校验、数据转换等)
    }

    // 多个初始化块按顺序执行
    init {
        // 第二个初始化块逻辑
    }
}

执行时机:创建对象时,初始化块会在主构造参数初始化后立即执行;若有多个初始化块,按代码定义顺序依次执行。

2.3.2 实用示例

示例 1:参数校验(确保对象创建的合法性)

kotlin 复制代码
class Person(val name: String, var age: Int) {
    // 初始化块:校验参数合法性
    init {
        require(name.isNotBlank()) { "姓名不能为空!" } // 非空校验
        require(age in 0..150) { "年龄必须在0-150之间!" } // 范围校验
        println("Person对象初始化完成:name=$name, age=$age")
    }
}

// 测试代码
fun main() {
    // 合法参数:正常初始化
    val zhangsan = Person("张三", 25) // 输出:Person对象初始化完成:name=张三, age=25

    // 非法参数:抛出异常
    // val lisi = Person("", 20) // 抛出IllegalArgumentException: 姓名不能为空!
    // val wangwu = Person("王五", 200) // 抛出IllegalArgumentException: 年龄必须在0-150之间!
}

示例 2:数据转换(处理参数格式适配)

kotlin 复制代码
class Person(val name: String, ageStr: String) {
    // 类属性:存储转换后的整数年龄
    val age: Int

    // 初始化块:将字符串年龄转换为整数
    init {
        require(name.isNotBlank()) { "姓名不能为空!" }
        // 转换失败则抛出异常
        age = ageStr.toIntOrNull() ?: throw IllegalArgumentException("年龄必须是整数!")
        require(age in 0..150) { "年龄必须在0-150之间!" }
    }
}

// 测试代码
fun main() {
    val zhangsan = Person("张三", "25")
    println("${zhangsan.name} 的年龄是 ${zhangsan.age}") // 输出:张三 的年龄是 25

    // 非法参数:抛出异常
    // val lisi = Person("李四", "abc") // 抛出IllegalArgumentException: 年龄必须是整数!
}

解析:主构造参数"ageStr"未用"val/var"修饰,仅作为临时参数;在初始化块中将其转换为整数后赋值给类属性"age",实现参数格式适配。

2.4 简单示例

综合以上知识点,通过"Book"类展示主构造函数的完整使用流程:

kotlin 复制代码
/**
 * 书籍类:主构造包含核心属性
 * title:书名(只读,必填)
 * author:作者(只读,必填)
 * price:价格(可读写,默认39.9)
 * category:分类(只读,默认"未知")
 */
class Book(
    val title: String,
    val author: String,
    var price: Double = 39.9,
    val category: String = "未知"
) {
    // 初始化块1:参数校验
    init {
        require(title.isNotBlank()) { "书名不能为空!" }
        require(author.isNotBlank()) { "作者不能为空!" }
        require(price > 0) { "价格必须大于0!" }
    }

    // 初始化块2:打印初始化信息
    init {
        println("书籍初始化完成:《$title》($author),分类:$category,价格:$price 元")
    }

    // 类方法:修改价格
    fun updatePrice(newPrice: Double) {
        require(newPrice > 0) { "新价格必须大于0!" }
        price = newPrice
        println("价格更新为:$newPrice 元")
    }
}

// 测试代码
fun main() {
    // 1. 传入所有参数
    val book1 = Book("Kotlin从入门到精通", "张三", 59.9, "编程")
    book1.updatePrice(49.9) // 输出:价格更新为:49.9 元

    // 2. 传入必填参数(使用默认值)
    val book2 = Book("Java核心技术", "李四")
    // 输出:书籍初始化完成:《Java核心技术》(李四),分类:未知,价格:39.9 元

    // 3. 按参数名传入(灵活调整顺序)
    val book3 = Book(
        title = "Python编程实践",
        author = "王五",
        category = "编程",
        price = 69.9
    )
}

三、次构造函数:灵活的辅助初始化方式

主构造函数适用于"参数固定、场景单一"的初始化场景,而实际开发中常需要"多样化参数组合""兼容旧逻辑"等灵活需求。Kotlin 的次构造函数作为辅助初始化方式,可适配这些复杂场景,同时通过委托机制保证核心逻辑统一。

3.1 基本语法

次构造函数通过"constructor"关键字定义,位于类体内部,必须通过": this(...)"委托给主构造函数或其他次构造函数。基本语法格式如下:

kotlin 复制代码
class 类名(主构造参数列表) {
    // 次构造函数1:委托给主构造函数
    constructor(参数列表1) : this(主构造参数映射逻辑) {
        // 次构造专属初始化逻辑(可选)
    }

    // 次构造函数2:委托给其他次构造函数
    constructor(参数列表2) : this(参数列表1映射逻辑) {
        // 次构造专属初始化逻辑(可选)
    }
}

语法说明:

  • "constructor"是次构造函数的标识,不可省略;
  • ": this(...)"是委托语句,必须存在------次构造函数不能独立初始化,最终需委托到主构造函数;
  • 花括号内是次构造专属逻辑,仅在通过该次构造创建对象时执行。

3.2 核心规则

次构造函数的核心规则是必须委托,即所有次构造函数最终都要委托到主构造函数,形成"次→主"或"次→次→主"的委托链。这一规则的本质是"保证主构造函数的核心初始化逻辑(参数校验、属性赋值)在所有场景下都被执行",避免不同构造函数导致的对象状态不一致。

3.2.1 委托给主构造函数(最常用)

次构造函数直接通过"this(主构造参数)"委托给主构造函数,示例:

kotlin 复制代码
class Person(val name: String, val age: Int, val gender: String) {
    // 初始化块:主构造核心逻辑
    init {
        require(name.isNotBlank()) { "姓名不能为空!" }
        require(age in 0..150) { "年龄必须在0-150之间!" }
        println("主构造初始化完成:$name($age 岁,$gender)")
    }

    // 次构造1:无参数,委托主构造(传默认值)
    constructor() : this("匿名", 0, "未知") {
        println("无参次构造执行:创建默认对象")
    }

    // 次构造2:双参数,委托主构造(性别默认)
    constructor(name: String, age: Int) : this(name, age, "未知") {
        println("双参次构造执行:性别默认未知")
    }
}

// 测试代码
fun main() {
    println("=== 无参次构造创建 ===")
    val person1 = Person()
    // 输出:主构造初始化完成:匿名(0 岁,未知)→ 无参次构造执行:创建默认对象

    println("=== 双参次构造创建 ===")
    val person2 = Person("张三", 25)
    // 输出:主构造初始化完成:张三(25 岁,未知)→ 双参次构造执行:性别默认未知
}

3.2.2 委托给其他次构造函数(链式委托)

次构造函数委托给另一个次构造函数,再由该次构造委托给主构造函数,形成链式委托,示例:

kotlin 复制代码
class Person(val name: String, val age: Int, val gender: String) {
    init {
        println("主构造初始化:$name($age,$gender)")
    }

    // 次构造3:双参数→委托主构造
    constructor(name: String, age: Int) : this(name, age, "未知") {
        println("双参次构造执行")
    }

    // 次构造2:单参数→委托次构造3
    constructor(name: String) : this(name, 18) {
        println("单参次构造执行")
    }

    // 次构造1:无参数→委托次构造2
    constructor() : this("匿名") {
        println("无参次构造执行")
    }
}

// 测试代码
fun main() {
    val person = Person()
    // 输出顺序:主构造初始化→双参次构造执行→单参次构造执行→无参次构造执行
}

3.3 适用场景

次构造函数的核心价值是"适配多样化初始化场景",常见适用场景包括:

3.3.1 多参数组合需求

当创建对象需要支持"不同数量、不同类型参数"时,通过次构造函数提供多个入口。例如"User"类需要支持:1. 用户名+密码登录;2. 用户名+密码+手机号注册;3. 第三方登录(如微信openId)。

kotlin 复制代码
class User(val username: String, val password: String, val phone: String?) {
    init {
        require(username.isNotBlank()) { "用户名不能为空!" }
        require(password.length >= 6) { "密码长度不能小于6位!" }
    }

    // 场景1:用户名+密码(手机号为空)
    constructor(username: String, password: String) : this(username, password, null)

    // 场景2:微信登录(自动生成用户名和密码)
    constructor(wechatOpenId: String) : this(
        username = "wechat_$wechatOpenId",
        password = generateRandomPwd(),
        phone = null
    )

    // 辅助方法:生成随机密码
    private fun generateRandomPwd(): String {
        val chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
        return (1..8).map { chars.random() }.joinToString("")
    }
}

3.3.2 兼容旧逻辑或第三方接口

项目迭代或对接第三方时,需兼容旧参数格式。例如旧系统"Order"类用"整数状态码"(0=待支付,1=已支付),新系统用"字符串状态",通过次构造函数适配。

kotlin 复制代码
// 新系统主构造:状态为字符串
class Order(val orderNo: String, val amount: Double, val status: String) {
    init {
        require(orderNo.isNotBlank()) { "订单号不能为空!" }
        require(amount > 0) { "金额必须大于0!" }
    }

    // 次构造:兼容旧系统整数状态码
    constructor(orderNo: String, amount: Double, statusCode: Int) : this(
        orderNo = orderNo,
        amount = amount,
        status = when (statusCode) {
            0 -> "待支付"
            1 -> "已支付"
            else -> throw IllegalArgumentException("状态码不合法!")
        }
    )
}

3.4 简单示例

通过"Student"类展示多个次构造函数的使用,适配不同创建场景:

kotlin 复制代码
/**
 * 学生类:主构造包含核心属性
 * id:学号(必填)
 * name:姓名(必填)
 * grade:年级(必填)
 * score:平均分(默认60.0)
 */
class Student(
    val id: String,
    val name: String,
    val grade: String,
    var score: Double = 60.0
) {
    init {
        require(id.isNotBlank()) { "学号不能为空!" }
        require(name.isNotBlank()) { "姓名不能为空!" }
    }

    // 次构造1:无参(测试用,生成默认学生)
    constructor() : this(
        id = "TEST_${System.currentTimeMillis()}",
        name = "测试学生",
        grade = "高一"
    )

    // 次构造2:三参数(使用默认平均分)
    constructor(id: String, name: String, grade: String) : this(id, name, grade, 60.0)

    // 次构造3:四参数(分数字符串转Double)
    constructor(id: String, name: String, grade: String, scoreStr: String) : this(
        id = id,
        name = name,
        grade = grade,
        score = scoreStr.toDoubleOrNull() ?: throw IllegalArgumentException("分数必须是数字!")
    )

    // 展示学生信息
    fun showInfo() {
        println("学号:$id,姓名:$name,年级:$grade,平均分:$score")
    }
}

// 测试代码
fun main() {
    // 1. 无参次构造
    Student().showInfo()
    // 2. 三参数次构造
    Student("2024001", "张三", "高一").showInfo()
    // 3. 四参数次构造(字符串分数)
    Student("2024002", "李四", "高二", "85.5").showInfo()
}

四、主构造函数与次构造函数的关联

主构造函数和次构造函数并非独立存在,而是通过"委托关系""初始化顺序""参数传递"形成紧密关联,理解这些关联是正确使用构造函数的关键。

4.1 委托关系

Kotlin 规定:所有次构造函数最终必须委托到主构造函数,形成"委托链"。这一规则的核心目的是"保证主构造函数的核心逻辑(参数校验、属性赋值)在任何初始化场景下都被执行",避免对象状态不一致。

委托关系的两种形式:

  1. 直接委托:次构造→主构造,如"constructor(name: String) : this(name, 18)";
  2. 间接委托:次构造→次构造→...→主构造,如"constructor() : this("匿名") → constructor(name: String) : this(name, 18)"。

强制要求:若类显式定义主构造函数,所有次构造函数必须显式委托,否则编译报错。例如以下代码编译失败:

kotlin 复制代码
// 编译失败:次构造未委托主构造
class Person(val name: String) {
    // 错误:缺少委托语句
    constructor(age: Int) { }
}

4.2 初始化顺序

通过次构造函数创建对象时,初始化逻辑的执行顺序是固定的,遵循"主构造参数初始化 → 初始化块(按顺序) → 次构造函数体"的规则。这一顺序决定了"哪些逻辑先执行,哪些属性可使用"。

4.2.1 执行顺序示例验证

kotlin 复制代码
class Person(val name: String, val age: Int) {
    // 初始化块1
    init {
        println("init块1:name=$name,age=$age")
    }

    // 类属性
    val gender: String = "未知"

    // 初始化块2
    init {
        println("init块2:gender=$gender")
    }

    // 次构造函数
    constructor(name: String) : this(name, 18) {
        println("次构造体执行:默认年龄18")
    }
}

// 测试代码
fun main() {
    val person = Person("张三")
}

输出结果:

ini 复制代码
init块1:name=张三,age=18
init块2:gender=未知
次构造体执行:默认年龄18

解析:执行顺序完全遵循规则,先完成主构造参数初始化和初始化块逻辑,再执行次构造体,确保次构造体可使用所有已初始化的属性。

4.3 参数传递

次构造函数向主构造函数传递参数是委托关系的核心,本质是"将次构造的输入参数转换为主构造所需参数",常见传递方式有三种:

4.3.1 直接传递

次构造参数与主构造参数一一对应,直接传入。示例:

kotlin 复制代码
class Person(val name: String, val age: Int, val gender: String) {
    // 次构造参数直接传递给主构造
    constructor(name: String, age: Int) : this(name, age, "未知")
}

4.3.2 默认值填充

次构造参数不足时,为缺失参数填充默认值后传递。示例:

kotlin 复制代码
class Book(val title: String, val author: String, val price: Double = 39.9) {
    // 无参次构造:填充所有默认值
    constructor() : this("未知书名", "未知作者")
}

4.3.3 数据转换

次构造参数类型与主构造不一致时,先转换再传递。示例:

kotlin 复制代码
class Product(val id: Long, val price: Double) {
    // 次构造:字符串id转Long,整数价格转Double
    constructor(idStr: String, priceInt: Int) : this(
        id = idStr.toLong(),
        price = priceInt.toDouble()
    )
}

五、实用场景举例

结合实际开发场景,说明主、次构造函数的合理搭配使用。

5.1 主构造函数场景:常规实体类初始化

数据库实体类、接口返回数据模型等场景,通常需要将外部数据直接映射为类属性,初始化逻辑简单,适合用主构造函数。

kotlin 复制代码
import java.util.Date

/**
 * 订单实体类:映射数据库order表
 * 字段与主构造参数一一对应,通过初始化块校验参数
 */
data class OrderEntity(
    val id: Long,
    val userId: Long,
    val amount: Double,
    val createTime: Date,
    val status: String
) {
    init {
        require(id > 0) { "订单ID必须大于0!" }
        require(userId > 0) { "用户ID必须大于0!" }
        require(amount > 0) { "金额必须大于0!" }
        require(status in listOf("待支付", "已支付", "已取消")) { "状态不合法!" }
    }
}

// 使用:接口返回数据直接构造对象
fun main() {
    val order = OrderEntity(
        id = 10001,
        userId = 101,
        amount = 199.9,
        createTime = Date(),
        status = "待支付"
    )
}

解析:数据模型类的核心需求是"属性映射"和"参数校验",主构造函数的简洁语法和初始化块的校验能力完美适配,代码清晰高效。

5.2 次构造函数场景:多场景初始化适配

工具类、第三方集成类等需要适配多种创建方式的场景,适合用"主构造+次构造"组合。例如"Excel导出工具类"需要支持:1. 指定文件路径;2. 指定输出流;3. 默认路径。

kotlin 复制代码
import java.io.OutputStream

/**
 * Excel导出工具类
 * 主构造:接收输出流(核心初始化方式)
 */
class ExcelExporter(private val outputStream: OutputStream) {
    // 初始化块:校验输出流
    init {
        require(outputStream != null) { "输出流不能为null!" }
    }

    // 次构造1:指定文件路径(转换为输出流)
    constructor(filePath: String) : this(java.io.FileOutputStream(filePath))

    // 次构造2:默认路径(项目根目录excel导出.xlsx)
    constructor() : this("${System.getProperty("user.dir")}/excel导出.xlsx")

    // 导出方法
    fun export(data: List<Map<String, Any>>) {
        // 导出逻辑:使用outputStream写入数据
        println("使用${outputStream}导出数据,共${data.size}条")
    }
}

// 测试代码
fun main() {
    val data = listOf(mapOf("name" to "张三", "age" to 25))
    // 1. 默认路径导出
    ExcelExporter().export(data)
    // 2. 指定路径导出
    ExcelExporter("D:/data.xlsx").export(data)
    // 3. 指定输出流导出
    ExcelExporter(System.out).export(data)
}

解析:主构造函数接收核心依赖"输出流",保证核心逻辑统一;次构造函数适配"文件路径""默认路径"等场景,通过参数转换委托给主构造,既灵活又保证一致性。

六、总结与使用建议

Kotlin 主/次构造函数的设计核心是"简洁高效+灵活适配",掌握两者的使用技巧可大幅提升对象初始化的代码质量。

6.1 核心知识点回顾

类型 核心语法 核心规则
主构造函数 类名后括号定义参数,val/var声明属性 核心初始化入口,可通过init块补充逻辑
次构造函数 constructor关键字定义,: this(...)委托 必须委托到主构造,适配多样化场景
关联关系 委托链、固定初始化顺序、参数映射传递 次构造最终委托主构造,保证逻辑统一

6.2 避坑点

  • 委托遗漏:显式定义主构造后,次构造必须委托,否则编译报错;
  • 初始化顺序错误:init块执行时次构造体未执行,不可在init块中依赖次构造体的逻辑;
  • 参数未声明为属性:主构造参数未加val/var时,仅为临时参数,类内部不可长期使用;
  • 默认值位置错误:有默认值的参数需放在无默认值参数之后,否则编译报错(如"class Person(age: Int = 18, name: String)"错误)。

6.3 选择技巧

  1. 优先使用主构造函数:常规场景(如实体类、数据模型)优先用主构造,通过val/var声明属性,init块补充校验,简洁高效;
  2. 复杂场景加次构造:需要适配多参数组合、兼容旧逻辑、第三方集成等场景时,添加次构造函数,通过委托复用主构造逻辑;
  3. 避免过度使用次构造:若次构造过多(超过3个),可考虑用"建造者模式"替代,提升代码可读性;
  4. 私有主构造控制实例化:需要限制对象创建方式(如单例模式)时,将主构造设为private,通过次构造或静态方法控制实例化。

总之,Kotlin 构造函数的使用核心是"主构造定核心,次构造补灵活",结合实际场景合理搭配,可写出简洁、高效、易维护的代码。

相关推荐
用户69371750013841 小时前
9.Kotlin 类:类的核心:属性 (Property) 与自定义访问器 (Getter/Setter)
android·后端·kotlin
kerli1 小时前
Android:使用 Tint 为图标 Icon 动态着色
android
回家路上绕了弯1 小时前
接口 QPS 从 100 飙到 1000?从应急到根治的全流程优化方案
分布式·后端
hqk1 小时前
鸿蒙零基础语法入门:开启你的开发之旅
android·前端·harmonyos
The_cute_cat2 小时前
通过内网穿透为课设临时添加域名访问【springboot+Vue】
vue.js·spring boot·后端
howcode2 小时前
女友去玩,竟带回一道 “虐哭程序员” 的难题
后端·设计模式·程序员
z***94842 小时前
SpringBoot教程(三十二) SpringBoot集成Skywalking链路跟踪
spring boot·后端·skywalking
程序员西西2 小时前
SpringBoot 数据存储实战拆解!
后端
QuantumLeap丶2 小时前
《Flutter全栈开发实战指南:从零到高级》- 17 -核心动画
android·flutter·ios