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

w1jd8yoj  于 2023-03-21  发布在  Java
关注(0)|答案(3)|浏览(247)

我有一个User和相关的有时间限制的Role s。我想知道一个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()有一个无效签名;因此,我不能从它返回ur.isUnexpired()。无论我在lambda表达式或匿名内部类中放置什么,都不能对它找到的值做任何有意义的事情。
我尝试在过滤流之前声明一个布尔值,并赋值,但得到(代码验证)错误: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然后单独操作它?

34gzjxbg

34gzjxbg1#

应该使用Optional::map来检索isUnexpired的值,使用orElse来返回false

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

如果有 * 多个 * 相同的角色具有 * 不同的 * 到期日期(@Ray提到的有点奇怪的用例),可能需要按到期日期(降序)进行额外的排序来解决冲突:

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

iezvtpos2#

如果你只关心用户是否在Collection<Role> roles中有任何未过期的UserRole条目,使用anyMatch()匹配 predicate 是一种更清晰、更简洁的方法:

public boolean hasRole (UserRole userRole) {
   return roles.stream()
       .anyMatch(r -> r.role.equals(userRole) && r.isUnexpired());
}

这种使用anyMatch()的方法对于将来的代码读者来说是清楚的,没有任何歧义。
为什么不使用stream/filter/findFirst方法呢?仅基于OP中的信息,private Collection<Role> roles中可能有多个条目具有相同的UseRole和不同的到期日期。findFirst()将产生不确定的结果,除非您采取措施确保集合的顺序(集合本身对订单没有保证)或者在过滤器中进行过期检查。即使使用签入过滤器,我认为anyMatch()对您的意图不太开放。

h79rfbju

h79rfbju3#

但是,我宁愿用一个更干净的,链式的函数来做。
为什么一个链式函数是“更干净”的?正如你所看到的,它使代码适应不断变化的需求变得更加困难,并且在你的其他代码上强制执行奇怪的风格选择,以便绕过你不能使用可变的局部变量,也不能获得控制流或检查异常透明性的事实。我不知道你正在研究的“更干净”的定义是什么,但很明显,它不是“代码导致更容易修改,更容易测试,更容易阅读,更容易在代码中满足其他需求”,这对我来说似乎是一个更明智的定义。也许你的定义是基于美学。嗯,正如他们所说,你不能与品味争论,也许。
无论如何,你有两个选择:

将optional.NONEMap到一个sentinel上,然后该sentinel测试失败。

你可以在这里使用.orElse()

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

Map可选。SOME

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

map调用将返回Optional.NONE,或者Boolean.TRUE的Optional或Boolean.FALSE的可选值。然后我们将NONE情况转换为FALSE,现在我们有一个布尔值要返回。
我认为第一个代码片段更容易理解,但它需要一个肯定过期的虚拟角色。
注意:如果你关心干净的函数,那么在布尔方法名中使用负数并不是一个好主意,原因很明显。也许isLive()isValid()Unexpired更好。

相关问题