Upgrading to Java 8——第三章 Optional and Similar Classes

原文地址Optional的代码相对更加简洁,当代码量较大时,我们很容易忘记进行null判定,但是使用Optional类则会避免这类问题。

  Java程序员对付空指针异常已经好多年了。在Java8中将有新的方式去处理他们。通过包装一个潜在的可能为null的类称为Optianal。

Java8新特性Optional、接口中的默认方法与静态方法,java8optional

下面这是一个嵌套的 if 判断,业务逻辑是从 httpRequst 中获取 X-Auth-Token
的值。逻辑是如果 header中有值则从 header 中取值否则从 cookie
中取值,取到值后调用一个 http 远程接口
获取用户信息,获取不到则报“获取用户信息失败”,如果 token
都不存在则直接返回 httpRespons 为 401-NoAuth

在Java8中添加了the Optional, OptionalInt, OptionalLong 和
OptionalDouble 类来处理空指针异常(NullPointerExceptions)。

Optional

Optional 类(java.util.Optional)
是一个容器类,代表一个值存在或不存在,原来用 null 表示一个值不存在,现在
Optional 可以更好的表达这个概念。并且可以避免空指针异常。

常用方法:

Optional.of(T t) : 创建一个 Optional 实例。

Optional.empty() : 创建一个空的 Optional 实例。

Optional.ofNullable(T t):若 t 不为 null,创建 Optional
实例,否则创建空实例。

isPresent() : 判断是否包含值。

orElse(T t) : 如果调用对象包含值,返回该值,否则返回t。

orElseGet(Supplier s) :如果调用对象包含值,返回该值,否则返回 s
获取的值。

map(Function f): 如果有值对其处理,并返回处理后的Optional,否则返回
Optional.empty()。

flatMap(Function mapper):与 map 类似,要求返回值必须是Optional。

下面引用ImportNew的一段内容来告诉我们如何正确使用Optional。比如千万不要写成这样子:

public static String getName(User u) {
    Optional<User> user = Optional.ofNullable(u);
    if (!user.isPresent())
        return "Unknown";
    return user.get().name;
}

这样改写非但不简洁,而且其操作还是和第一段代码一样。无非就是用isPresent方法来替代u==null。这样的改写并不是Optional正确的用法,我们再来改写一次。

public static String getName(User u) {
    return Optional.ofNullable(u)
                    .map(user->user.name)
                    .orElse("Unknown");
}

这样才是正确使用Optional的姿势。那么按照这种思路,我们可以安心的进行链式调用,而不是一层层判断了。看一段代码:

public static String getChampionName(Competition comp) throws IllegalArgumentException {
    if (comp != null) {
        CompResult result = comp.getResult();
        if (result != null) {
            User champion = result.getChampion();
            if (champion != null) {
                return champion.getName();
            }
        }
    }
    throw new IllegalArgumentException("The value of param comp isn't available.");
}

由于种种原因(比如:比赛还没有产生冠军、方法的非正常调用、某个方法的实现里埋藏的大礼包等等),我们并不能开心的一路comp.getResult().getChampion().getName()到底。而其他语言比如kotlin,就提供了在语法层面的操作符加持:comp?.getResult()?.getChampion()?.getName()。所以讲道理在Java里我们怎么办!

让我们看看经过Optional加持过后,这些代码会变成什么样子。

public static String getChampionName(Competition comp) throws IllegalArgumentException {
    return Optional.ofNullable(comp)
            .map(c->c.getResult())
            .map(r->r.getChampion())
            .map(u->u.getName())
            .orElseThrow(()->new IllegalArgumentException("The value of param comp isn't available."));
}

这就很舒服了。Optional的魅力还不止于此,Optional还有一些神奇的用法,比如Optional可以用来检验参数的合法性。

public void setName(String name) throws IllegalArgumentException {
    this.name = Optional.ofNullable(name).filter(User::isNameValid)
                        .orElseThrow(()->new IllegalArgumentException("Invalid username."));
}

上面代码引用importnew—Java8 如何正确使用 Optional。

这下面是之前同事写的代码

在java.util的包中,有很多使用Lambda表达式和方法引用的范例类。

接口中的默认方法与静态方法

