optional类,便利函数,创建Optional,Optional对象操作以及Optional流

前言

我们必须考虑在一个空流中获取元素会发生什么,我们总是习惯流是不会中断的。然而,在流中放置null却会轻易令其中断,那么是否可以有一种对象,可以使我们即在持有流元素的同时,也可以在元素不存在时也向我们进行友好提示呢(不会产生异常)

一、 Optional 类

Optional 可以实现这样的功能。标准流操作中的一些可以返回 Optional 对象,因为它们并不能保证预期结果一定存在。

  • findFirst():返回包含第一个元素的 Optional 对象,如果流为空则返回 Optional.empty
  • findAny():返回包含任意元素的 Optional 对象,如果流为空则返回Optional.empty
  • max()min():返回一个包含最大值或是最小值的 Optional 对象,如果流为空则返回 Optional.empty

reduce()不再以identity形式开头,而是将其返回值包含在 Optional 中。(identity 对象成为其他形式的reduce()的默认结果,因此不存在空结果的风险)对于数字流 IntStream 等,average()会将结果包装在 Optional 以防止流为空。

public class OptionalsFromEmptyStreams {
    public static void main(String[] args) {
        System.out.println(Stream.<String>empty()
                .findFirst());
        System.out.println(Stream.<String>empty()
                .findAny());
        System.out.println(Stream.<String>empty()
                .max(String.CASE_INSENSITIVE_ORDER));
        System.out.println(Stream.<String>empty()
                .min(String.CASE_INSENSITIVE_ORDER));
        System.out.println(Stream.<String>empty()
                .reduce((s1, s2) -> s1 + s2));
        System.out.println(IntStream.empty()
                .average());
    }
}
/*
output:
Optional.empty
Optional.empty
Optional.empty
Optional.empty
Optional.empty
OptionalDouble.empty
 */

当流为空的时候你会获得一个Optional.empty 对象,而不是抛出异常。Optional 拥有toString()方法可以用于展示有用信息。
注意:空流是通过Stream.<String>empty()创建的。如果在没有任何上下文环境的情况下调用Stream.empty(),Java 并不知道它的数据类型。

下面是Optional 的基本用法:

public class OptionalBasics {
    static void test(Optional<String> optString) {
        if (optString.isPresent())
            System.out.println(optString.get());
        else
            System.out.println("Nothing inside!");
    }

    public static void main(String[] args) {
        test(Stream.of("Epithets").findFirst());
        test(Stream.<String>empty().findFirst());
    }
}
/*
output:
Epithets
Nothing inside!
 */

当你接收到Optional 对象时,应首先调用isPresent()检查其中是否包含元素,如果存在,可使用get()获取。

二、 便利函数

有许多便利函数可以解包 Optional ,

  • isPresent(Consumer):当值存在时调用 Consumer,否则什么也不做。
  • orElse(otherObject):如果值存在则直接返回,否则生成 otherObject。
  • orElseGet(Supplier):如果只存在则直接返回,否则使用Supplier 函数生成一个可替代对象。
  • orElseThrow(Supplier):如果值存在直接返回,否则使用Supplier 函数生成一个异常。
public class Optionals {
    static void basics(Optional<String> optString) {
        if (optString.isPresent()) {
            System.out.println(optString.get());
        } else System.out.println("Nothing Inside!");
    }

    static void ifPresent(Optional<String> optString) {
        optString.ifPresent(System.out::println);
    }

    static void orElse(Optional<String> optString) {
        System.out.println(optString.orElse("Nada"));
    }

    static void orElseGet(Optional<String> optString) {
        System.out.println(optString.orElseGet(() -> "Generated"));
    }

    static void orElseThrow(Optional<String> optString) {
        try {
            System.out.println(optString.orElseThrow(() -> new Exception("Supplied")));
        } catch (Exception e) {
            System.out.println("Caught " + e);
        }
    }

    static void test(String testName, Consumer<Optional<String>> cos) {
        System.out.println("===" + testName + "=====");
        cos.accept(Stream.of("Epithets").findFirst());
        cos.accept(Stream.<String>empty().findFirst());
    }

    public static void main(String[] args) {
        test("basics", Optionals::basics);
        test("ifPresent", Optionals::ifPresent);
        test("orElse", Optionals::orElse);
        test("orElseGet", Optionals::orElseGet);
        test("orElseThrow", Optionals::orElseThrow);
    }
}
/*
output:
===basics=====
Epithets
Nothing Inside!
===ifPresent=====
Epithets
===orElse=====
Epithets
Nada
===orElseGet=====
Epithets
Generated
===orElseThrow=====
Epithets
Caught java.lang.Exception: Supplied
 */

