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. 第一章:安全性

第9条: 使用 use 来关闭资源

有些资源是不能自动关闭的,一旦不再需要它们,我们就需要调用 close 方法,我们在 Kotlin/JVM 中使用 Java 标准库,涵盖了很多这样的资源,例如:

  • InputStream 和 OutputStream

  • java.sql.Connection

  • java.io.Reader(FileReader、BufferedReader、CSSParser)

  • java.new.Socket 和 java.util.Scanner

所有这些资源都实现了 Closeable 接口,其扩展了 AutoCloseable 接口

问题是在这些情况下,我们必须先确保我们不再需要这些资源,才能去执行其 close 方法,因为这些资源是相当昂贵的,它们不会轻易的关闭自身(如果我们不处理,垃圾收集器最终会收集它们,但这需要一些时间)。因此,为了确保关闭它们,我们通常将这些资源包装在 try-finally 块中,并在那里调用 close:

fun countCharacterInFile(path: String): Int {
    val reader = BufferedReader(FileReader(path))
    try {
        return reader.lineSequence().sumBy { it.length }
    } finally {
        reader.close()
    }
}

这样的代码结构既复杂又不正确,因为 close 函数可能会抛出错误,而这样的错误缺没有捕获。同样,如果 try 块 和 finally 块同时有错误,那么我们只能感知到某一个块的错误 。我们所期望的行为是将新出现错误的信息添加到前一个错误信息中去。这个函数的正确实现是漫长而又复杂的,但它也是常见的,因此它被抽象到 use 函数中。它应该被用于资源和处理异常。此函数可用于任何实现了 Closeable 的对象:

fun coyntCharactersInFile(path: String): Int {
    val reader = BufferedReader(FileReader(path))
    reader.use {
        return reader.lineSequence().sumBy { it.length }
    }
}

接收者(在本例中是 reader)作为参数传递到 Lambda 表达式中,所以该语法可以简化成:

fun coyntCharactersInFile(path: String): Int {
    BufferedReader(FileReader(path)).user { reader ->
          return reader.lineSequence().sumBy { it.length }
    }
}

由于文件经常需要这种支持,而且逐行读取也很常见,在 Kotlin 标准库中还有一个 useLines 函数,它为我们提供了一个所有行的序列(String),并在处理完后关闭底层的 reader:

 fun countCharactersInFile(path: String): Int {
    File(path).useLines { lines ->
        return lines.sumBy { it.length }
    }
}

这是一个处理大文件的合适方式,因为我们需要读取文件多个 line,并且在内存中一次不保留超过一行,代价是这个序列只能使用一次,如果需要多次遍历文件中的内容,就需要多次打开文件。 useLines 也可以用作表达式:

 fun countCharactersInFile(path: String): Int = 
    File(path).useLines { lines -> lines.sumBy { it.length }}

上述所有实现都使用序列对文件进行操作,这是正确的做法。多亏了这一点,我们总是可以只准备一行,而不是加载整个文件内容。关于它的更多信息,请参见_第49条:在处理具有多个步骤的大型集合时,优先使用序列_。

总结

使用 use 代码块来处理实现了 Closeable 或 AutoCloseable 接口的资源。这是一个既安全又简单的选择,当需要对文件进行操作时,请考虑使用 useLines ,它会产生一个序列,用于读取每一行。

Previous第8条:妥善处理空值Next第10条:编写单元测试

Last updated 2 years ago