《深入理解Scala》——第2章,第2.4节用None不用null

2.4 用None不用null
深入理解Scala
Scala在标准库里提供了scala.Option类,鼓励大家在一般编程时尽量不要使用null。Option可以视作一个容器,里面要么有东西,要么什么都没有。Option通过两个子类来实现此含义:Some和None。Some表示容器里有且仅有一个东西,None表示空容器,有点类似List的Nil的含义。
在Java和其他允许null的语言里,null经常作为一个占位符用于返回值,表示非致命的错误,或者表示一个变量未被初始化。Scala里,你可以用Option的None子类来代表这个意思,反过来用Option的Some子类代表一个初始化了的变量或者非致命(non-fatal)的变量状态。我们来看看这两个类的用法。

不包含任何值的Option用None对象来构建,包含一个值的Option用Some工厂方法来创建。Option提供了很多不同的方法用来把其值取出来。用得特别多的是get和getOrElse方法。get方法会尝试访问Option里保存的值,如果Option是空的则抛出异常。这和其他语言里访问可能为null的变量一样。getOrElse也访问Option里存放的值,有则返回,否则返回其参数(作为默认值)。你应该尽量使用getOrElse而不是get。
Scala在Option的伴生对象里提供了工厂方法,这个方法能把Java风格的引用(null代表空变量)转换为Option类型,使其更明确。我们快速过一下。

如果输入是null,Option工厂方法会创建一个None对象,如果输入是初始化了的值,则创建一个Some对象。这使我们处理来自不信任的来源(比如另一种JVM语言)的输入,把输入包装成Option时容易许多。你可能会问,为什么我要这么做?代码里检查一下null不是一样简单吗?好吧,Option提供了一些高级特性,使它比简单用if null检查要理想得多。
Option高级技巧
Option的最重要特性是可以被当作集合看待。这意味着你可以对Option使用标准的map、flatMap、foreach等方法,还可以用在for表达式里。这不仅有助于确保优美简洁的语法,而且开启了另一种不同的处理未初始化值的方法。我们来看几个常见问题,分别用null和Option来解决。第一个问题是创建对象或返回默认值。
1.创建对象或返回默认值
代码里有很多地方需要在某变量有值的时候构建某结果,变量没值的时候构建一个默认值。假设我们有个应用在执行时需要某种临时文件存储。应用设计为用户能在命令行下提供可选的参数指定一个目录来存放临时文件,如果不指定目录,那我们要返回一个合理的默认临时文件目录。我们来创建一个返回临时文件目录的方法。

getTemporaryDirectory接受Option[String]类型的参数,返回指向我们将使用的临时文件目录的File对象。我们首先对option应用map方法,在参数有值的情况下创建一个File对象。然后我们用filter方法来确保这个新创建的文件对象必须是目录,filter方法检查option里的值是否符合断言要求,如果不符合就转化为None。最后我们检查Option里是否有值,如果没有则返回默认的文件路径。
这使得我们可以不需要嵌套很多(判断是否为空的)if语句或代码块就可以实施一系列的检查。有时候我们会想要基于某个参数是否存在来决定是否执行一个代码块。
2.如果变量已初始化则执行代码块
可以通过foreach方法来做到仅当Option有值时才执行某段代码块。foreach方法正如其名所示,遍历Option里的所有值。因为Option只能有零或一个值,所以其代码块要么执行,要么不执行。foreach语法和for表达式协作尤其好用。我们来看个例子。

如你所见,代码看上去就像一般的“迭代一个集合”的控制块。如果我们需要迭代多个变量,还是用相似的语法。我们来看个案例,假设我们使用某种Java Servlet框架,现在我们想要对用户做验证。如果验证成功,我们要把安全令牌注入(inject)HttpSession,以便后续的filter和servlet可以检查用户的访问权限。

