Skip to content

Kotlin 集合排序:让数据井然有序 🎉

引言:为什么排序如此重要?

想象一下,你正在开发一个电商系统的后端服务。用户搜索商品时,你需要按价格、销量、评分等不同维度对商品进行排序。如果没有高效的排序机制,用户体验将会一塌糊涂——谁愿意在一堆杂乱无章的商品中寻找自己想要的东西呢?

Kotlin 的集合排序功能就像是一位贴心的图书管理员,能够按照各种规则将你的数据整理得井井有条。今天我们就来深入了解这位"管理员"的工作原理和使用技巧。

核心概念:排序的四大法宝

1. sorted() - 自然排序的魔法师

sorted() 是最基础的排序方法,它按照元素的自然顺序(升序)进行排序。

kotlin
// 基础示例
val numbers = listOf(5, 4, 2, 1, 3, -10)
val sortedNumbers = numbers.sorted()
println(sortedNumbers) // [-10, 1, 2, 3, 4, 5]

NOTE

自然排序对于数字是升序,对于字符串是字典序(A-Z),对于日期是时间先后顺序。

2. sortedBy() - 自定义规则的艺术家

当你需要按照特定规则排序时,sortedBy() 就派上用场了。它接受一个选择器函数,根据函数返回值进行排序。

kotlin
// 按绝对值排序
val numbers = listOf(5, -4, 2, -1, 3, -10)
val sortedByAbs = numbers.sortedBy { kotlin.math.abs(it) }
println(sortedByAbs) // [-1, 2, 3, -4, 5, -10]

3. sortedDescending() - 倒序的反叛者

有时候我们需要从大到小排序,sortedDescending() 就是为此而生。

kotlin
val numbers = listOf(5, 4, 2, 1, 3, -10)
val descending = numbers.sortedDescending()
println(descending) // [5, 4, 3, 2, 1, -10]

4. sortedByDescending() - 自定义倒序的大师

结合了自定义规则和倒序排列的强大功能。

kotlin
val numbers = listOf(5, -4, 2, -1, 3, -10)
val descendingByAbs = numbers.sortedByDescending { kotlin.math.abs(it) }
println(descendingByAbs) // [-10, 5, -4, 3, 2, -1]

实战场景:电商商品排序系统

让我们用一个完整的 SpringBoot 项目来展示排序在实际业务中的应用:

kotlin
data class Product(
    val id: Long,
    val name: String,
    val price: Double,
    val rating: Double,
    val salesCount: Int,
    val createTime: LocalDateTime
)
kotlin
@Service
class ProductService {
    
    private val products = listOf(
        Product(1, "iPhone 15", 5999.0, 4.8, 1500, LocalDateTime.now().minusDays(30)),
        Product(2, "小米14", 3999.0, 4.6, 2300, LocalDateTime.now().minusDays(15)),
        Product(3, "华为Mate60", 6999.0, 4.9, 800, LocalDateTime.now().minusDays(45)),
        Product(4, "OPPO Find X7", 4599.0, 4.5, 1200, LocalDateTime.now().minusDays(20))
    )
    
    /**
     * 按价格排序(升序)
     */
    fun getProductsSortedByPrice(): List<Product> {
        return products.sorted() 
        // 错误:Product 没有实现 Comparable 接口
        
        return products.sortedBy { it.price } 
        // 正确:按价格升序排列
    }
    
    /**
     * 按评分排序(降序)- 评分高的在前
     */
    fun getProductsSortedByRating(): List<Product> {
        return products.sortedByDescending { it.rating } 
    }
    
    /**
     * 按销量排序(降序)- 热销商品在前
     */
    fun getHotProducts(): List<Product> {
        return products.sortedByDescending { it.salesCount } 
    }
    
    /**
     * 按上架时间排序(降序)- 新品在前
     */
    fun getNewestProducts(): List<Product> {
        return products.sortedByDescending { it.createTime } 
    }
    
