《现代Java》一书。 Lambda表达式,流和函数式编程”

图片 嗨,habrozhiteli! 现代应用程序的优势在于高级解决方案,包括微服务,反应式体系结构和数据流。 Lambda表达式,数据流和期待已久的Java平台模块系统极大地简化了其实现。

该书将帮助您学习现代附加组件的新功能,例如Streams API和Java平台模块系统。 发现提高竞争力的新方法,并学习功能性概念如何改善代码工作。

本书中:•Java的新功能•流数据和反应式编程•Java平台模块系统。

摘录。 第11章可选类,它是null的最佳替代方法


如果您在Java开发人员的职业生涯中曾经收到过NullPointerException,请举手。 如果此异常是您遇到的最常见异常,请将其引发。 不幸的是,我们现在看不到您,但是很有可能举起了您的手。 我们还怀疑您可能在想类似的事情:“是的,我同意。 异常NullPointerException对于任何Java开发人员(无论是初学者还是专家)来说都是令人头疼的问题。 但是无论如何他们都无能为力,这就是我们为使用这种方便且可能不可避免的设计(如空链接)所付出的代价。” 这是(命令式)编程世界的普遍观点。 但是,也许这不是全部事实,而是根深蒂固的偏见。

英国计算机科学专家Tony Hoare于1965年创建了空链接,当时他开发了ALGOL W语言,这是最早的一种具有在堆上分配内存的记录的类型化编程语言之一,后来承认他这样做是“只是因为易于实施。” 尽管他想保证“对空链接使用例外是完全安全的,但他认为这是模拟缺少值的最便捷方法。 许多年后,他对这个决定感到遗憾,称其为“我的十亿美元错误”。 我们都看到了这项决定的结果。 例如,我们可以检查对象的字段以确定它是否代表两个可能的值之一,只是发现我们不是在检查对象,而是空指针,然后立即获取此烦人的NullPointerException。

实际上,Hoar可能低估了在过去50年中修复数百万开发人员因空链接而导致的错误的巨大成本。 的确,在过去的几十年中,包括Java在内的绝大多数编程语言1都是基于相同的设计决策,也许是出于与较旧语言兼容的原因,或者(如更可能是)Hoar所说,“仅仅是因为易于实现。 ”。 我们首先说明一个使用null时遇到的问题的简单示例。

11.1。 如何模拟缺乏价值


想象一下,对于购买汽车保险的汽车的所有者,您在清单11.1中具有嵌套的对象结构。

清单11.1 人/车/保险数据模型

public class Person { private Car car; public Car getCar() { return car; } } public class Car { private Insurance insurance; public Insurance getInsurance() { return insurance; } } public class Insurance { private String name; public String getName() { return name; } } 

您认为以下代码有什么问题?

 public String getCarInsuranceName(Person person) { return person.getCar().getInsurance().getName(); } 

这段代码看起来很合理,但是很多人没有汽车,那么在这种情况下调用getCar方法会有什么结果? 他们经常(而且徒劳地)返回一个空链接以指示不存在值(在这种情况下,指示不存在机器)。 结果,调用getInsurance方法将返回空白链接保险,这将导致在运行时引发NullPointerException并停止程序。 但这还不是全部。 但是,如果人员对象为空,该怎么办? 如果getInsurance也返回null怎么办?

11.1.1。 通过安全检查减少NullPointerException


如何避免意外的NullPointerException? 通常,您可以在任何需要的地方(有时甚至超出安全编程的要求,在不需要的地方)以及通常以不同的样式添加空检查。 清单11.2展示了我们首次尝试编写一种方法来防止NullPointerException生成的尝试。

图片

每次取消引用变量时,此方法都会检查是否为null,如果在此引用链中遇到的至少一个变量为空值,则返回字符串值“未知”。 该规则的唯一例外是我们不检查保险公司的名称是否为空,因为我们知道(像其他公司一样)保险公司必须有一个名称。 请注意,仅由于对主题领域的了解,我们设法避免了最后一次检查,但是这一事实并未反映在对我们的数据进行建模的Java类中。

我们将清单11.2中的方法描述为“深层怀疑”,因为其中有一个重复的模式:每次有疑问时,变量都不为null,则必须添加另一个嵌套的if块,从而提高了代码缩进的级别。 显然,这项技术无法很好地扩展并降低了可读性,因此最好尝试使用其他解决方案。 为了避免这个问题,让我们采取另一条路径,如清单11.3所示。

