上一篇介绍了lambda和函数接口,这一篇主要讲解Java8 新增的Optional和Stream的使用。
讲解Optional前,我们先看看Java中的空指针异常。
Java世界两大异常 OOM和NEP。
OOM即OutOfMemoryError,一旦出现OOM,就意味着非常严重的问题,虚拟机直接down掉。JMM 5大区域:程序计数器、Java 栈、方法区、堆、本地方法栈,规定只有程序计数器不会出现OOM。现在随着JVM和硬件越来越强,OOM极其少见,本篇暂不展开。
NEP即NullPointerException,空指针异常是Java世界最臭名昭著的异常,至少60%的错误都是NEP问题造成的,NEP很复杂吗?恰恰相反,但是如果不能深入理解,NEP可能跟随程序员的一生,即使是老手,也常常出错。Java祖师爷高斯林也承认这是一项失误。
1 2 3 4 5 6
| Object obj = null; Runnable runnable = null; String str = null; Integer integer = null; int num = null;
|
null 是个引用类型的特殊值,可以赋值给任何引用类型,不能赋值给基本类型。
null是个右值,只能出现在赋值表达式右边。
C语言的Null ((void*)0),代表0x0000逻辑地址[不存在实际物理地址],Java 也是类似。引用类型就相当于一个弱化的指针,无法对其进行指针运算,只能对其指向的内容操作。
Object类型的引用 obj可以指向任何对象,null可以赋值个任意类型的引用。
造成NEP的最根本原因是一个引用类型的指向null,却调用了其实例方法[属性]。
1 2 3 4 5 6 7
| obj.toString(); runnable.run(); str.equals(str1); integer.intValue();
integer.max(10,20);
|
NEP 有很大一部分是自动装箱与拆箱(Autoboxing and unboxing)造成的,自动装箱与拆箱是Java5 引入的语法糖,但是却让NEP更加难以察觉.
1 2 3 4 5 6
| int a = 10; Integer integer = a; Object obj = 10; int b = integer;
|
装箱调用的是静态方法,所以不会有NEP问题。恶心的是拆箱,一旦出现包装类对象为null,就会出现NEP.
Java中并没有提供跟null相关的操作符,所以代码随处可见 if 判断,Java8 吸收了Google Guava框架Optional设计,让null处理更加优雅,更加可读。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
int? a = null; int b = 2 * a ?? 0;
if (obj == null) { }
if (integer instanceof Integer) { }
|
以上两种办法其实简化粗鲁,我们看看 Optional<T>,Optional 是 final类,且构造方法私有。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| 1.Optional.empty(); 2.Optional.of(T value); 3.Optional.ofNullable(T value);
4.optional.isPresent(); 5.optional.ifPresent(Consumer<? super T> consumer);
6.optional.filter(Predicate<? super T> predicate); 7.optional.map(Function<? super T, ? extends U> mapper); 8.optional.flatMap(Function<? super T, Optional<U>> mapper);
9.optional.get(); 10.optional.orElse(T other); 11.optional.orElseGet(Supplier<? extends T> other); 12.optional.orElseThrow(Supplier<? extends X> exceptionSupplier);
isEmpty(); ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction); stream();
|
我们看一个很重要的概念:map/reduce。本身内涵丰富,Hadoop设计思想之一。在其他语言就是一个函数,看一个例子。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
function pow(x) { return x * x; } var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]; arr.map(pow);
function add(x, y) { return x + y; } var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]; arr.reduce(add);
|
可以发现,map函数特点,一个参数一个返回值.reduce 两个参数一个返回值
map 相当于 Function 函数接口。
reduce 相当于 BinaryOperator extends BiFunction<T,T,T>。
forEach 需要的是 Consumer。
1 2 3 4 5 6 7 8 9 10
| Integer ints = 1; Function<Integer, Integer> mapper = e -> { if (e == null) { return 0; } return 2 * e; };
int a = Optional.ofNullable(ints).map(mapper).filter(e -> e > 3).orElse(3);
|
Optional 一般用于检验方法入参,或者作为函数返回值,一般不用于函数参数。OOP和函数式没有孰优孰劣,当方法链越来越长,再夹杂一些异步,就会丧失可读性。
Stream是对集合和增强,简化了并行[forkJoin],非常的实用,一定要掌握。
1 2 3 4 5 6 7 8 9 10 11 12 13
|
List<Integer> nums = Arrays.asList(1, 1, null, 2, 3, 4, null, 5, 6, 7, 8, 9, 10); HashSet<Integer> integers = new HashSet<>(nums); int mount = 0; for (Integer integer : integers) { if (integer == null) { continue; } mount += integer; }
int sum = nums.stream().filter(Objects::nonNull).distinct().mapToInt(Integer::intValue).sum();
|
Stream 不是集合元素,并不保存数据,Stream 就如同一个高级迭代器(Iterator),单向,不可往复,数据只能遍历一次。Stream 分类:1.适用引用类型 Stream<T> 2.基本类型对应的Stream,IntStream,LongStream,DoubleStream。这些可以相互转化。
Stream 使用,三步:
1.获取Stream
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 1.从集合获取 Collection.stream(); Collection.parallelStream(); 2.Array工具类Arrays可以获取任何类型Stream,除了下面还有一些重载方法 stream(T[] array); stream(int[] array); stream(long[] array); stream(double[] array); eg: Integer[] arr = {10, 7, 8, 4, 6}; Stream<Integer> stream = Arrays.stream(arr); 3.Stream 静态方法 Stream<T> empty(); Stream.of(T t); Stream.of(T... values); Stream.ofNullable(T t);
|