前面在介绍清单用法的时候,讲到了既能使用for循环遍历清单,也能经过stream流式加工清单。譬如从一个苹果清单中挑选出红苹果清单,采起for循环和流式处理均可以实现。下面是经过for循环挑出红苹果清单的代码例子:html
// 经过简单的for循环挑出红苹果清单 private static void getRedAppleWithFor(List<Apple> list) { List<Apple> redAppleList = new ArrayList<Apple>(); for (Apple apple : list) { // 遍历现有的苹果清单 if (apple.isRedApple()) { // 判断是否为红苹果 redAppleList.add(apple); } } System.out.println("for循环 红苹果清单=" + redAppleList.toString()); }
至于经过流式处理挑出红苹果清单的代码示例以下:java
// 经过流式处理挑出红苹果清单 private static void getRedAppleWithStream(List<Apple> list) { // 挑出红苹果清单 List<Apple> redAppleList = list.stream() // 串行处理 .filter(Apple::isRedApple) // 过滤条件。专门挑选红苹果 .collect(Collectors.toList()); // 返回一串清单 System.out.println("流式处理 红苹果清单=" + redAppleList.toString()); }
然而上述的两段代码只能在数据完整的状况下运行,一旦原始的苹果清单存在数据缺失,则两段代码均没法正常运行。例如,苹果清单为空,清单中的某条苹果记录为空,某个苹果记录的颜色字段为空,这三种状况都会致使程序遇到空指针异常而退出。看来编码不是一件轻松的活,不但要让程序能跑通正确的数据,并且要让程序对各类非法数据应对自如。换句话说,程序要足够健壮,要拥有适当的容错性,即便是吃错药了,也要可以自动吐出来,而不是硬吞下去结果一病不起。对应到挑选红苹果的场合中,则需层层递进判断原始苹果清单的数据完整性,假若发现任何一处的数据存在缺漏状况(如出现空指针),就跳过该处的数据处理。因而在for循环先后添加了空指针校验的红苹果挑选代码变成了下面这样:程序员
// 在for循环的内外添加必要的空指针校验 private static void getRedAppleWithNull(List<Apple> list) { List<Apple> redAppleList = new ArrayList<Apple>(); if (list != null) { // 判断清单非空 for (Apple item : list) { // 遍历现有的苹果清单 if (item != null) { // 判断该记录非空 if (item.getColor() != null) { // 判断颜色字段非空 if (item.isRedApple()) { // 判断是否为红苹果 redAppleList.add(item); } } } } } System.out.println("加空指针判断 红苹果清单=" + redAppleList.toString()); }
因而可知修改后的for循环代码一共增长了三个空指针判断,可是上面代码明显太复杂了,没必要说层层嵌套的条件分支,也没必要说屡次缩进的代码格式,单单说后半部分的数个右花括号,简直叫人看得眼花缭乱,难以分清哪一个右花括号究竟对应上面的哪一个流程控制语句。这种状况实在考验程序员的眼力,要是一不留神看走眼放错其它代码的位置,岂不是捡了芝麻丢了西瓜?
空指针的校验代码当然繁琐,倒是万万少不了的,究其根源,乃是Java设计之初偷懒所致。正常状况下,声明某个对象时理应为其分配默认值,从而确保该对象在任什么时候候都是有值的,但早期的Java图省事,若是程序员没在声明对象的同时加以赋值,那么系统也不给它初始化,结果该对象只好指向一个虚无缥缈的空间,而在太虚幻境中不管作什么事情都只能是黄粱一梦。
空指针的设计缺陷根深蒂固,以致于后来的Java版本难以根除该毛病,迟至Java8才推出了针对空指针的解决方案——可选器Optional。Optional本质上是一种特殊的容器,其内部有且仅有一个元素,同时该元素还可能为空。围绕着这个可空元素,Optional衍生出若干泛型方法,目的是将复杂的流程控制语句概括为接续进行的方法调用。为了兼容已有的Java代码,一般并不直接构造Optional实例,而是调用它的ofNullable方法塞入某个实体对象,再调用Optional实例的其它方法进行处理。Optional经常使用的实例方法罗列以下:
get:获取可选器中保存的元素。若是元素为空,则扔出无此元素异常NoSuchElementException。
isPresent:判断可选器中元素是否为空。非空返回true,为空返回false。
ifPresent:若是元素非空,则对该元素执行指定的Consumer消费事件。
filter:若是元素非空,则根据Predicate断言条件检查该元素是否符合要求,只有符合才原样返回,若不符合则返回空值。
map:若是元素非空,则执行Function函数实例规定的操做,并返回指定格式的数据。
orElse:若是元素非空就返回该元素,不然返回指定的对象值。
orElseThrow:若是元素非空就返回该元素,不然扔出指定的异常。
接下来看一个Optional的简单应用例子,以前在苹果类中写了isRedApple方法,用来判断自身是否为红苹果,该方法的代码以下所示:app
// 判断是否红苹果 public boolean isRedApple() { // 不严谨的写法。一旦color字段为空,就会发生空指针异常 return this.color.toLowerCase().equals("red"); }
显而易见这个isRedApple方法很不严谨,一旦颜色color字段为空,就会发生空指针异常。常规的补救天然是增长空指针判断,遇到空指针的状况便自动返回false,此时方法代码优化以下:函数
// 判断是否红苹果 public boolean isRedApple() { // 常规的写法,判断color字段是否为空,再作分支处理 boolean isRed = (this.color==null) ? false : this.color.toLowerCase().equals("red"); return isRed; }
如今借助可空器Optional,支持一路过来的方法调用,先调用ofNullable方法设置对象实例,再调用map方法转换数据类型,再调用orElse方法设置空指针之时的取值,最后调用equals方法进行颜色对比。采起Optional形式的方法代码示例以下:优化
// 判断是否红苹果 public boolean isRedApple() { // 利用Optional进行可空对象的处理,可空对象指的是该对象可能不存在(空指针) boolean isRed = Optional.ofNullable(this.color) // 构造一个可空对象 .map(color -> color.toLowerCase()) // map指定了非空时候的取值 .orElse("null") // orElse设置了空指针时候的取值 .equals("red"); // 再判断是否红苹果 return isRed; }
然而上面Optional方式的代码行数明显超过了条件分支语句,它的先进性又何从体现呢?其实可选器并不是要彻底取代原先的空指针判断,而是提供了另外一种解决问题的新思路,经过合理搭配各项技术,方能取得最优的解决办法。仍以挑选红苹果为例,本来判断元素非空的分支语句“if (item != null)”,采用Optional改进以后的循环代码以下所示:this
// 把for循环的内部代码改写为Optional校验方式 private static void getRedAppleWithOptionalOne(List<Apple> list) { List<Apple> redAppleList = new ArrayList<Apple>(); if (list != null) { // 判断清单非空 for (Apple item : list) { // 遍历现有的苹果清单 if (Optional.ofNullable(item) // 构造一个可空对象 .map(apple -> apple.isRedApple()) // map指定了item非空时候的取值 .orElse(false)) { // orElse指定了item为空时候的取值 redAppleList.add(item); } } } System.out.println("Optional1判断 红苹果清单=" + redAppleList.toString()); }
注意到以上代码仍然存在形如“if (list != null)”的清单非空判断,并且该分支后面还有要命的for循环,这下既要利用Optional的ifPresent方法输入消费行为,又要使用流式处理的forEach方法遍历每一个元素。因而进一步改写后的Optional代码变成了下面这般:编码
// 把清单的非空判断代码改写为Optional校验方式 private static void getRedAppleWithOptionalTwo(List<Apple> list) { List<Apple> redAppleList = new ArrayList<Apple>(); Optional.ofNullable(list) // 构造一个可空对象 .ifPresent( // ifPresent指定了list非空时候的处理 apples -> { apples.stream().forEach( // 对苹果清单进行流式处理 item -> { if (Optional.ofNullable(item) // 构造一个可空对象 .map(apple -> apple.isRedApple()) // map指定了item非空时候的取值 .orElse(false)) { // orElse指定了item为空时候的取值 redAppleList.add(item); } }); }); System.out.println("Optional2判断 红苹果清单=" + redAppleList.toString()); }
虽然二度改进后的代码已经消除了空指针判断分支,可是依然留下是否为红苹果的校验分支,仅存的if语句着实碍眼,干脆一不作二不休引入流式处理的filter方法替换if语句。几经修改获得了如下的最终优化代码:设计
// 联合运用Optional校验和流式处理 private static void getRedAppleWithOptionalThree(List<Apple> list) { List<Apple> redAppleList = new ArrayList<Apple>(); Optional.ofNullable(list) // 构造一个可空对象 .ifPresent(apples -> { // ifPresent指定了list非空时候的处理 // 从原始清单中筛选出红苹果清单 redAppleList.addAll(apples.stream() .filter(a -> a != null) // 只挑选非空元素 .filter(Apple::isRedApple) // 只挑选红苹果 .collect(Collectors.toList())); // 返回结果清单 }); System.out.println("Optional3判断 红苹果清单=" + redAppleList.toString()); }
好不容易去掉了全部if和for语句,尽管代码的总行数未有明显减小,不过逻辑结构显然变得更加清晰了。指针
更多Java技术文章参见《Java开发笔记(序)章节目录》