Effective Kotlin中文版
  • ReadMe
  • 前言
  • 第一部分:良好的代码
    • 第一章:安全性
      • 第1条:限制可变性
      • 第2条:最小化变量的作用域
      • 第3条:尽可能消除平台类型
      • 第4条: 不要暴露需要推断的类型
      • 第5条:指明你期望的参数和状态
      • 第 6 条: 优先使用标准错误,而不是自定错误
      • 第7条:当返回结果可能缺失时,优先使 null 或 Failure
      • 第8条:妥善处理空值
      • 第9条: 使用 use 来关闭资源
      • 第10条:编写单元测试
    • 第二章:可读性
      • 第11条:为了可读性设计代码
      • 第12条:操作符的行为应该与其名称一致
      • 第13条:避免返回或操作 Unit?
      • 第14条: 在变量不清晰时指定其类型
      • 第15条:考虑显式引用接收者
      • 第16:属性应该代表状态,而非行为
      • 第17条:考虑使用具名参数
      • 第18条:遵守编程惯例
  • 第二部分:良好的设计
    • 第三章:可重用性
      • 第19条:不要重复知识
      • 第20条:不要重复实现常用算法
      • 第21条 使用属性代理来提取公共的属性模式
      • 第22条:当实现公共算法时使用泛型
      • 第23条:避免隐藏类型参数
      • 第24条:在使用泛型时考虑型变
      • 第25条:在不同的平台上提取公共模块进行重用
    • 第四章:抽象设计
      • 第26条:每个方法都应该基于单一的抽象级别而编写
      • 第27条:使用抽象来保护代码不受更改
      • 第28条:指定 Api 的稳定性
      • 第29条:考虑包装扩展 API
      • 第30条:最小化元素的可见性
      • 第31条:用文档定义合约
      • 第32条:遵守抽象合约
    • 第五章:对象的创建
      • 第33条:考虑使用工厂方法代替构造函数
      • 第34条:考虑带命名默认参数的主构造函数
      • 第35条:考虑为复杂的对象创建定义 DSL
    • 第六章:类的设计
      • 第36条:组合优于继承
      • 第37条:使用数据修饰符来表示一组数据
      • 第38条:使用函数类型而不是接口来传递操作和行为
      • 第39条:类层次结构优于标签类
      • 第40条:遵守 equals 的合约
      • 第41条:遵守 hashCode 的合约
      • 第42条:遵守 compareTo 的合约
      • 第43条: 考虑将 API 的非必要部分提取到扩展函数中
      • 第44条:避免在成员中定义扩展
  • 第三部分:性能
    • 第七章:让开发成本更低
      • 第45条:避免不必要的对象创建
      • 第46条:给高阶函数使用 inline 修饰符
      • 第47条:考虑使用内联类
      • 第48条:消除过时的对象引用
    • 第八章:高效的集合处理
      • 第49条:在具有多个处理步骤的大型集合上,优先使用 Sequence
      • 第50条:限制操作步骤的数量
      • 第51条:性能关键处考虑使用原语的数组
      • 第52条:考虑使用可变集合
Powered by GitBook
On this page
  • 我们需要 compareTo 吗?
  • 实现 compareTo
  1. 第二部分:良好的设计
  2. 第六章:类的设计

第42条:遵守 compareTo 的合约

compareTo 方法不在 Any 类中。它是 Kotlin 的一个运算符,可以换成数学中不等式的符号:

obj1 > obj2 // Translates to obj1.compareTo(obj2) > 0
obj1 < obj2 // Translates to obj1.compareTo(obj2) < 0
obj1 >= obj2 // Translates to obj1.compareTo(obj2) >= 0
obj1 <= obj2 // Translates to obj1.compareTo(obj2) <= 0

