Java 8函数式编程模式:不要使用空指针null

栏目: Java · 发布时间: 6年前

内容简介:空指针并不存在,是我们发明了它,我们现在就摆脱它,好吗?下面代码练习很简单:我们需要返回一个格式很好的行,根据他收集的点数为客户打印合适的折扣:让我们看看它返回,应该先是60,然后是10点:

空指针并不存在,是我们发明了它,我们现在就摆脱它,好吗?

下面代码练习很简单:我们需要返回一个格式很好的行,根据他收集的点数为客户打印合适的折扣:

<b>public</b> String getDiscountLine(Customer customer) {
        <b>return</b> <font>"Discount%: "</font><font> + getDiscountPercentage(customer.getMemberCard());
}
<b>private</b> Integer getDiscountPercentage(MemberCard card) {
        <b>if</b> (card.getFidelityPoints() >= 100) {
                <b>return</b> 5;
        }
        <b>if</b> (card.getFidelityPoints() >= 50) {
                <b>return</b> 3;
        }
        <b>return</b> <b>null</b>;
}
</font>

让我们看看它返回,应该先是60,然后是10点:

System.out.println(discountService.getDiscountLine(<b>new</b> Customer(<b>new</b> MemberCard(60))));
System.out.println(discountService.getDiscountLine(<b>new</b> Customer(<b>new</b> MemberCard(10))));

输出:

Discount%: 3

Discount%: null

我认为向用户显示“null”并不是您每天都想做的事情。显然,问题是我们连接了一个潜在的null 整数。修复它是很轻松的:

<b>public</b> String getDiscountLine(Customer customer) {
        Integer discount = getDiscountPercentage(customer.getMemberCard());
        <b>if</b> (discount != <b>null</b>) {
                <b>return</b> <font>"Discount%: "</font><font> + discount;
        } <b>else</b> {
                <b>return</b> </font><font>""</font><font>;
        }
}
</font>

如果没有折扣,我们返回一个空字符串。我们使用空检查污染了我们的代码。并且,更糟糕的是,我们必须通过窥视getDiscountPercentage()函数来找到我们遇到的问题,以确定它何时可以返回一个null,但是,这种技术不能在大型代码库中扩展。相反,您的API应该明确该函数可能不返回任何内容。

<b>import</b> java.util.Optional.*;
<b>public</b> String getDiscountLine(Customer customer) {
        Optional<Integer> discount = getDiscountPercentage(customer.getMemberCard());
        <b>if</b> (discount.isPresent()) {
                <b>return</b> <font>"Discount%: "</font><font> + discount.get();
        } <b>else</b> {
                <b>return</b> </font><font>""</font><font>;
        }
}
<b>private</b> Optional<Integer> getDiscountPercentage(MemberCard card) {
        <b>if</b> (card.getFidelityPoints() >= 100) {
                <b>return</b> of(5);
        }
        <b>if</b> (card.getFidelityPoints() >= 50) {
                <b>return</b> of(3);
        }
        <b>return</b> empty();
}
</font>

这里使用Optional.isPresent() 替代空检查。

但是,有时候,从我们头脑中弹出第一个想法并不总是最好的。当你玩的时候Optional,你需要反过来考虑如何使用它。每当你试图改变魔术盒中的内容时,使用.map(),应用于该魔术盒中,这样只有存在这个盒里面的东西才会转换。

<b>public</b> String getDiscountLine(Customer customer) {
<b>return</b> getDiscountPercentage(customer.getMemberCard())
      .map(d -> <font>"Discount%: "</font><font> + d).orElse(</font><font>""</font><font>);
}
</font>

代码不仅更简洁,而且一旦习惯了这种风格,它也更容易阅读。

下面测试一下没有MemberCard的Customer :

System.out.println(discountService.getDiscountLine(new Customer()));

输出:

Exception in thread "main" java.lang.NullPointerException...

