前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >全面探索Optional类型

全面探索Optional类型

作者头像
张逸
发布于 2023-03-23 09:19:09
发布于 2023-03-23 09:19:09
39900
代码可运行
举报
文章被收录于专栏:斑斓斑斓
运行总次数:0
代码可运行

最近,出版社邀请我作为分享导师为Bruce Eckel的大作OnJava录制部分视频,视频内容主要面向Java初学者。录制到14.4章节,书中详细介绍了Java 8引入的Optional类型,我忽然想起很早以前写的一篇文章《并非Null Object这么简单》。

或许是《OnJava》更倾向于对Java语法的介绍,又或者对于这样一部鸿篇巨制,如果对Java的每一个语法都要深入探讨,可能篇幅就实在太长了,因此该书并未详细介绍Optional的“前世今生”,自然也不会去对比其他语言的类似语法。想到这一点,就觉得我写的这篇文章对于广大读者而言,应该有一定参考价值,那就不如将它分享给大家。

1

在大多数程序语言中,我们都需要与Null打交道,并且纠缠于对它的检查中。一不小心让它给溜出来,就可能像打开潘多拉的盒子一般,给程序世界带来灾难。

说起来,在我们人类世界中,Null到底算什么“东西”呢?语义上讲,它就是一场空,即所谓“虚无”。然而,这个世界并没有任何物质可以代表“虚无”,因而它仅存于我们的精神层面。说虚无存在其实是一种悖论,因为存在其实是虚无的反面。若从程序本质上讲,Null代表一种状态,指一个对象(或变量),虽获声明却未真正诞生,甚至可能永远不会诞生。而一旦诞生,Null就被抹去了,回归了正确的状态。

站在OO的角度来讲,既然Everything is object,自然可以将Null同样视为Object——这近似于前面提到的悖论,既然是Null,为何又是Object呢?换言之,在对象世界里,其实没有什么不存在,所谓“不存在”仍然是一种“存在”。这么说容易让人变糊涂,就好像我们搞不清楚“我是谁”。所以,我宁肯采用Martin Fowler的说法,将Null Object视为一种Special Case,即Null其实是一种特例。

视Null为一种特例,即可用OO的特化来表达。当某个对象可能存在Null这种状态时,都可以将这种状态表示为一种特化的类,它不再代表Null,而是代表“什么都不做”。凡是返回Null的地方,都替换为这个Null Object,用以表达这种Null其实仅仅是一种特列。于是乎,我们像抹杀异教徒一般抹去了“虚无”的存在。(当虚无被抹去,是什么样的存在?)

然而,若在Java程序语言中实现自己的Null Object,固然可以在一定程度上消除对Null的检查,却存在一些约束:

  • 对于String之类的类型,无法定义NullString子类;
  • 每次都需要自己去定义子类来表示Null;
  • 必须约束团队不能返回Null。

这些约束未免给我们使用该模式带了诸多不便。

2

Google的Guava框架为了解决这一问题,引入了Optional(那是在还未诞生Java 8的时代):

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public abstract class Optional<T> implements Serializable {
  public static <T> Optional<T> absent() {
    return (Optional<T>) Absent.INSTANCE;
  }
  public static <T> Optional<T> of(T reference) {
    return new Present<T>(checkNotNull(reference));
  }
  public static <T> Optional<T> fromNullable(@Nullable T nullableReference) {
    return (nullableReference == null)
        ? Optional.<T>absent()
        : new Present<T>(nullableReference);
  }
  public abstract boolean isPresent();
  public abstract T get();
  public abstract T or(T defaultValue);
  public abstract <V> Optional<V> transform(Function<? super T, V> function);
}

我们可以这样来使用Optional:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  public final Optional<E> first() {
    Iterator<E> iterator = iterable.iterator();
    return iterator.hasNext()
        ? Optional.of(iterator.next())
        : Optional.<E>absent();
  }

first()方法返回的是一个Optional类型。这是Guava中操作集合的一个方法。当我们要获得第一个元素时,可以调用该方法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
List<Person> persons = newArrayList();
String name = from(persons).first().transform(new Function<Person, String>() {
        @Override
        public String apply(Person input) {
            return input.getName();
        }
    }).or("not found");
assertThat(name, is("not found"));

不知是巧合,还是一种借鉴,Java 8同样定义了Optional用以处理这种情况。前面的代码在Java 8下可以改写为:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
List<Person> persons = newArrayList();
String name = persons.stream()
                     .findFirst()
                     .map(p -> p.getName())
                     .orElse("not found");
assertThat(name, is("not found"));

其实Scala在很早以前就提供了Option[T]类型。前面的代码若用scala编写,就变成:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
case class Person(name: String, age: Int)
val persons = List[Person]()
persons.headOption.map(p => p.name).getOrElse("not found")

3

这样的设计方式,还是Null Object模式吗?

