Skip to content

Kotlin 作用域函数 apply 深度解析 🎉

引言:为什么需要 apply

想象一下,你刚买了一台新电脑,需要进行各种初始化设置:安装软件、配置桌面、设置用户信息等。传统的做法是:

kotlin
val computer = Computer()
computer.installSoftware("IntelliJ IDEA")
computer.setWallpaper("kotlin-logo.jpg")
computer.configureUser("Developer")
computer.enableDarkMode()
// ... 更多配置

这样写有什么问题吗?代码重复、冗长,而且如果 computer 变量名很长,会让代码变得更加臃肿。

TIP

apply 就像是给对象提供了一个"装修工作台",你可以在这个工作台上对对象进行各种"装修"操作,最后还能把装修好的对象完整地交还给你。

核心概念:apply 的本质

什么是 apply

apply 是 Kotlin 中的一个作用域函数(Scope Function),它的核心特点是:

  • 执行代码块:在对象上执行一段代码
  • 返回对象本身:执行完毕后返回调用 apply 的对象
  • this 引用:在代码块内部,this 指向调用对象

设计哲学:链式调用与流畅接口

apply 的设计遵循了流畅接口(Fluent Interface)的设计模式,让代码读起来更像自然语言:

kotlin
val person = Person()
    .apply { name = "张三" }
    .apply { age = 25 }
    .apply { email = "zhangsan@example.com" }

NOTE

流畅接口的核心思想是让代码的可读性接近自然语言,减少认知负担。

在 SpringBoot 中的实际应用场景

场景一:配置类初始化

在 SpringBoot 项目中,我们经常需要创建配置对象:

kotlin
@Configuration
class DatabaseConfig {
    
    fun createDataSource(): DataSource {
        val config = HikariConfig()
        config.jdbcUrl = "jdbc:mysql://localhost:3306/mydb"
        config.username = "root"
        config.password = "password"
        config.maximumPoolSize = 20
        config.minimumIdle = 5
        config.connectionTimeout = 30000
        config.idleTimeout = 600000
        config.maxLifetime = 1800000
        
        return HikariDataSource(config)
    }
}
kotlin
@Configuration
class DatabaseConfig {
    
    fun createDataSource(): DataSource {
        val config = HikariConfig().apply {
            jdbcUrl = "jdbc:mysql://localhost:3306/mydb"
            username = "root"
            password = "password"
            maximumPoolSize = 20
            minimumIdle = 5
            connectionTimeout = 30000
            idleTimeout = 600000
            maxLifetime = 1800000
        }
        
        return HikariDataSource(config)
    }
}

TIP

使用 apply 后,代码更加简洁,配置项的归属关系也更加清晰。

场景二:实体类构建器模式

在处理复杂的业务实体时,apply 可以很好地替代传统的构建器模式:

kotlin
@Entity
@Table(name = "users")
data class User(
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    var id: Long? = null,
    
    @Column(nullable = false)
    var username: String = "",
    
    @Column(nullable = false)
    var email: String = "",
    
    @Column(name = "created_at")
    var createdAt: LocalDateTime = LocalDateTime.now(),
    
    @Column(name = "updated_at")
    var updatedAt: LocalDateTime = LocalDateTime.now(),
    
    var isActive: Boolean = true
) {
    constructor() : this(null, "", "", LocalDateTime.now(), LocalDateTime.now(), true)
}

@Service
class UserService {
    
    fun createUser(registerRequest: RegisterRequest): User {
        return User().apply {
            username = registerRequest.username
            email = registerRequest.email
            createdAt = LocalDateTime.now()
            updatedAt = LocalDateTime.now()
            isActive = true
        }
    }
}

IMPORTANT

在 JPA 实体类中使用 apply 时,确保有无参构造函数,这是 JPA 的要求。

场景三:响应对象构建

在 RESTful API 开发中,构建响应对象是常见需求:

kotlin
data class ApiResponse<T>(
    var code: Int = 200,
    var message: String = "Success",
    var data: T? = null,
    var timestamp: Long = System.currentTimeMillis()
)

@RestController
@RequestMapping("/api/users")
class UserController(private val userService: UserService) {
    
    @GetMapping("/{id}")
    fun getUserById(@PathVariable id: Long): ApiResponse<User> {
        val user = userService.findById(id)
        
        return ApiResponse<User>().apply {
            code = 200
            message = "用户信息获取成功"
            data = user
            timestamp = System.currentTimeMillis()
        }
    }
    
    @PostMapping
    fun createUser(@RequestBody request: CreateUserRequest): ApiResponse<User> {
        return try {
            val user = userService.createUser(request)
            ApiResponse<User>().apply {
                code = 201
                message = "用户创建成功"
                data = user
            }
        } catch (e: Exception) {
            ApiResponse<User>().apply { 
                code = 500
                message = "用户创建失败: ${e.message}"
                data = null
            } 
        }
    }
}

高级应用:链式调用与条件配置

链式调用的威力

kotlin
@Component
class EmailService {
    
    fun sendWelcomeEmail(user: User): EmailMessage {
        return EmailMessage().apply {
            to = user.email
            subject = "欢迎加入我们的平台!"
            template = "welcome-template"
        }.apply {
            // 添加个性化内容
            addVariable("username", user.username)
            addVariable("registrationDate", user.createdAt.format(DateTimeFormatter.ISO_LOCAL_DATE))
        }.apply {
            // 设置发送选项
            priority = EmailPriority.HIGH
            trackOpens = true
            trackClicks = true
        }
    }
}

条件配置

kotlin
@Configuration
class RedisConfig {
    
    @Bean
    fun redisTemplate(): RedisTemplate<String, Any> {
        return RedisTemplate<String, Any>().apply {
            connectionFactory = jedisConnectionFactory()
            keySerializer = StringRedisSerializer()
            valueSerializer = GenericJackson2JsonRedisSerializer()
            
            // 根据环境进行条件配置
            if (isProductionEnvironment()) {
                defaultSerializer = GenericJackson2JsonRedisSerializer()
                enableTransactionSupport = true
            }
        }
    }
    
    private fun isProductionEnvironment(): Boolean {
        // 环境判断逻辑
        return System.getProperty("spring.profiles.active") == "prod"
    }
}

最佳实践与常见陷阱

✅ 最佳实践

  1. 对象初始化场景apply 最适合用于对象的初始化配置
kotlin
val httpClient = OkHttpClient.Builder().apply {
    connectTimeout(30, TimeUnit.SECONDS)
    readTimeout(30, TimeUnit.SECONDS)
    writeTimeout(30, TimeUnit.SECONDS)
    addInterceptor(loggingInterceptor)
}.build()
  1. 配置对象构建:特别适合配置类的构建
kotlin
@Bean
fun webMvcConfigurer(): WebMvcConfigurer {
    return object : WebMvcConfigurer {}.apply {
        // 这里可以进行一些额外的配置
    }
}

⚠️ 常见陷阱

  1. 过度使用导致可读性下降
kotlin
// ❌ 不推荐:过度嵌套
val result = SomeObject().apply {
    property1 = "value1"
    nestedObject = AnotherObject().apply {
        nestedProperty = "nested"
        deepNestedObject = DeepObject().apply {
            deepProperty = "deep"
        }
    }
}

// ✅ 推荐:适度使用
val nestedObj = AnotherObject().apply {
    nestedProperty = "nested"
    deepNestedObject = createDeepObject()
}

val result = SomeObject().apply {
    property1 = "value1"
    nestedObject = nestedObj
}
  1. 与其他作用域函数混淆

WARNING

不要混淆 apply 与其他作用域函数:

  • apply:返回对象本身,使用 this
  • also:返回对象本身,使用 it
  • let:返回 lambda 结果,使用 it
  • run:返回 lambda 结果,使用 this