它也位于 Compareable<T> 接口中。当对象实现此接口,或者实现具有名为 compareTo 的操作符函数时,这意味着对象是有顺序的。具备顺序性需要遵循下面的规则:

  • 反对称性, 即如果 a >= b 且 b >= a,则 a == b。 因此比较与相等性是有关系的,它们需要相互一致

  • 传递性, 即如果 a >= b 且 b >= c,则 a >= c。 类似的,如果 a > b 且 b > c,则 a > c。这个特性很重要,因为如果没有了它,元素的排序可能会花费很长的时间

  • 连通性,意味着任意两个元素间必定有一个关系。所以要么是 a >= b,要么是 b >= a 。在 Kotlin 中, compareTo 的类型系统保证了这一点,因为它返回 Int 值,每个 Int 要么是正的,要么是负的,要么是0。这很重要,因为如果两个元素之间没有联系,就不能使用快速排序或插入排序等经典算法。相反,我们需要使用一种特殊的算法来处理偏序,比如拓扑排序。

我们需要 compareTo 吗?

在 Kotlin 中,我们很少实现 compareTo。我们可以根据具体情况来指定顺序,而不是指定死整个的排序规则。例如,我们可以使用 sortedBy 对集合进行排序,并提供一个可以比较的键。所以在下面的例子中,我们按照姓氏对用户进行排序:

class User(val name: String, val surname: String)
val names = listOf<User>(/*...*/)

val sorted = names.sortedBy { it.surname }

如果我们需要一个比对比键更加复杂的比较呢?为此,可以使用 sortedWith 函数,该函数使用比较器对元素进行排序。这个比较器可以用 compareBy 来生成。在下面的例子中,我们首先按姓氏对用户进行排序,对于姓氏相同的,则按照名字来比较:

val sorted = names
    .sortedWith(compareBy({ it.surname }, { it.name }))

当然,我们可以让 User 实现 Comparable<User>,但它应该选择什么样的顺序呢? 我们可能根据属性对它们进行排序,但当这一点不完全清楚时,最好不要让这些对象具有可比性。

String 有一个自然的顺序,即字母和数字的顺序,因此它是实现了 Compare<String>。这个事实非常有用,因为我们经常需要按字母数字顺序对文本进行排序。然而,它也有缺点,例如,我们可以使用不等号比较两个字符串,这看起来非常不直观。大多数人看到两个字符串比较时使用不等号会感到困惑。

// 不要这样做!
print("Kotlin" > "Java") // true

当然,有些对象是具有清晰的自然秩序。度量单位、日期、时间都是很好的例子。如果你不确定你的对象是否具有自然顺序,最好使用比较器。如果你经常使用它们中的一些,你可以把它们放在你的类的伴生对象中:

class User(val name: String, val surname: String) {
    // ...
    
    companion object {
        val DISPLAY_ORDER =
            compareBy(User::surname, User::name)
    }
}

val sorted = names.sortedWith(User.DISPLAY_ORDER)

实现 compareTo

当我们需要实现 compareTo 时,有顶级函数可以帮助我们。如果你只需要比较两个值,你可以使用 compareValues 函数:

class User(
    val name: String,
    val surname: String
): Comparable<User> {
    override fun compareTo(other: User): Int =
        compareValues(surname, other.surname)
}

如果你需要使用更多的值比较,或者你需要使用选择器来比较他们,你可以使用 compareValuesBy:

class User(
    val name: String,
    val surname: String
): Comparable<User> {
    override fun compareTo(other: User): Int =
        compareValuesBy(this, other, { it.surname }, { it.name })
}

这个函数帮助我们创建绝大多数能满足需求的比较器,如果你需要实现一些特殊的逻辑,记住它们应该返回:

  • 如果接收方和另一方相等,返回 0

  • 如果接收方大于另一方,返回正数

  • 如果接收方小于另一方,返回负数

一旦你这样做了,不要忘了验证你的比较是反对称的、传递的和具有连接性的。

Previous第41条:遵守 hashCode 的合约Next第43条: 考虑将 API 的非必要部分提取到扩展函数中

Last updated 2 years ago