让我们回到Null的本原状态,思考为什么会产生Null?首先,Null代表一种异常状态,即在某种未可知的情形下,可能返回Null;正常情况下,返回的则是非Null的对象。Null与非Null,代表一种未知与不确定性,在量子力学中,就是薛定谔的猫。

在程序世界里,我们似乎可以将其抽象为一个集合来表达这种非此即彼的状况,但从函数式编程的角度来讲,应将其设计为一个Monad。根据DSL in Action一书对Monad的介绍,一个Monad由以下三部分定义:

  • 一个抽象M[A],其中M是类型构造函数。在Scala语言中可以写成class M[A],或者case class M[A],有或者trait M[A]
  • 一个unit方法(unit v)。对应Scala中的函数new M(v)或者M(v)的调用。
  • 一个bind方法,起到将运算排成序列的作用。在Scala中通过flatMap组合子来实现。bind f m对应的Scala语句是m flatMap f。

同时,Monad还必须满足以下三条规则。

1. 右单位元(identity)。即对于任意Monad m,有m flatMap unit => m。对于Option,unit就是Option伴生对象定义的apply()方法。若m为Some("Scala"),则m flatMap {x => Option(x)},其结果还是m。

2. 左单位元(unit)。即对于任意Monad m,有unit(v) flatMap f => f(v)。假设我们定义一个函数f:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
def f(v: String) = Option(v)

则Option("Scala") flatMap {x => f(x)}的结果就等于f("scala")。

3. 结合律。即对于任意Monad m,有m flatMap g flatMap h => m flatMap {x => g(x) flatMap h}。

4

无论是Scala中的Option[A],还是Java 8中的Optional[T],都是一个Monad。此时的Null不再是特例,而是抽象Option[A]对称的两个元素中的其中一个,在Scala中,即Option[T]中的Some[T]或None。它们俩面貌相同,却是一对性格迥异的双生子。

在设计为Monad后,就可以利用Monad提供的bind功能,完成多个函数的组合。组合时,并不需要考虑返回为None的情况。Monad能保证在前一个函数返回空值时,后续函数不会被调用。

让我们来看一个案例。例如,我们需要根据某个key从会话中获得对应的值,然后再将该值作为参数去查询符合条件的特定Customer。在Scala中,可以将这两个步骤定义为函数,返回结果分别为Option[String]与Option[Customer]:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
def params(key: String): Option[String]
def queryCustomer(refId: String): Option[Customer]
val customer = 
    (
        for {
            r <- params("customerId")
            c <- queryCustomer(r)
        } yield c
    ) getOrElse error("Not Found")

这段代码用到了Scala的for comprehension,它实则是对flatMap的一种包装。尤其当嵌套多个flatMap时,使用for comprehension会更加直观可读。翻译为flatMap,则为:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
params("customerId").flatMap{
    r => queryCustomer(r).map {
        c => c
    }
} getOrElse error("Not Found")

当我最初看到Guava设计的Optional[T]时,我以为是Null Object模式的体现。事实上,它的功能要超出Null Object的范畴。但它也并非Monad,在前面给出的定义中,我们可以看到Guava的Optional[T]仅提供了map(即定义中的transform)功能,而没有提供更基本的flatMap操作。

具有函数式编程功能的Scala与Java 8加强了这一功能,利用Monad强化了程序对Null的处理。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-02-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 逸言 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
深圳scala-meetup-20180902(1)- Monadic 编程风格
刚完成了9月份深圳scala-meetup,趁刮台风有空,把我在meetup里的分享在这里发表一下。我这次的分享主要分三个主题:“Monadic编程风格“、”Future vs Task and ReaderMonad应用方法“及”using heterogeneous monads in for-comprehension with MonadTransformer“。这篇想先介绍一下Monadic编程风格。
用户1150956
2018/09/28
5450
Cats(1)- 从Free开始,Free cats
用户1150956
2018/01/05
3.6K0
Scalaz(42)- Free :FreeAp-Applicative Style Programming Language
  我们在前面花了几期时间讨论Free Monad,那是因为FP既是Monadic programming,Free Monad是FP模式编程的主要方式。对我们来说,Free Monad代表着fp从学
用户1150956
2018/01/05
6830
Scalaz(19)- Monad: \/ - Monad 版本的 Either
  scala标准库提供了一个Either类型,它可以说是Option的升级版。与Option相同,Either也有两种状态:Left和Right,分别对应Option的None和Some,不同的是L
用户1150956
2018/01/05
5930
Scalaz(12)- Monad:再述述flatMap,顺便了解MonadPlus
  在前面的几篇讨论里我们初步对FP有了些少了解:FP嘛,不就是F[A]吗?也是,FP就是在F[]壳子(context)内对程序的状态进行更改,也就是在F壳子(context)内施用一些函数。再直白一