Java8接口中可以添加静态方法,也可以添加默认方法,默认方法用
default修饰。

public interface Fun<T> {

    default void getName(){
        System.out.println("hello world");
    }

    static void getAge(){
        System.out.println("nine");
    }

}

若一个接口中定义了一个默认方法,他的实现类的一个父类定义了具有相同名称和参数列表的方法。则调用该实现类的时候执行父类中的方法。

public class TestF {
    public void getName(){
        System.out.println("TestF");
    }
}

public interface TestInterface {
    default void getName(){
        System.out.println("hello world");
    }
}

public class Test extends TestF  implements TestInterface{
    public static void main(String[] args) {
        Test t = new Test();
        t.getName();//输出的是TestF
    }
}

若一个实现类实现了两个接口,如果一个父接口提供一个默认方法,而另一个父接口也提供了一个具有相同名称和参数列表的方法(不管方法是否是默认方法),那么必须覆盖该方法来解决冲突,否则会报错。

public interface TestInterface {
    default void getName(){
        System.err.println("hello world");
    }
}
public interface TestInterface1 {
     void getName();
}
public class Test1 implements TestInterface, TestInterface1{
    public void getName(){
        System.out.println("Tes1F");
    }
}
java学习群669823128

Optional Optional 类(java.util.Optional)
是一个容器类,代表一个值存在或不存在,原…