性能考虑

内存分配

kotlin
// 性能测试示例
@Component
class PerformanceTestService {
    
    fun createLargeObjectTraditional(): LargeObject {
        val obj = LargeObject()
        obj.property1 = "value1"
        obj.property2 = "value2"
        // ... 更多属性设置
        return obj
    }
    
    fun createLargeObjectWithApply(): LargeObject {
        return LargeObject().apply {
            property1 = "value1"
            property2 = "value2"
            // ... 更多属性设置
        }
    }
}

NOTE

apply 本身不会带来显著的性能开销,它主要是语法糖,编译后的字节码与传统方式基本相同。

实战案例:订单处理系统

让我们通过一个完整的订单处理系统来展示 apply 的实际应用:

完整的订单处理示例
kotlin
// 订单实体
@Entity
@Table(name = "orders")
data class Order(
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    var id: Long? = null,
    
    @Column(name = "order_number", unique = true)
    var orderNumber: String = "",
    
    @Column(name = "user_id")
    var userId: Long = 0,
    
    @Column(name = "total_amount")
    var totalAmount: BigDecimal = BigDecimal.ZERO,
    
    @Enumerated(EnumType.STRING)
    var status: OrderStatus = OrderStatus.PENDING,
    
    @Column(name = "created_at")
    var createdAt: LocalDateTime = LocalDateTime.now(),
    
    @Column(name = "updated_at")
    var updatedAt: LocalDateTime = LocalDateTime.now(),
    
    @OneToMany(mappedBy = "order", cascade = [CascadeType.ALL])
    var items: MutableList<OrderItem> = mutableListOf()
) {
    constructor() : this(null, "", 0, BigDecimal.ZERO, OrderStatus.PENDING, LocalDateTime.now(), LocalDateTime.now(), mutableListOf())
}

enum class OrderStatus {
    PENDING, CONFIRMED, SHIPPED, DELIVERED, CANCELLED
}

// 订单项实体
@Entity
@Table(name = "order_items")
data class OrderItem(
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    var id: Long? = null,
    
    @ManyToOne
    @JoinColumn(name = "order_id")
    var order: Order? = null,
    
    @Column(name = "product_id")
    var productId: Long = 0,
    
    var quantity: Int = 0,
    
    @Column(name = "unit_price")
    var unitPrice: BigDecimal = BigDecimal.ZERO,
    
    @Column(name = "total_price")
    var totalPrice: BigDecimal = BigDecimal.ZERO
) {
    constructor() : this(null, null, 0, 0, BigDecimal.ZERO, BigDecimal.ZERO)
}

// 订单创建请求
data class CreateOrderRequest(
    val userId: Long,
    val items: List<OrderItemRequest>
)

data class OrderItemRequest(
    val productId: Long,
    val quantity: Int,
    val unitPrice: BigDecimal
)

// 订单服务
@Service
@Transactional
class OrderService(
    private val orderRepository: OrderRepository,
    private val orderNumberGenerator: OrderNumberGenerator
) {
    
    fun createOrder(request: CreateOrderRequest): Order {
        // 使用 apply 创建订单对象
        val order = Order().apply {
            orderNumber = orderNumberGenerator.generate()
            userId = request.userId
            status = OrderStatus.PENDING
            createdAt = LocalDateTime.now()
            updatedAt = LocalDateTime.now()
        }
        
        // 使用 apply 创建订单项
        val orderItems = request.items.map { itemRequest ->
            OrderItem().apply {
                this.order = order 
                productId = itemRequest.productId
                quantity = itemRequest.quantity
                unitPrice = itemRequest.unitPrice
                totalPrice = itemRequest.unitPrice.multiply(BigDecimal(itemRequest.quantity))
            }
        }.toMutableList()
        
        // 设置订单项并计算总金额
        order.apply {
            items = orderItems
            totalAmount = orderItems.sumOf { it.totalPrice }
            updatedAt = LocalDateTime.now()
        }
        
        return orderRepository.save(order)
    }
    
    fun updateOrderStatus(orderId: Long, newStatus: OrderStatus): Order {
        val order = orderRepository.findById(orderId)
            .orElseThrow { OrderNotFoundException("订单不存在: $orderId") }
        
        return order.apply {
            status = newStatus
            updatedAt = LocalDateTime.now()
        }.let { orderRepository.save(it) }
    }
}