用户1150956
2018/01/05
9420
Java函数式编程之Optional
java.util.Optional是JDK8中引入的类,它是JDK从著名的Java工具包Guava中移植过来。本文编写的时候使用的是JDK11。Optional是一个包含了NULL值或者非NULL值的对象容器,它常用作明确表明没有结果(其实明确表明存在结果也可以用Optional表示)的方法返回类型,这样可以避免NULL值带来的可能的异常(一般是NullPointerException)。也就是说,一个方法的返回值类型是Optional,则应该避免返回NULL,而应该让返回值指向一个包含NULL对象的Optional实例。Optional的出现为NULL判断、过滤操作、映射操作等提供了函数式适配入口,它算是Java引入函数式编程的一个重要的里程碑。
Throwable
2020/06/23
1.6K0
Scala入门篇 顶
scala> val a = println("ddd") ddd a: Unit = ()
算法之名
2019/08/21
4750
Scala入门篇
                                                                            顶
Scala写Spark笔记
CBeann
2023/12/25
1720
Java 8 Optional类深度解析
身为一名Java程序员,大家可能都有这样的经历:调用一个方法得到了返回值却不能直接将返回值作为参数去调用别的方法。我们首先要判断这个返回值是否为null,只有在非空的前提下才能将其作为其他方法的参数。这正是一些类似Guava的外部API试图解决的问题。一些JVM编程语言比如Scala、Ceylon等已经将对在核心API中解决了这个问题。在我的前一篇文章中,介绍了Scala是如何解决了这个问题。
allsmallpig
2021/02/12
5280
Null 值及其处理方式
Null 值由来已久,它最早是由 Tony Hoare 图方便而创造的,后来被证明这是个错误,而他本人也对此进行了道歉,并称之为「十亿美金错误」1。
zhiruili
2021/08/10
1.2K0
用过Optional;那用过Try么?
Java的Optional非常好用。我们一般使用Optional做非空处理,省去if的处理。主要的目的,就是为了解决Java中臭名昭著的空指针异常。
xjjdog
2021/01/11
7830
用过Optional;那用过Try么?
[译]厌倦了NullPointException?Optional拯救你!
有人说,当你处理过了空指针异常才真正成为一个Java开发者。抛开玩笑话不谈,空指针确实是很多bug的根源。Java SE 8引入了一个新的叫做java.util.Optional 的类来缓解这个问题。
weishu
2018/09/05
1K0
[译]厌倦了NullPointException?Optional拯救你!
Scalaz(40)- Free :versioned up,再回顾
   在上一篇讨论里我在设计示范例子时遇到了一些麻烦。由于Free Monad可能是一种主流的FP编程规范,所以在进入实质编程之前必须把所有东西都搞清楚。前面遇到的问题主要与scalaz Free的F
用户1150956
2018/01/05
1.3K0
Java函数式开发——优雅的Optional空指针处理
    在Java江湖流传着这样一个传说:直到真正了解了空指针异常,才能算一名合格的Java开发人员。在我们逼格闪闪的java码字符生涯中,每天都会遇到各种null的处理,像下面这样的代码可能我们每天都在反复编写:
随风溜达的向日葵
2018/08/15
7380
Scalaz(13)- Monad:Writer - some kind of logger
  通过前面的几篇讨论我们了解到F[T]就是FP中运算的表达形式(representation of computation)。在这里F[]不仅仅是一种高阶类型,它还代表了一种运算协议(computa
用户1150956
2018/01/05
9160
Scalaz(17)- Monad:泛函状态类型-State Monad
  我们经常提到函数式编程就是F[T]。这个F可以被视为一种运算模式。我们是在F运算模式的壳子内对T进行计算。理论上来讲,函数式程序的运行状态也应该是在这个运算模式壳子内的,也是在F[]内更新的。那么
用户1150956
2018/01/05
1.8K0
Java8 中的 Optional 类的解析
身为一名 Java 程序员,大家可能都有这样的经历:调用一个方法得到了返回值却不能直接将返回值作为参数去调
用户1289394
2021/01/06
6150
Scalaz(41)- Free :IO Monad-Free特定版本的FP语法
本文介绍了如何使用函数式编程的方式处理IO,并使用Scalaz的IO Monad实现了一个简单的程序。通过这个例子,展示了函数式编程在处理异步逻辑中的优势。
用户1150956
2018/01/05
1.6K0
Java示例演示Functor 和monad
This article was initially an appendix in our Reactive Programming with RxJavabook. However introduction to monads, albeit very much related to reactive programming, didn't suit very well. So I decided to take it out and publish separately as a blog post.
Dylan Liu
2019/07/01
6230
泛函编程(25)-泛函数据类型-Monad-Applicative
    上两期我们讨论了Monad。我们说Monad是个最有概括性(抽象性)的泛函数据类型,它可以覆盖绝大多数数据类型。任何数据类型只要能实现flatMap+unit这组Monad最基本组件函数就可以
用户1150956
2018/01/05
1.4K0
相关推荐
深圳scala-meetup-20180902(1)- Monadic 编程风格
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文