    /**
     * 复合排序:先按评分降序,再按价格升序
     */
    fun getProductsWithComplexSort(): List<Product> {
        return products
            .sortedWith(compareByDescending<Product> { it.rating } 
                .thenBy { it.price }) 
    }
}
kotlin
@RestController
@RequestMapping("/api/products")
class ProductController(
    private val productService: ProductService
) {
    
    @GetMapping("/sort-by-price")
    fun getProductsByPrice(): ResponseEntity<List<Product>> {
        val products = productService.getProductsSortedByPrice()
        return ResponseEntity.ok(products)
    }
    
    @GetMapping("/sort-by-rating")
    fun getProductsByRating(): ResponseEntity<List<Product>> {
        val products = productService.getProductsSortedByRating()
        return ResponseEntity.ok(products)
    }
    
    @GetMapping("/hot-products")
    fun getHotProducts(): ResponseEntity<List<Product>> {
        val products = productService.getHotProducts()
        return ResponseEntity.ok(products)
    }
    
    @GetMapping("/newest")
    fun getNewestProducts(): ResponseEntity<List<Product>> {
        val products = productService.getNewestProducts()
        return ResponseEntity.ok(products)
    }
    
    @GetMapping("/complex-sort")
    fun getProductsWithComplexSort(): ResponseEntity<List<Product>> {
        val products = productService.getProductsWithComplexSort()
        return ResponseEntity.ok(products)
    }
}

高级技巧:排序的进阶玩法

1. 多条件排序

在实际业务中,我们经常需要按多个条件排序:

kotlin
@Service
class AdvancedSortService {
    
    /**
     * 多级排序:评分 > 销量 > 价格
     */
    fun getRecommendedProducts(products: List<Product>): List<Product> {
        return products.sortedWith(
            compareByDescending<Product> { it.rating }    // 1. 评分高的优先
                .thenByDescending { it.salesCount }       // 2. 销量高的优先
                .thenBy { it.price }                      // 3. 价格低的优先
        )
    }
    
    /**
     * 自定义排序逻辑:VIP用户的商品优先
     */
    fun getProductsForVip(products: List<Product>, vipProductIds: Set<Long>): List<Product> {
        return products.sortedWith { product1, product2 ->
            val isVip1 = vipProductIds.contains(product1.id)
            val isVip2 = vipProductIds.contains(product2.id)
            
            when {
                isVip1 && !isVip2 -> -1  // product1 是VIP商品,排在前面
                !isVip1 && isVip2 -> 1   // product2 是VIP商品,排在前面
                else -> product1.rating.compareTo(product2.rating) // 都是或都不是VIP,按评分排序
            }
        }
    }
}

2. 空值处理

在实际开发中,数据可能包含空值,我们需要妥善处理:

kotlin
data class ProductWithNullable(
    val id: Long,
    val name: String,
    val price: Double?,  // 可能为空
    val rating: Double?  // 可能为空
)

@Service
class NullSafeProductService {
    
    /**
     * 安全的价格排序:空值排在最后
     */
    fun sortByPriceSafely(products: List<ProductWithNullable>): List<ProductWithNullable> {
        return products.sortedWith(
            compareBy<ProductWithNullable> { it.price ?: Double.MAX_VALUE } 
        )
    }
    
    /**
     * 使用 nullsLast() 处理空值
     */
    fun sortByRatingWithNullsLast(products: List<ProductWithNullable>): List<ProductWithNullable> {
        return products.sortedWith(
            compareByDescending<ProductWithNullable> { it.rating }.nullsLast() 
        )
    }
}

性能优化与最佳实践

1. 排序性能对比

TIP

对于大数据集,考虑在数据库层面进行排序,而不是在应用层排序,这样可以减少内存使用和网络传输。

2. 缓存排序结果

kotlin
@Service
class CachedProductService {
    
