Chinaunix首页 | 论坛 | 博客
  • 博客访问: 117381
  • 博文数量: 165
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 1655
  • 用 户 组: 普通用户
  • 注册时间: 2022-09-26 14:37
文章分类

全部博文(165)

文章存档

2024年(2)

2023年(95)

2022年(68)

我的朋友

分类: Java

2022-11-14 11:42:57

概述:Optional{BANNED}最佳早是Google公司Guava中的概念,代表的是可选值。Optional类从Java8版本开始加入豪华套餐,主要为了解决程序中的NPE问题,从而使得更少的显式判空,防止代码污染,另一方面,也使得领域模型中所隐藏的知识,得以显式体现在代码中。Optional类位于java.util包下,对链式编程风格有一定的支持。实际上,Optional更像是一个容器,其中存放的成员变量是一个T类型的value,可值可Null,使用的是Wrapper模式,对value操作进行了包装与设计。本文将从Optional所解决的问题开始,逐层解剖,由浅入深,文中会出现Optioanl方法之间的对比,实践,误用情况分析,优缺点等。与大家一起,对这项Java8中的新特性,进行理解和深入。

1、解决的问题

臭名昭著的空指针异常,是每个程序员都会遇到的一种常见异常,任何访问对象的方法与属性的调用,都可能会出现NullPointException,如果要确保不触发异常,我们通常需要进行对象的判空操作。

举个栗子,有一个人(Shopper)进超市购物,可能会用购物车(Trolley)也可能会用其它方式,购物车里可能会有一袋栗子(Chestnut),也可能没有。三者定义的代码如下:

PlainJavascriptJavaHTML/XMLMarkdownMakefileGoJSONSQLObjective-cYAMLBashPHPPython public class Shopper { private Trolley trolley; public Trolley getTrolley(){ return trolley;
  }
} public class Trolley { private Chestnut chestnut; public Chestnut getChestnut(){ return chestnut;
  }
} public class Chestnut { private String name; public String getName(){ return name;
  }
}

这时想要获得购物车中栗子的名称,像下面这么写,就可能会见到我们的“老朋友”(NPE)

PlainJavascriptJavaHTML/XMLMarkdownMakefileGoJSONSQLObjective-cYAMLBashPHPPython public String result(Shopper shopper){ return shopper.getTrolley().getChestnut().getName();
}

为了能避免出现空指针异常,通常的写法会逐层判空(多层嵌套法),如下

PlainJavascriptJavaHTML/XMLMarkdownMakefileGoJSONSQLObjective-cYAMLBashPHPPython public String result(Shopper shopper) { if (shopper != null) {
            Trolley trolley = shopper.getTrolley(); if (trolley != null) {
                Chestnut chestnut = trolley.getChestnut(); if (chestnut != null) { return chestnut.getName();
                }
            }
        } return "获取失败辽";
    }

多层嵌套的方法在对象级联关系比较深的时候会看得眼花缭乱的,尤其是那一层一层的括号;另外出错的原因也因为缺乏对应信息而被模糊(例如trolley为空时也只返回了{BANNED}最佳后的获取失败。当然也可以在每一层增加return,相应的代码有会变得很冗长),所以此时我们也可以用遇空则返回的卫语句进行改写。

PlainJavascriptJavaHTML/XMLMarkdownMakefileGoJSONSQLObjective-cYAMLBashPHPPython public String result(Shopper shopper) { if (shopper == null) { return "购物者不存在";
    }
    Trolley trolley = shopper.getTrolley(); if (trolley == null) { return "购物车不存在";
    }
    Chestnut chestnut = trolley.getChestnut(); if (chestnut == null) { return "栗子不存在";
    } return chestnut.getName();
}

为了取一个名字进行了三次显示判空操作,这样的代码当然没有问题,但是优秀的工程师们总是希望能获得更优雅简洁的代码。Optional就提供了一些方法,实现了这样的期望。

PlainJavascriptJavaHTML/XMLMarkdownMakefileGoJSONSQLObjective-cYAMLBashPHPPython public String result(Shopper shopper){ return Optional.ofNullable(shopper) .map(Shopper::getTrolley) .map(Trolley::getChestnut) .map(Chestnut::getName) .orElse("获取失败辽");
}

2、常用方法

1)获得Optional对象

Optional类中有两个构造方法:带参和不带参的。带参的将传入的参数赋值value,从而构建Optional对象;不带参的用null初始化value构建对象。

PlainJavascriptJavaHTML/XMLMarkdownMakefileGoJSONSQLObjective-cYAMLBashPHPPython private Optional() {} private Optional(T value) {}

但是两者都是私有方法,而实际上Optional的对象都是通过静态工厂模式的方式构建,主要有以下三个函数

PlainJavascriptJavaHTML/XMLMarkdownMakefileGoJSONSQLObjective-cYAMLBashPHPPython public static  Optional of(T value) {} public static  Optional ofNullable(T value) {} public static  Optional empty() {}

创建一个一定不为空的Optional对象,因为如果传入的参数为空会抛出NPE

PlainJavascriptJavaHTML/XMLMarkdownMakefileGoJSONSQLObjective-cYAMLBashPHPPython
Chestnut chestnut = new Chestnut();
Optional opChest = Optional.of(chestnut);

创建一个空的Optional对象

PlainJavascriptJavaHTML/XMLMarkdownMakefileGoJSONSQLObjective-cYAMLBashPHPPython
Optional<Chestnut> opChest = Optional.empty();

创建一个可空的Optional对象

PlainJavascriptJavaHTML/XMLMarkdownMakefileGoJSONSQLObjective-cYAMLBashPHPPython Chestnut chestnut = null; Optional opChest = Optional.ofNullable(chestnut);