在第二次尝试中,我们尝试使用不同的策略来避免if块的深层嵌套:每当偶然发现null变量时,我们都会返回字符串值“ Unknown”。 但是这种解决方案也不是理想的。 现在,该方法具有四个不同的出口点,这大大增加了其维护工作。 此外,在null的情况下返回的默认值-字符串“ Unknown”-在三个地方重复,并且(我们希望)不包含拼写错误! 为避免这种情况,您当然可以将重复字符串移动到一个常量。

图片

此外,此过程容易出错。 如果您忘记检查属性之一是否为空怎么办? 在本章中,我们认为使用null表示缺乏价值是一种根本上错误的方法。 需要一种更好的建模价值缺失和存在的方法。

11.1.2。 null遇到的问题


综上所述,在Java中使用空链接会导致以下理论和实践问题。

  • 充当错误来源。 NullPointerException是Java中最常见的(很广泛)异常。
  • “夸大”代码。 由于需要使用通常深嵌套的空检查填充代码,因此可读性恶化。
  • 这没有任何意义。 它没有任何语义含义,特别是,这种使用静态类型化对语言中缺少意义进行建模的方法根本是错误的。
  • 违反Java语言的意识形态。 Java总是向开发人员隐藏指针,但有一个例外:空指针。
  • 在类型系统中产生间隙。 null不包含任何类型或其他信息,因此可以将其分配给任何类型的链接。 当将null传输到系统的另一部分时,如果没有有关该null最初应该是什么信息的信息,这可能会导致问题。

在下一节中,作为其他可行解决方案的基础,我们简要检查了其他编程语言提供的可能性。

11.1.3。 其他编程语言中null的替代方法


近年来,诸如Groovy之类的语言通过引入安全调用运算符(?。)设法解决了这个问题,该运算符旨在安全地处理潜在的空值。 要了解在实践中如何实现此过程,请考虑以下Groovy代码。 它检索指定人员为汽车提供保险的保险公司的名称:

def carInsuranceName = person?.car?.insurance?.name

您应该清楚此代码的作用。 一个人可能没有汽车,为了模拟,我们将空值分配给人对象的汽车链接。 同样,机器可能没有保险。 Groovy的安全调用运算符使您可以安全地处理可能为空的链接,而不会引发NullPointerException,将空链接传递到调用链中,如果链中的任何值为null,则返回null。

有人提议在Java 7中实现类似的功能,但后来决定不这样做。 但是,奇怪的是,Java确实不需要安全调用运算符。 任何Java开发人员在遇到NullPointerException时,首先想到的是通过在调用其方法之前添加if语句以检查null来快速解决问题。 以类似的方式解决问题,而无需考虑在这种特殊情况下对于您的算法或数据模型而言null是否可接受,这不会导致更正,而是会隐藏错误。 随后对于下一个开发人员(可能是一周或一个月的时间),将很难找到并修复此错误。 实际上,您只需要扫扫地毯下面的垃圾即可。 Groovy的null安全取消引用运算符只是一个更大,功能更强大的扫帚,可以执行此类愚蠢的操作而无需担心后果。

其他功能编程语言,例如Haskell和Scala,则以不同的方式看待这个问题。 Haskell具有Maybe类型,该类型本质上封装了一个可选值。 类型的对象可能包含指定类型的值或不包含任何值。 Haskell缺少空链接的概念。 在Scala中,为了封装是否存在类型T的值,提供了相似的逻辑结构Option [T],我们将在第20章中进行讨论。在这种情况下,必须使用Option类型的操作显式检查是否存在值,该操作提供“空检查”。 。 现在,不再可能“忘记检查null”,因为类型系统本身需要检查。

好的,我们的话题有点不对劲了,听起来似乎很抽象。 您可能想知道Java 8在这种意义上提供了什么,受可选值概念的启发,Java 8的创建者引入了新类java.util.Optional! 在本章中,我们将说明使用它的好处是对潜在的缺失值建模而不是为它们分配空引用。 我们还将解释为什么从null到Optional的这种转换需要程序员回顾在域模型中使用可选值的思想。 最后,我们将探讨这种新的Optional类的可能性,并提供一些有效使用它的实际例子。 结果,您将学习如何设计改进的API,其中用户已经从方法签名中了解了此处是否可以使用可选值。

11.2。 选修课介绍


