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
  1. 第一部分:良好的代码
  2. 第一章:安全性

第4条: 不要暴露需要推断的类型

Kotlin 的类型推断是 JVM 世界中最受欢迎的 Kotlin 特性之一。如此的受欢迎,以至于 Java 10 也引入了类型推断(不过与Kotlin相比是有限的)。虽然在使用这个特性的时候也有一些危险。最重要的是,我们需要记住赋值的隐式类型是右边的类型,而不是某些超类或接口:

open class Animal
class Zebra: Animal()

fun main() {
    var animal = Zebra()    
    animal = Animal() // Error: Type mismatch
}

这里 animal 是一个需要推断的类型。 在大多数情况下,这并不是一个问题,当推断的类型过于严格时,我们只需要指定它,问题就解决了:

open class Animal
class Zebra: Animal()

fun main() {
    var animal: Animal = Zebra()
    animal = Animal()
}

但是,当这些类型是另外一个不受我们控制的库或者模块来提供时,就没上面的方案那样顺利了。在这种情况下,暴露了需要推断的类型可能非常危险,我们来看一个例子。

假设你有以下用于代表汽车工厂的接口:

interface CarFactory {
    fun produce(): Car
}

如果没有其他参数,就使用默认的 Car:

val DEFAULT_CAR: Car = Fiat126P()

因为可能大部分工厂都会生成这个Car,所以你把它设成了默认。 你省略了函数返回的类型,因为你定义了 DEFAULT_CAR 是一个 Car 类型,如下所示:

interface CarFactory {
    fun produce() = DEFAULT_CAR
}

类似地,后来有人查看了 DEFAULT_CAR 并且发现可以通过下面语句来推断其类型:

val DEFAULT_CAR: Car = Fiat126P()

现在,你们所有的工厂只能生产 Fiat126P 了,这可不是什么好事,如果你自己定义了这个接口,这个问题可能很快就被发现并轻松的解决。然而,如果它是对外 API 的一部分,你就有可能会先被愤怒的调用者找上门了。

除此之外,当我们遇到一些不太熟知的 API时, 返回类型是很重要的信息,所以为了可读性,我们也应该明确地指出其类型,特别是我们 API 上外部可见的部分。

总结

一般的规则是,如果我们不确定类型时,就应该指定它,这是很重要的讯息,我们不应该隐藏它(第 14 条:变量不清晰时,指定其类型)。此外,为了安全起见,在对外 API 中,我们应该始终指定类型。我们不能让它们因偶然而改变。推断的类型可能会有太多的限制,或者在我们项目的发展过程中太容易发生变化了。

Previous第3条:尽可能消除平台类型Next第5条:指明你期望的参数和状态

Last updated 2 years ago