我了去!我们经常匆匆忙忙地看看异常突然出现在哪里。在这里,它位于getDiscountPercentage()函数的第一行。这是由于我们从未处理过MemberCard参数为null的某些边界值,我们马上解决这个问题 - 尽快隐藏那个bug并假装我们从未见过它:

<b>private</b> Optional<Integer> getDiscountPercentage(MemberCard card) {
        <b>if</b> (card == <b>null</b>) {
                <b>return</b> empty();
        }
        <b>if</b> (card.getFidelityPoints() >= 100) {
                <b>return</b> of(5);
        }
        <b>if</b> (card.getFidelityPoints() >= 50) {
                <b>return</b> of(3);
        }
        <b>return</b> empty();
}

,我们再次错过了一个设计洞察(你看到一个模式吗?)。我们在这里快速应用了防御性编程,并针对所有其他无效数据实现保护我们的代码。但是,据说最好的防守就是进攻。如果不是在恐惧中守护我们的代码,我们会说:“等一下。因此,客户是否可以没有会员卡?如果可能存在,Customer.getMemberCard()应该返回一个Optional<MemberCard>

<b>public</b> <b>class</b> Customer {
          ...
          <b>public</b> Optional<MemberCard> getMemberCard() {
                   returnofNullable(memberCard);
          }
}

是的,我在这里触及神圣的事物。我敢改变领域实体!但是,我相信我更具表现力,因为客户和会员卡之间的关联实际上是可选的(1:0..N)

调用这段代码时我们使用.isPresent()?

<b>private</b> Optional<Integer> getDiscountPercentage(Optional<MemberCard> cardOpt) {
        <b>if</b> (!cardOpt.isPresent()) {
                returnempty();
        }
...<font><i>// Wait a bit!</i></font><font>
</font>

停!

我们一无所获!我会说这段代码变得更加丑陋!但是,有一个干净的代码规则:不要采用可以为空的参数,因为你需要做的第一件事就是检查null。在 Java 8中,不采用Optional参数作为入参。

我们看到如果有会员卡,会应用getDiscountPercentage()到MemberCard,那么我们放弃更改getDiscountPercentage(),而是改变getDiscountLine():

<b>public</b> String getDiscountLine(Customer customer) {
          <b>return</b> customer.getMemberCard()
                             .map(card -> getDiscountPercentage(card))
                             .map(d -> <font>"Discount%: "</font><font> + d)
                             .orElse(</font><font>""</font><font>);
}
</font>

输出可能让我们感到惊讶:

Discount%: Optional[3]

Discount%: Optional.empty

这是因为第4行的d不再是一个Integer类型,而是一个Optional<Integer>,如果你将鼠标悬停在第一个上.map()并查看返回类型,你会自己看到它:Optional<Optional<Integer>>。

在我们的例子中,我们使用.flatMap()而不是.map()去除额外的包装。

<b>public</b> String getDiscountLine(Customer customer) {
            <b>return</b> customer.getMemberCard()
                                    .flatMap(<b>this</b>::getDiscountPercentage)
                                    .map(d -> <font>"Discount%: "</font><font> + d)
                                    .orElse(</font><font>""</font><font>);
}
</font>

因此,只要null存在Java 8中就会给您带来问题,请不要犹豫,使用Optional 并在可能为空的魔术盒上应用转换函数。干净的代码规则变为:不要选择Optional 参数; 相反,只要你的函数想要向你的调用者发出信号,在某些情况下可能没有返回值,就返回一个Optional。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Introduction to Linear Optimization

Introduction to Linear Optimization

Dimitris Bertsimas、John N. Tsitsiklis / Athena Scientific / 1997-02-01 / USD 89.00

"The true merit of this book, however, lies in its pedagogical qualities which are so impressive..." "Throughout the book, the authors make serious efforts to give geometric and intuitive explanations......一起来看看 《Introduction to Linear Optimization》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具