三、 创建 Optional

当我们在自己的代码块中加入 Optional 时,可以使用以下三个静态方法:

  • empty():生成一个空 Optional。
  • of(value):将一个非空值包装到 Optional 中。
  • ofNullable():针对一个可能为空的值,为空时自动生成 Optional.empty,否则将值包装在 Optional 中。

下面是示例:

public class CreatingOptionals {
    static void test(String testName, Optional<String> optional) {
        System.out.println("====" + testName + "====");
        System.out.println(optional.orElse("NULL"));
    }

    public static void main(String[] args) {
        test("empty", Optional.empty());
        test("of", Optional.of("Howdy()"));
        try {
            test("of", Optional.of(null));
        } catch (Exception e) {
            System.out.println(e);
        }

        test("ofNullable", Optional.ofNullable("Hi"));
        test("ofNullable", Optional.ofNullable(null));
    }
}
/*
output:
====empty====
NULL
====of====
Howdy()
java.lang.NullPointerException
====ofNullable====
Hi
====ofNullable====
NULL
 */

我们不能通过传递 null 到 of() 来创建 Optional 对象,最安全的方法是:使用 ofNullable() 来优雅的处理 null。

四、 Optional 对象操作:filter、map、flatMap

当我们的流管道生产了 Optional 对象,下面三个方法可使得 Optional 的后续能做更多的操作:

  • filter(Predicate):对 Optional 中的内容应用 Predicate 并将结果返回。如果 Optional 不满足 Predicate ,将 Optional 转化为空 Optional。如果 Optional 为空,则直接返回空 Optional。
  • map(Function):如果 Optional 不为空,应用 Function 于 Optional 中的内容,并返回结果。否则直接返回 Optional.empty。
  • flatMap(Function):同map(),但是提供的映射函数(就是Function)将结果包装在 Optional 对象中,因此 flatMap() 不会在最后进行任何包装。
public class OptionalFilter {
    static String[] elements = {"Foo", "Bar", "Baz", "Bingo"};
    static Stream<String> testStream() {
        return Arrays.stream(elements);
    }

    static void test(String descr, Predicate<String> predicate) {
        System.out.println("---(" + descr + ")----");
        for (int i = 0; i <= elements.length; i++) {
            System.out.println(testStream()
                    .skip(i)
                    .findFirst()
                    .filter(predicate));
        }
    }

    public static void main(String[] args) {
        test("true", s -> true);
        test("false", s -> false);
        test("s != \"\"", s -> s != "");
        //此处的s.length() 是传入的单个流元素
        test("s.length() == 3", s -> s.length() == 3);
        test("startsWith(\"B\")", s -> s.startsWith("B"));
    }
}
/*
output:
---(true)----
Optional[Foo]
Optional[Bar]
Optional[Baz]
Optional[Bingo]
Optional.empty
---(false)----
Optional.empty
Optional.empty
Optional.empty
Optional.empty
Optional.empty
---(s != "")----
Optional[Foo]
Optional[Bar]
Optional[Baz]
Optional[Bingo]
Optional.empty
---(s.length() == 3)----
Optional[Foo]
Optional[Bar]
Optional[Baz]
Optional.empty
Optional.empty
---(startsWith("B"))----
Optional.empty
Optional[Bar]
Optional[Baz]
Optional[Bingo]
Optional.empty
 */

即使输出看起来像流,要特别注意 test() 中的for循环:1.每次for循环都操作的是新生成的流,2.跳过指定数量的元素,3.通过调用findFirst()获取剩余元素中的第一个元素,并将其包装在 Optional 对象中。
注意:这里不同于普通for 循环,这里的 等于 号实际上使得最后一位超出了数组转化为的流的范围,但是并不会报错

map()一样,Optional.map()仅仅在 Optional 不为空时才执行这个映射函数,并将 Optional 的内容提取出来,传递给映射函数

public class OptionalMap {
    static String[] elements = {"12", "", "23", "45"};
    static Stream<String> stream() {
        return Arrays.stream(elements);
    }

    static void test(String descr, Function<String,String> func) {
        System.out.println("---(" + descr + ")----");
        for (int i = 0; i <= elements.length; i++) {
            System.out.println(stream()
                    .skip(i)
                    .findFirst()//此处产生一个optional
                    .map(func));
        }
    }