if (methodNeedAuth) { //***身份验证 String token = request.getHeader("X-Auth-Token"); if (StringUtils.isEmpty { // 如果 header 中没有 X-Auth-Token 则从 cookie 中取 Cookie[] cookies = request.getCookies(); if (cookies == null || cookies.length == 0) { //cookie 为 null return returnNoAuthResult; } //这个地方判空,否则下面的 Arrays.stream 回报空指针异常 token = Arrays.stream.filter(cookie -> "X-Auth-Token".equals(cookie.getName.collect(Collectors.toList.getValue(); if (token == null) { // cookie 有值但是 cookie 中没有 X-Auth-Token return returnNoAuthResult; } } if (!StringTool.isNullOrEmpty { userInfo = userService.getUserInfoByToken; } if (userInfo == null || StringTool.isNullOrEmpty(userInfo.getUser_id { return returnNoAuthResult; }}

if (methodNeedAuth) { //***身份验证 String token = Optional.ofNullable(request.getHeader("X-Auth-Token")).orElseGet -> getTokenFromCookie //提取出一个方法 ); userInfo = Optional.ofNullable.map(Try.of(t -> userService.getUserInfoByToken.orElse; if (userInfo == null || StringTool.isNullOrEmpty(userInfo.getUser_id { response.sendError(401, "no auth"); return false; }}/** * 从 cookie 中获取 token */private String getTokenFromCookie(HttpServletRequest request) { Cookie[] cookies = Optional.ofNullable(request.getCookies.orElse(new Cookie[0]); // Optional 强制赋默认值,cookies一定不为 null String cookie = Arrays.stream.filter(item -> "X-Auth-Token".equals(item.getName.findFirst().map(Cookie::getValue).orElse; return cookie;}

这四个类是很相似的,Optional是它们当中最重要的类因为他可以用在任何类型上,其他的类只能各自用在integer,long和double上。

Java8 Optional 的常规用法

Java8 的 Optional 可以规避所有的空指针异常问题么?答案当然是否定的,
Optional<T>() 也是对象,他也会为 null, 所以也有可能报空指针异常哟。

  1. Optional.of: 它要求传入的 obj 不能是 null 值的,
    否则还没开始进入角色就倒在了 NullPointerException 异常上了.
  2. Optional.ofNullable: 它以一种智能的, 宽容的方式来构造一个 Optional
    实例. 来者不拒, 传 null 进到就得到 Optional.empty(), 非 null 就调用
    Optional.of.

那是不是我们只要用 Optional.ofNullable 一劳永逸,
以不变应二变的方式来构造 Optional 实例就行了呢? 那也未必, 否则
Optional.of 何必如此暴露呢, 私有则可?我本人的观点是:

  1. 当我们非常非常的明确将要传给 Optional.of 的 obj 参数不可能为 null
    时, 比如它是一个刚 new 出来的对象(Optional.of(new User, 或者是一个非
    null 常量时;
  2. 当想为 obj 断言不为 null 时, 即我们想在万一 obj 为 null 立即报告
    NullPointException 异常, 立即修改, 而不是隐藏空指针异常时,
    我们就应该果断的用 Optional.of 来构造 Optional 实例,
    而不让任何不可预计的 null 值有可乘之机隐身于 Optional 中.

   Optional
是一个包含可能会为null的值的容器。你应该认识到,试图去一个null的引用去调用方法或是属性会发生NullPointerException异常。处理空指针异常不是很难,但是很乏味。

Java8 Optional需要小心的地方

  1. Reports calls to java.util.Optional.get() without first checking
    with a isPresent() call if a value is available. If the Optional
    does not contain a value, get() will throw an exception. (调用
    Optional.get() 前不事先用 isPresent() 检查值是否可用. 假如 Optional
    不包含一个值, get() 将会抛出一个异常)
  2. Reports any uses of java.util.Optional<T>,
    java.util.OptionalDouble, java.util.OptionalInt,
    java.util.OptionalLong or com.google.common.base.Optional as the
    type for a field or a parameter. Optional was designed to provide a
    limited mechanism for library method return types where there needed
    to be a clear way to represent “no result”. Using a field with type
    java.util.Optional is also problematic if the class needs to be
    Serializable, which java.util.Optional is not. (使用任何像 Optional
    的类型作为字段或方法参数都是不可取的. Optional 只设计为类库方法的,
    可明确表示可能无值情况下的返回类型. Optional 类型不可被序列化,
    用作字段类型会出问题的)

一句话小结: 使用 Optional 时尽量不直接调用 Optional.get() 方法,
Optional.isPresent() 更应该被视为一个私有方法, 应依赖于其他像
Optional.orElse(), Optional.orElseGet(), Optional.map() 等这样的方法.

  考虑下面的代码,一个公司会有一个办公室,进而会有一个地址。为了简单起见,在这里地址有两个属性是街道和城市,这些所有的属性都可能为null。一个公司可能会没有办公室,一个地址可能没有完整的街道和城市的信息。

class Company {
    private String name;
    private Office office;

    public Company(String name, Office office) {
    this.name = name;
    this.office = office;
    }

    public String getName() {
    return name;
    }

    public Office getOffice() {
    return office;
    }
}

class Office {
    private String id;
    private Address address;

    public Office(String id, Address address) {
    this.id = id;
    this.address = address;
    }

    public String getId() {
    return id;
    }

    public Address getAddress() {
    return address;
    }
}

class Address {
    private String street;
    private String city;

    public Address(String street, String city) {
    this.street = street;
    this.city = city;
    }

    public String getStreet() {
    return street;
    }

    public String getCity() {
    return city;
    }
}

public class OptionalDemo1 {

    public static void main(String[] args) {
    Address address1 = new Address(null, "New York");
    Office office1 = new Office("OF1", address1);
    Company company1 = new Company("Door Never Closed", office1);

    // What is the street address of company1?
    // In which city company1 is located?
    String streetAddress = null;
    String city = null;
    if (company1 != null) {
        Office office = company1.getOffice();
        if (office != null) {
        Address address = office.getAddress();
        if (address != null) {
            streetAddress = address.getStreet();
            city = address.getCity();
        }
        }
    }
    System.out.println("Street Name:" + streetAddress);
    System.out.println("City:" + city);
    }
}

OptionalDemo1类为了测试创建了Company实例去获取公司的街道地址,考虑到所有的属性可能为空,我们在调用方法前都做了非空的处理,就像下面的代码:

if (company1 != null) {
            Office office = company1.getOffice();

            if (office != null) {
                Address address = office.getAddress();
                if (address != null) {
                    streetAddress = address.getStreet();
                    city = address.getCity();
                }
            } 
        }

  这种代码既无聊也读起来遭罪。
  这个时候Optional能够帮助你,如果你决定用它,你需要把所有可能为null的属性全部用Optional包装起来。例如,Company类中的office属性应该是这样的:

private Optional<Office> office;

  Address类中的city属性应该是这样的:

相关文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*
*
Website