- Introduction
- Part 1 Good Code
- Chapter 1 Safety
- 引言
- 第1条:限制可变性
- 第2条:最小化变量作用域
- 第3条:尽快消除平台类型
- 第4条:不要把推断类型暴露给外部
- Item 5 Specify Your Expectations On Arguments And State
- 第6条:尽可能使用标准库中提供的异常
- 第7条:当不能返回预期结果时,优先使用null o或Failure 作为返回值
- Item 8 Handle Nulls Properly
- 第9条:使用use关闭资源
- Item 10 Write Unit Tests
- Chapter 2 Readability
- Introduction
- Item 11 Design For Readability
- Item 12 Operator Meaning Should Be Consistent With Its Function Name
- Item 13 Avoid Returning Or Operating On Unit
- Item 14 Specify The Variable Type When It Is Not Clear
- Item 15 Consider Referencing Receivers Explicitly
- Item 16 Properties Should Represent State Not Behavior
- Item 17 Consider Naming Arguments
- Item 18 Respect Coding Conventions
- Part 2 Code Design
- Chapter 3 Reusability
- Introduction
- Item 19 Do Not Repeat Knowledge
- Item 20 Do Not Repeat Common Algorithms
- Item 21 Use Property Delegation To Extract Common Property Patterns
- Item 22 Use Generics When Implementing Common Algorithms
- Item 23 Avoid Shadowing Type Parameters
- Item 24 Consider Variance For Generic Types
- Item 25 Reuse Between Different Platforms By Extracting Common Modules
- Chapter 4 Abstraction Design
- Introduction
- Item 26 Each Function Should Be Written In Terms Of A Single Level Of Abstraction
- Item 27 Use Abstraction To Protect Code Against Changes
- Item 28 Specify API Stability
- Item 29 Consider Wrapping External API
- Item 30 Minimize Elements Visibility
- Item 31 Define Contract With Documentation
- Item 32 Respect Abstraction Contracts
- Chapter 5 Object Creation
- Introduction
- Item 33 Consider Factory Functions Instead Of Constructors
- Item 34 Consider A Primary Constructor With Named Optional Arguments
- Item 35 Consider Defining A DSL For Complex Object Creation
- Chapter 6 Class Design
- Introduction
- Item 36 Prefer Composition Over Inheritance
- Item 37 Use The Data Modifier To Represent A Bundle Of Data
- Item 38 Use Function Types Instead Of Interfaces To Pass Operations And Actions
- Item 39 Prefer Class Hierarchies To Tagged Classes
- Item 40 Respect The Contract Of Equals
- Item 41 Respect The Contract Of Hash Code
- Item 42 Respect The Contract Of Compare To
- Item 43 Consider Extracting Non Essential Parts Of Your API Into Extensions
- Item 44 Avoid Member Extensions
- Part 3 Efficiency
- Chapter 7 Make It Cheap
- Introduction
- Item 45 Avoid Unnecessary Object Creation
- Item 46 Use Inline Modifier For Functions With Parameters Of Functional Types
- Item 47 Consider Using Inline Classes
- Item 48 Eliminate Obsolete Object References
- Chapter 8 Efficient Collection Processing
- Introduction
- Item 49 Prefer Sequence For Big Collections With More Than One Processing Step
- Item 50 Limit The Number Of Operations
- Item 51 Consider Arrays With Primitives For Performance Critical Processing
- Item 52 Consider Using Mutable Collections
- Published with GitBook
Item 42 Respect The Contract Of Compare To
Item 42: Respect the contract of compareTo
The compareTo
method is not in the Any
class. It is an operator in Kotlin that translates into the mathematical comparison signs:
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
It is also located in the Comparable
interface. When an object implements this interface or when it has an operator method named compareTo
, it means that this object has a natural order. Such an order needs to be:
- Antisymmetric, meaning that if a >= b and b >= a then a == b. Therefore there is a relation between comparison and equality and they need to be consistent with each other.
- Transitive, meaning that if a >= b and b >= c then a >= c. Similarly when a > b and b > c then a > c. This property is important because without it, sorting of elements might take literally forever in some sorting algorithms.
- Connex, meaning that there must be a relationship between every two elements. So either a >= b, or b >= a. In Kotlin, it is guaranteed by typing system for
compareTo
because it returnsInt
, and everyInt
is either positive, negative or zero. This property is important because if there is no relationship between two elements, we cannot use classic sorting algorithms like quicksort or insertion sort. Instead, we need to use one of the special algorithms for partial orders, like topological sorting.
Do we need a compareTo?
In Kotlin we rarely implement compareTo
ourselves. We get more freedom by specifying the order on a case by case basis than by assuming one global natural order. For instance, we can sort a collection using sortedBy
and provide a key that is comparable. So in the example below, we sort users by their surname:
class User(val name: String, val surname: String)
val names = listOf<User>(/*...*/)
val sorted = names.sortedBy { it.surname }
What if we need a more complex comparison than just by a key? For that, we can use the sortedWith
function that sorts elements using a comparator. This comparator can be produced using a function compareBy
. So in the following example, we first sort users comparing them by their surname
, and if they match, we compare them by their name
:
val sorted = names
.sortedWith(compareBy({ it.surname }, { it.name }))
Surely, we might make User
implement Comparable
, but what order should it choose? We might need to sort them by any property. When this is not absolutely clear, it is better to not make such objects comparable.
String
has a natural order, which is an alphanumerical order, and so it implements Comparable
. This fact is very useful because we often do need to sort text alphanumerically. However, it also has its downsides. For instance, we can compare two strings using a comparision sign, which seems highly unintuitive. Most people seeing comparison sign between two strings will be rather confused.
// DON'T DO THIS!
print("Kotlin" > "Java") // true
Surely there are objects with a clear natural order. Units of measure, date and time are all perfect examples. Although if you are not sure about whether your object has a natural order, it is better to use comparators instead. If you use a few of them often, you can place them in the companion object of your class:
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)
Implementing compareTo
When we do need to implement compareTo
ourselves, we have top-level functions that can help us. If all you need is to compare two values, you can use the compareValues
function:
class User(
val name: String,
val surname: String
): Comparable<User> {
override fun compareTo(other: User): Int =
compareValues(surname, other.surname)
}
If you need to use more values, or if you need to compare them using selectors, use compareValuesBy
:
class User(
val name: String,
val surname: String
): Comparable<User> {
override fun compareTo(other: User): Int =
compareValuesBy(this, other, { it.surname }, {
it.name })
}
This function helps us create most comparators we might need. If you need to implement some with a special logic, remember that it should return:
- 0 if the receiver and
other
are equal - a positive number if the receiver is greater than
other
- a negative number if the receiver is smaller than
other
Once you did that, don’t forget to verify that your comparison is antisymmetric, transitive and connex.