注意你可以在for表达式里嵌入条件逻辑。这样可以在代码里少用嵌套的逻辑代码块。另一个要点是所有的辅助方法都不需要使用Option类。Option用作对未初始化变量的一道优良的防火墙,你代码的其他部分可以不受污染(译者注:指不需要到处判断非空,也不需要到处使用Option,防火墙后的部分直接处理有值的情况就可以了)。在Scala里,参数类型为Option表示参数可能是未初始化的。Scala的惯例是不要把null或未初始化的参数传给函数。
Scala的for表达式相当强大,你甚至可以用它产生值,而不只是执行代码块。当你想把一些可能为空的参数转化为某个其他结果变量的时候,这个功能就非常好用了。
3.用多个可能未初始化的变量构造另一个变量
有时候我们需要把多个可能未初始化的变量转化为一个变量以便处理。为此我们要再次使用for表达式,这次加上yield。我们来看个案例,假设我们从用户输入或者某个安全位置读取了数据库配置信息,然后尝试用这个参数创建数据库连接。因为这只是个工具函数,不需要直接面对用户,所以我们不想对获取连接失败的情况做很多处理。我们只想简单地把数据库配置参数转化为一个Option,里面放上我们的数据库连接。

这个函数准确地达成了我们期望,虽然看上去只是在DriverManager.getConnection外面包了一层。那如果我们想把这种包装方法抽象化,让我们能把任意函数包装成同样对Option友好的版本要怎么做呢?来看一下我们称为lift的函数。

lift3方法看上去有点像我们之前那个createConnection方法,差别在于它接受一个函数作为唯一的参数。如你在REPL里所见,我们可以把它应用在已有的函数上,创建出Option友好的函数来。我们直接接受DriverManager.getConnection方法,然后把它提升(lift)为语义上与我们之前的createConnection方法相等的函数。这个技巧在“封装”未初始化变量时很有效。你在写大部分代码,包括工具类时,可以假定所有变量都是初始化好的,然后在需要的地方把你的函数lift成Option友好的版本。
有一点要重点注意,Option根据其包含的值来计算判等和散列值。用Scala的时候,理解判等和散列值是非常重要的,尤其是在多态的场景下。

时间: 2024-08-26 21:58:10

《深入理解Scala》——第2章,第2.4节用None不用null的相关文章

《深入理解Scala》——第1章,第1.4节与JVM的无缝集成

1.4 与JVM的无缝集成 深入理解Scala Scala的吸引力之一在于它与Java和JVM的无缝集成.Scala与Java有很强的兼容性,比如说Java类可以直接映射为Scala类.这种紧密联系使Java到Scala的迁移相当简单,但在使用Scala的一些高级特性时还是需要小心的,Scala有些高级特性是Java里没有的.在Scala语言设计时已经小心地考虑了与Java无缝交互的问题,用Java写的库,大部分可以直接照搬(as-is)到Scala里. 1.4.1 Scala调用Java 从S

《深入理解Scala》——第2章,第2.1节学习使用Scala交互模式(REPL)

第2章 核心规则深入理解Scala 本章包括的内容: • 使用Scala交互模式(Read Eval Print Loop 简称REPL) • 面向表达式编程 • 不变性(Immutability) • Option类 本章内容覆盖了每个新Scala开发者都需要知道的几个主题.本章不会深入到每个主题里,但是会讲到可以让你自己去接着探索的程度.你将学会使用REPL,学会如何利用这个工具做软件的快速原型开发.然后我们会学到面向表达式编程,并从另一个视角来看控制结构是怎么回事.在此基础上,我们来研究不

《深入理解Scala》——第1章,第1.3节静态类型和表达力

1.3 静态类型和表达力 深入理解Scala 开发人员中有一个误解,认为静态类型必然导致冗长的代码.之所以如此是因为很多继承自C的语言强制要求程序员必须在代码中多处明确地指定类型.随着软件开发技术和编译器理论的发展,情况已经改变.Scala利用了其中一些技术进步来减少样板(boilerplate)代码,保持代码简洁. Scala做了以下几个简单的设计决策,以提高代码表达力. • 把类型标注(type annotation)换到变量右边. • 类型推断. • 可扩展的语法. • 用户自定义的隐式转