// 订单控制器
@RestController
@RequestMapping("/api/orders")
class OrderController(private val orderService: OrderService) {
    
    @PostMapping
    fun createOrder(@RequestBody request: CreateOrderRequest): ApiResponse<Order> {
        return try {
            val order = orderService.createOrder(request)
            
            ApiResponse<Order>().apply {
                code = 201
                message = "订单创建成功"
                data = order
                timestamp = System.currentTimeMillis()
            }
        } catch (e: Exception) {
            ApiResponse<Order>().apply {
                code = 500
                message = "订单创建失败: ${e.message}"
                data = null
                timestamp = System.currentTimeMillis()
            }
        }
    }
    
    @PutMapping("/{id}/status")
    fun updateOrderStatus(
        @PathVariable id: Long,
        @RequestParam status: OrderStatus
    ): ApiResponse<Order> {
        return try {
            val order = orderService.updateOrderStatus(id, status)
            
            ApiResponse<Order>().apply {
                code = 200
                message = "订单状态更新成功"
                data = order
            }
        } catch (e: OrderNotFoundException) {
            ApiResponse<Order>().apply {
                code = 404
                message = e.message ?: "订单不存在"
                data = null
            }
        }
    }
}

与其他 Kotlin 特性的协同

与数据类的结合

kotlin
data class UserProfile(
    var username: String = "",
    var email: String = "",
    var avatar: String = "",
    var preferences: UserPreferences = UserPreferences()
) {
    fun updateFromRequest(request: UpdateProfileRequest): UserProfile {
        return this.apply {
            username = request.username ?: username
            email = request.email ?: email
            avatar = request.avatar ?: avatar
            preferences = preferences.apply {
                theme = request.theme ?: theme
                language = request.language ?: language
                notifications = request.notifications ?: notifications
            }
        }
    }
}

与扩展函数的结合

kotlin
// 为 HttpServletResponse 添加扩展函数
fun HttpServletResponse.applyJsonResponse(): HttpServletResponse {
    return this.apply {
        contentType = "application/json;charset=UTF-8"
        characterEncoding = "UTF-8"
        setHeader("Cache-Control", "no-cache")
    }
}

@RestController
class ApiController {
    
    @GetMapping("/data")
    fun getData(response: HttpServletResponse): String {
        response.applyJsonResponse()
        return """{"message": "Hello, World!"}"""
    }
}

总结与展望

核心价值回顾

apply 函数的核心价值在于:

  1. 提升代码可读性:让对象初始化代码更加清晰
  2. 支持链式调用:实现流畅的接口设计
  3. 减少重复代码:避免重复的对象引用
  4. 增强表达能力:让代码更接近自然语言

使用建议

TIP

何时使用 apply

  • 对象初始化和配置
  • 需要链式调用的场景
  • 构建复杂对象时

何时避免使用:

  • 简单的单一属性设置
  • 过度嵌套的场景
  • 性能敏感的热点代码

学习路径建议

apply 函数虽然看似简单,但它体现了 Kotlin 语言设计的优雅和实用性。通过合理使用 apply,我们可以写出更加简洁、可读、易维护的代码。在 SpringBoot 项目中,apply 更是处理配置、构建对象、响应数据等场景的得力助手。

继续探索 Kotlin 的其他作用域函数(letrunwithalso),你会发现它们各有特色,组合使用时能发挥更大的威力! 💯