2)正常使用

正常使用的方法可以被大致分为三种类型,判断类操作类取值类

PlainJavascriptJavaHTML/XMLMarkdownMakefileGoJSONSQLObjective-cYAMLBashPHPPython //判断类 public boolean isPresent() {} //操作类 public void ifPresent(Consumersuper T> consumer) {} //取值类 public T get() {} public T orElse(T other) {} public T orElseGet(Supplier other) {} public  T orElseThrow(Supplier exceptionSupplier) throws X {}

isPresent()方法像一个安全阀,控制着容器中的value值是空还是有值,用法与原本的null != obj的用法相似。当obj有值返回true,为空返回false(即value值存在为真)。但一般实现判断空或不为空的逻辑,使用Optional其他的方法处理会更为常见。如下代码将会打印出没有栗子的悲惨事实。

PlainJavascriptJavaHTML/XMLMarkdownMakefileGoJSONSQLObjective-cYAMLBashPHPPython
Optional opChest = Optional.empty(); if (!opChest.isPresent()){
  System.out.println("容器里没有栗子");
} 

ifPresent()方法是一个操作类的方法,他的参数是一段目标类型为Consumer的函数,当value不为空时,自动执行consumer中的accept()方法(传入时实现),为空则不执行任何操作。比如下面这段代码,我们传入了一段输出value的lamda表达式,打印出了“迁西板栗”。

PlainJavascriptJavaHTML/XMLMarkdownMakefileGoJSONSQLObjective-cYAMLBashPHPPython Optional<Chestnut> opChest = Optional.ofNullable(new Chestnut("迁西板栗"));
opChest.ifPresent(c -> System.out.println(c.getName()));

get()方法源码如下,可以看出,get的作用是直接返回容器中的value。但如此粗暴的方法,使用前如果不判空,在value为空时,便会毫不留情地抛出一个异常。

PlainJavascriptJavaHTML/XMLMarkdownMakefileGoJSONSQLObjective-cYAMLBashPHPPython public T get() { if (value == null) { throw new NoSuchElementException("No value present");
   } return value;
}

三个orElse方法与get相似,也都属于取值的操作。与get不同之处在于orElse方法不用额外的判空语句,撰写逻辑时比较愉快。三个orElse的相同之处是当value不为空时都会返回value。当为空时,则另有各自的操作:orElse()方法会返回传入的other实例(也可以为Supplier类型的函数);orElseGet()方法会自动执行Supplier类型实例的get()方法;orElseThrow()方法会抛出一个自定的异常。更具体的差别会在后面的方法对比中描述。

如下面这段代码,展示了在没有栗子的时候,如何吐出“信阳板栗”、“镇安板栗”,以及抛出“抹油栗子”的警告。

PlainJavascriptJavaHTML/XMLMarkdownMakefileGoJSONSQLObjective-cYAMLBashPHPPython
Optional opChest = Optional.empty();
System.out.println(opChest.orElse(new Chestnut("信阳板栗")));
System.out.println(opChest.orElseGet(() -> new Chestnut("镇安板栗"))); try {
   opChest.orElseThrow(() -> new RuntimeException("抹油栗子呀"));
}catch (RuntimeException e){
   System.out.println(e.getMessage());
}

3)进阶使用

PlainJavascriptJavaHTML/XMLMarkdownMakefileGoJSONSQLObjective-cYAMLBashPHPPython public Optional filter(Predicatesuper T> predicate) {} public Optional map(Functionsuper T, ? extends U> mapper) {} public Optional flatMap(Functionsuper T, Optional> mapper) {}

filter()方法接受谓词为Predicate类型的函数作为参数,如果value值不为空则自动执行predicate的test()方法(传入时实现),来判断是否满足条件,满足则会返回自身Optional,不满足会返回空Optional;如果value值为空,则会返回自身Optional(其实跟空Optional也差不多)。如下代码,第二句中筛选条件“邵店板栗”与opChest中的板栗名不符,没有通过过滤。而第三句的筛选条件与opChest一致,所以{BANNED}最佳后打印出来的是“宽城板栗”。

PlainJavascriptJavaHTML/XMLMarkdownMakefileGoJSONSQLObjective-cYAMLBashPHPPython Optional<Chestnut> opChest = Optional.ofNullable(new Chestnut("宽城板栗"));
opChest.filter(c -> "邵店板栗".equals(c.getName())).ifPresent(System.out::println);
opChest.filter(c -> "宽城板栗".equals(c.getName())).ifPresent(System.out::println);

map()flatmap()方法传入的都是一个Function类型的函数,map在这里翻译为“映射”,当value值不为空时进行一些处理,返回的值是经过mapper的apply()方法处理后的Optional类型的值,两个方法的结果一致,处理过程中会有差别。如下代码,从opChest中获取了板栗名后,重新new了一个板栗取名“邢台板栗”,并打印出来,两者输出一致,处理形式上有差异,这个在后面的方法对比中会再次说到。

PlainJavascriptJavaHTML/XMLMarkdownMakefileGoJSONSQLObjective-cYAMLBashPHPPython Optional<Chestnut> opChest = Optional.ofNullable(new Chestnut("邢台板栗")); System.out.println(opChest.map(c -> new Chestnut(c.getName()))); System.out.println(opChest.flatMap(c -> Optional.ofNullable(new Chestnut(c.getName()))));

4)1.9新增

PlainJavascriptJavaHTML/XMLMarkdownMakefileGoJSONSQLObjective-cYAMLBashPHPPython public void ifPresentOrElse(Consumersuper T> action, Runnable emptyAction) {} public Optional or(Supplier
            
阅读(233) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~