使用 isPresent() 从 Optional 内部获取值

Getting a value from an inside an Optional with isPresent()

我有一个 User 和相关的限时 Role。我想知道 User 是否有特定的 UserRole 未过期。我可以将用户的角色变成一个流,filter() 它和 findFirst(),给我一个 Optional.

角色

public class Role {
    private UserRole role;
    private Date expiry;
    
    public boolean isUnexpired () {
        return (expiry == null) ? true : expiry.after(new Date());
    }
}

用户

public class User {
  //...
  private Collection<Role> roles

  public boolean hasRole (UserRole userRole) {
    return roles.stream()
      .filter(r -> r.getRole().equals(userRole))
      .findFirst()
      .ifPresent(ur -> {  /* ... herein the problem ... */ ur.isUnexpired(); } );
  }
}

最后一行的问题是 ifPresent() 的签名无效;因此,我不能 return ur.isUnexpired() 从它。无论我在 lambda 表达式或匿名内部 class 中放入什么,都无法对其找到的值做任何有意义的事情。

我尝试在过滤流并分配它之前声明一个布尔值,但出现(代码验证)错误:local variables referenced from a lambda expression must be final or effectively final.

(我知道,当它不存在时还有更多的事情要处理;如果我能解决这个问题,我可以换成 ifPresentOrElse()。)

我可以做到以下几点:

  public boolean hasRole (UserRole userRole) {
    Optional<Role> o = roles.stream()
      .filter(r -> r.getRole().equals(userRole))
      .findFirst();
    return o.isPresent() ? o.get().isUnexpired() : false;
  }

但是,我宁愿使用更清晰的链式函数。

有什么方法可以提取和使用我的 isUnexpired() 布尔值和链式函数吗?或者我必须分配 Optional 然后单独操作它?

您应该使用 Optional::map 检索 isUnexpired 的值,并使用 orElse 检索 return false:

public boolean hasRole (UserRole userRole) {
    return roles.stream()
        .filter(r -> r.getRole().equals(userRole))
        .findFirst()
        .map(Role::isUnexpired)
        .orElse(false);
}

However, I would rather do it with a cleaner, chained function.

为什么链式函数会是 'cleaner'?正如您所看到的,这使得 更难以使代码适应不断变化的需求, 在您的其他代码上强制执行奇怪的样式选择,以便绕过这样一个事实,即您不能使用可变局部变量并且无法获得控制流或已检查的异常透明度。我不知道你正在研究 'cleaner' 的什么定义,但显然它不是“导致更容易修改、更容易测试、更容易阅读、更容易在代码中满足其他要求的代码”,这对我来说似乎是一个更明智的定义。也许你的定义是基于美学的。好吧,正如他们所说,您可能无法与品味争论。

无论如何,你有两个选择:

将 optional.NONE 映射到一个随后未通过测试的哨兵上。

您可以在这里简单地使用 .orElse()

...
.findFirst()
.orElse(dummyRoleThatIsDefinitelyExpired)
.isUnexpired();

映射 optional.SOME

...
.findFirst()
.map(r -> r.isUnexpired())
.orElse(false);

map 调用将 return 一个 Optional.NONE 或 Boolean.TRUE 的可选或 Boolean.FALSE 的可选。然后我们 orElse 将 NONE 案例变为 FALSE,我们现在有一个布尔值 return.

我会说第一个代码片段更容易理解,但它需要有一个肯定已过期的虚拟角色。

注意:如果您关心干净的函数,那么出于显而易见的原因,在布尔方法名称中加入否定并不是一个好主意。而不是 Unexpired,也许 isLive()isValid() 是更好的主意。