《深入理解Scala》——第1章,第1.5节总结

1.5 总结 深入理解Scala 本章中,你学到了一些Scala的设计理念.设计Scala的初衷在于把不同语言中的多种概念融合起来.Scala融合了函数式和面向对象编程,尽管显然Java也已经这么做了.Scala精选其语法,极大地减少了语言中的繁冗之处,使一些强大的特性可以优雅地表达,比如类型推断.最后,Scala和Java能够紧密集成,而且运行在Java虚拟机上,这或许是让Scala变成一种实用选择的最重要的一点.几乎不花代价就可以把Scala用于我们的日常工作中. 因为Scala融合了多种概

《深入理解Scala》——第1章,第1.1节Scala一种混合式编程语言

第1章 Scala--一种混合式编程语言 Scala是一种将其他编程语言中的多种技巧融合为一的语言.Scala尝试跨越多种不同类型的语言,给开发者提供面向对象编程.函数式编程.富有表达力的语法.静态强类型和丰富的泛型等特性,而且全部架设于Java虚拟机之上.因此开发者使用Scala时可以继续使用原本熟悉的某种编程特性,但要发挥Scala的强大能力则需要结合使用这些有时候相互抵触的概念和特性,建立一种平衡的和谐.Scala对开发者的真正解放之处在于让开发者可以随意使用最适合手头上的问题的编程范式.

《深入理解Scala》——第2章,第2.6节总结

2.6 总结 深入理解Scala 本章中我们了解了Scala编程时的第一个关键组成部分.利用REPL做快速原型是每个成功的Scala开发者必须掌握的关键技术之一.面向表达式编程和不可变性都有助于简化程序和提高代码的可推理性.Option也有助于可推理性,因为它明确声明了是否接受空值.另外,在多态的场景下实现好的判等可能不容易.以上这些实践可以帮助我们成功踏出Scala开发的第一步.要想后面的路也走得顺利,我们就必须来看一下编码规范,以及如何避免掉进Scala解析器的坑.

《深入理解Scala》——第1章,第1.2节当函数式编程遇见面向对象

1.2 当函数式编程遇见面向对象 深入理解Scala 函数式编程和面向对象编程是软件开发的两种不同途径.函数式编程并非什么新概念,在现代开发者的开发工具箱里也绝非是什么天外来客.我们将通过Java生态圈里的例子来展示这一点,主要来看Spring Application framework和Google Collections库.这两个库都在Java的面向对象基础上融合了函数式的概念,而如果我们把它们翻译成Scala,则会优雅得多.在深入之前,我们需要先理解面向对象编程和函数式编程这两个术语的含义

《深入理解Scala》——第2章,第2.2节优先采用面向表达式编程

2.2 优先采用面向表达式编程 深入理解Scala 面向表达式编程是个术语,意思是在代码中使用表达式而不用语句.表达式和语句的区别是什么?语句是可以执行的东西,表达式是可以求值的东西.在实践中这有什么意义呢?表达式返回值,语句执行代码,但是不返回值.本节我们将学习面向表达式编程的全部知识,并理解它对简化程序有什么帮助.我们也会看一下对象的可变性,以及可变性与面向表达式编程的关系. 作者注:语句VS表达式 语句是可以执行的东西,表达式是可以求值的东西. 表达式是运算结果为一个值的代码块.Scala

《深入理解Scala》——第2章,第2.3节优先选择不变性

2.3 优先选择不变性 深入理解Scala 编程中的不变性指对象一旦创建后就不再改变状态.这是函数式编程的基石之一,也是JVM上的面向对象编程的推荐实践之一.Scala也不例外,在设计上优先选择不变性,在很多场景中把不变性作为默认设置.对此,你可能一下子会不适应.本节中,我们将学到不变性对于判等问题和并发编程能提供什么帮助. Scala里首先要明白的是不变对象和不变引用(immutable referene)的区别.Scala里的所有变量都是指向对象的引用.把变量声明为val意味着它是个不变"引