    public static void main(String[] args) {
        test("Add brackets", s -> "[" + s + "]");
        test("Increment", s -> {
            try {
                return Integer.parseInt(s) + 1 + "";
            } catch (Exception e) {
                return s;
            }
        });
        test("Replace", s -> s.replace("2", "9"));
        //记得每个s是单个流元素
        test("Take last digit", s -> s.length() > 0 ? s.charAt(s.length() - 1) + "" : s);
    }
}
/*
output:
---(Add brackets)----
Optional[[12]]
Optional[[]]
Optional[[23]]
Optional[[45]]
Optional.empty
---(Increment)----
Optional[13]
Optional[]
Optional[24]
Optional[46]
Optional.empty
---(Replace)----
Optional[19]
Optional[]
Optional[93]
Optional[45]
Optional.empty
---(Take last digit)----
Optional[2]
Optional[]
Optional[3]
Optional[5]
Optional.empty
 */

映射函数的返回结果会自动包装成为 Optional。Optional.empty() (空)会被直接跳过。

public class OptionalFlatMap {
    static String[] elements = {"12","", "23", "45"};

    static Stream<String> stream() {
        return Arrays.stream(elements);
    }

    static void test(String descr, Function<String, Optional<String>> func) {
        System.out.println("---(" + descr + ")----");
        for (int i = 0; i <= elements.length; i++) {
            System.out.println(stream()
                    .skip(i)
                    .findFirst()
                    .flatMap(func));//这里的参数变成了Optional类型
        }
    }

    public static void main(String[] args) {
        test("Add brackets", new Function<String, Optional<String>>() {
            @Override
            public Optional<String> apply(String s) {
                return Optional.of("[" + s + "]");
            }
        });
        test("Increment",s -> {
            try {
                return Optional.of(Integer.parseInt(s) + 1 + "");
            } catch (Exception e) {
                return Optional.of(s);
            }
        });
        test("Replace", s -> Optional.ofNullable(s.replace("2", "9")));
        test("Take last digit",s->Optional.of(s.length()>0?s.charAt(s.length()-1)+"":s));
    }
}
/*
output:
---(Add brackets)----
Optional[[12]]
Optional[[]]
Optional[[23]]
Optional[[45]]
Optional.empty
---(Increment)----
Optional[13]
Optional[]
Optional[24]
Optional[46]
Optional.empty
---(Replace)----
Optional[19]
Optional[]
Optional[93]
Optional[45]
Optional.empty
---(Take last digit)----
Optional[2]
Optional[]
Optional[3]
Optional[5]
Optional.empty
 */

Optional 的 flatMap()应用于已生成 Optional 的映射函数,所以它不会像 map()那样将结果封装在 Optional 里。
map()flatMap()将提取非空的Optional 内容将其应用在映射函数。唯一的区别就是它不再将结果包装在 Optional 中,因为已经包装过了。

五、 Optional 流

假设你的生成器可能产生 null 值。那么当用它来创建流时,用 Optional 来包装元素更好。

public class Signal {
    private final String msg;

    public Signal(String msg) {
        this.msg = msg;
    }

    public String getMsg() {
        return msg;
    }

    @Override
    public String toString() {
        return "Signal{" +
                "msg='" + msg + '\'' +
                '}';
    }

    static Random rand = new Random(47);
    public static Signal morse() {
        switch (rand.nextInt(4)) {
            case 1:
                return new Signal("dot");
            case 2:
                return new Signal("dash");
            default:
                return null;
        }
    }

    public static Stream<Optional<Signal>> stream() {
        return Stream.generate(Signal::morse)
                .map(signal -> Optional.ofNullable(signal));
    }
}

当我们使用这个流的时候,必须要弄清楚如何解包 Optional 。代码如下:

public class StreamOfOptionals {
    public static void main(String[] args) {
        Signal.stream()
                .limit(10)
                .forEach(System.out::println);
        System.out.println("-----------");
        Signal.stream()
                .limit(10)
                .filter(Optional::isPresent)
                .map(Optional::get)
                .forEach(System.out::println);
    }
}
/*
output:
Optional[Signal{msg='dash'}]
Optional[Signal{msg='dot'}]
Optional[Signal{msg='dash'}]
Optional.empty
Optional.empty
Optional[Signal{msg='dash'}]
Optional.empty
Optional[Signal{msg='dot'}]
Optional[Signal{msg='dash'}]
Optional[Signal{msg='dash'}]
-----------
Signal{msg='dot'}
Signal{msg='dot'}
Signal{msg='dash'}
Signal{msg='dash'}
 */

在这里我们使用filter()来保留那些非空 Optional, 然后在 map() 中使用get()获取元素。由于每种情况都需要定义“空值”的含义,所以通常我们要为每个应用程序采用不同的方法。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值