    @Cacheable("sorted-products")
    fun getCachedSortedProducts(sortType: String): List<Product> {
        return when (sortType) {
            "price" -> products.sortedBy { it.price }
            "rating" -> products.sortedByDescending { it.rating }
            "sales" -> products.sortedByDescending { it.salesCount }
            else -> products
        }
    }
}

3. 分页排序

kotlin
@RestController
class PaginatedProductController(
    private val productService: ProductService
) {
    
    @GetMapping("/products/paged")
    fun getProductsPaged(
        @RequestParam(defaultValue = "0") page: Int,
        @RequestParam(defaultValue = "10") size: Int,
        @RequestParam(defaultValue = "price") sortBy: String,
        @RequestParam(defaultValue = "asc") direction: String
    ): ResponseEntity<Page<Product>> {
        
        val products = productService.getAllProducts()
        
        // 先排序再分页
        val sortedProducts = when (sortBy) {
            "price" -> if (direction == "desc") 
                products.sortedByDescending { it.price } 
                else products.sortedBy { it.price }
            "rating" -> if (direction == "desc") 
                products.sortedByDescending { it.rating } 
                else products.sortedBy { it.rating }
            else -> products
        }
        
        // 手动分页
        val startIndex = page * size
        val endIndex = minOf(startIndex + size, sortedProducts.size)
        val pagedProducts = sortedProducts.subList(startIndex, endIndex)
        
        return ResponseEntity.ok(
            PageImpl(pagedProducts, PageRequest.of(page, size), sortedProducts.size.toLong())
        )
    }
}

常见陷阱与解决方案

1. 排序稳定性问题

WARNING

Kotlin 的排序是稳定的,但在某些复杂场景下可能会出现意外结果。

kotlin
// 问题示例:相同评分的商品顺序可能不符合预期
val products = listOf(
    Product(1, "商品A", 100.0, 4.5, 100, LocalDateTime.now()),
    Product(2, "商品B", 200.0, 4.5, 200, LocalDateTime.now()),
    Product(3, "商品C", 150.0, 4.5, 150, LocalDateTime.now())
)

// 只按评分排序,相同评分的商品保持原有顺序
val sortedByRating = products.sortedByDescending { it.rating }

// 更好的做法:添加次要排序条件
val betterSorted = products.sortedWith(
    compareByDescending<Product> { it.rating }
        .thenByDescending { it.salesCount }  // 相同评分时按销量排序
)

2. 大数据集排序内存问题

CAUTION

对大数据集进行排序可能导致内存溢出,考虑使用流式处理或数据库排序。

大数据集排序优化方案
kotlin
@Service
class OptimizedSortService {
    
    /**
     * 使用 Sequence 进行惰性排序
     */
    fun sortLargeDatasetLazily(products: List<Product>): Sequence<Product> {
        return products.asSequence()
            .sortedByDescending { it.rating }
            .take(100)  // 只取前100个,避免全量排序
    }
    
    /**
     * 分批排序处理
     */
    fun sortInBatches(products: List<Product>, batchSize: Int = 1000): List<Product> {
        return products.chunked(batchSize) // 分批处理
            .map { batch -> batch.sortedByDescending { it.rating } }
            .flatten()
            .sortedByDescending { it.rating } // 最终合并排序
    }
}

总结与展望

Kotlin 的集合排序功能为我们提供了强大而灵活的数据整理能力。通过合理使用 sorted()sortedBy()sortedDescending()sortedByDescending() 这四大法宝,我们可以轻松应对各种排序需求。

关键要点回顾:

选择合适的排序方法:根据业务需求选择最适合的排序函数
处理空值:使用 nullsLast() 或默认值策略
多条件排序:使用 compareBy 系列函数
性能优化:考虑缓存、分页和数据库层排序
稳定性保证:添加次要排序条件确保结果可预测

TIP

在微服务架构中,排序逻辑应该尽可能下沉到数据层,这样可以减少网络传输和内存占用,提高系统整体性能。

掌握了这些排序技巧,你就能让你的 SpringBoot 应用中的数据始终保持井然有序,为用户提供更好的体验! 💯