《深入理解Scala》——第2章,第2.4节用None不用null-阿里云开发者社区

开发者社区> 数据库> 正文

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

简介:

本节书摘来自异步社区《深入理解Scala》一书中的第2章,第2.4节用None不用null,作者[美]Josh Suereth,更多章节内容可以访问云栖社区“异步社区”公众号查看

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)的变量状态。我们来看看这两个类的用法。


6ffa3d4a55e4a132d47050aeb0c14d1fcb624e24

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

5e3770500be6010123274adf79044769d5504d4a

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


0fe9063786c9c51fcdee69321081425c3ddcd858

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


a4726b11f1d6015bd3bc186e5e8b98cb57ec828f

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


e1b0f9bd33487fb62bdce39aecc72c508452da9d

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


15fc1a17d5f773820821f22dbd7365ae0f32fdfe

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


87f9084e7ea0249c81b0e78db9564f7d978c9522

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

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

分享:
数据库
使用钉钉扫一扫加入圈子
+ 订阅

分享数据库前沿,解构实战干货,推动数据库技术变革

其他文章