在Java 8中,在Haskell和Scala语言的影响下,新的java.util.Optional类似乎封装了一个可选值。 例如,如果您知道某人可能拥有或不拥有汽车,则不应在Person类中使用Car类型声明变量car,并且如果该人没有汽车,则不要将其分配为空引用; 相反,它的类型应该是Optional,如图2所示。 11.1。

图片

给定一个值,Optional类充当它的适配器。 相反,使用Optional.empty方法返回的空optional建模缺少值。 此静态工厂方法返回Optional类的特殊单独实例。 您可能想知道空链接和Optional.empty()之间的区别是什么。 从语义上讲,它们可以被认为是相同的,但实际上它们之间存在巨大差异。 尝试取消引用null不可避免会导致NullPointerException,而Optional.empty()是Optional类型的有效且可行的对象,可以方便地对其进行访问。 很快,您将看到准确无误。

使用Optional对象而不是null时,在实践上存在重要的语义差异:声明Optional类型的变量明确表明,此时允许空值。 反之亦然,总是使用Car类型,有时可能会为该类型的变量分配空引用,这意味着您仅依靠领域知识来了解null是否属于给定变量的定义域。

考虑到这一点,您可以重做清单11.1中的原始模型。 我们使用Optional类,如代码清单11.4所示。

请注意,使用Optional类可以丰富模型的语义。 在“人”类中的“可选”类型字段和在“汽车”类中的“可选”类型字段反映了一个事实,即有人可能会或可能不会有汽车,就像机器可能会或可能不会被保险。

同时,将保险公司的名称声明为String而不是Optional,明确表明保险公司必须具有名称。 因此,您可以肯定地知道,取消引用保险公司的名称时将得到NullPointerException; 无需添加空检查,因为这只会隐藏问题。 保险公司应该有一个名字,所以如果您遇到一家没有名字的公司,则需要找出数据出了什么问题,而不必添加代码来隐藏这一事实。

图片

可选值的一致使用在可能缺少的值和由于算法错误或数据问题而丢失的值之间建立了清晰的区分。 重要的是要注意,Optional类并非旨在用单个链接替换所有空链接。 它的任务是帮助设计更易理解的API,以便通过方法的签名可以了解是否可以在其中找到可选值。 Java类型系统强制使用unpack选项来处理缺少值的情况。

关于作者


Raul-Gabriel Urma是Cambridge Spark(英国)的首席执行官兼联合创始人,Cambridge Spark是面向数据研究人员和开发人员的领先教育社区。 Raul在2017年当选为Java Champions计划的参与者之一。 他曾在Google,eBay,Oracle和Goldman Sachs工作。 他为剑桥大学的计算机工程专业辩护。 此外,他拥有伦敦帝国学院的工程硕士学位,以优异的成绩毕业,并因合理化建议获得了多个奖项。 劳尔在国际会议上发表了100多项技术报告。

Mario Fusco是Red Hat的一名高级软件工程师,参与了JBoss规则引擎Drools内核的开发。 他在Java开发方面拥有丰富的经验,曾参与(通常是领先的开发人员)许多行业的公司项目,从媒体到金融领域。 他的兴趣之一是功能编程和领域特定的语言。 基于这两个爱好,他创建了开源的lambdaj库,希望开发一个内部Java DSL来处理集合并使其能够使用Java中的某些功能性编程元素。

艾伦·迈克罗夫特Alan Mycroft)是剑桥大学计算机科学系的教授,自1984年以来一直在该校任教。 他还是鲁宾逊学院的雇员,鲁宾逊学院是欧洲编程语言和系统协会的创始人之一,也是Raspberry Pi基金会的创始人和受托人之一。 他拥有数学(剑桥)和计算机科学(爱丁堡)的学位。 艾伦(Alan)是100多篇科学文章的作者。 他是20多位博士生的导师。 他的研究主要涉及编程语言及其语义,优化和实现领域。 一段时间以来,他曾在AT&T实验室和英特尔研究部门工作,还与他人共同创立了Codemist Ltd.,后者发布了用于ARM体系结构的C语言编译器,称为Norcroft。

»这本书的更多信息可以在出版商的网站上找到
» 目录
» 摘录

小贩优惠券可享受25%的折扣-Java

支付纸质版本的书后,就会通过电子邮件发送电子书。

Source: https://habr.com/ru/post/zh-CN470598/


All Articles