【问题标题】:Returning different generic type with Optional.map.orElse使用 Optional.map.orElse 返回不同的泛型类型
【发布时间】:2019-10-15 14:03:29
【问题描述】:

我使用 Spring Boot 来编写一个 REST 服务。 当操作成功并相应地失败时,我需要返回一个不同的实体。 Spring 中的ResponseEntityT 类型参数化。我知道我可以省略类型并仅返回 ResponseEntity,但是在尝试使用 Java 8 OptionalorElse 链创建响应时这还不够:

public ResponseEntity getDashboard(String user, UUID uuid) {
Optional<Dashboard> dashboard = dashboardService.getDashboard( user, uuid );

// this gives unchecked assignment: 'org.springframework.http.ResponseEntity' 
// to 'org.springframework.http.ResponseEntity<my.package.SomeClass>'
return dashboard
    .map( ResponseEntity::ok )
    .orElse( createNotFoundResponse( uuid, "No such object" ) );
}

public static <T> ResponseEntity createNotFoundResp( T entity, String message ) {
    ResponseMessage<T> responseMessage = new ResponseMessage<>( message, entity );
    return ResponseEntity.status( HttpStatus.NOT_FOUND ).body( responseMessage );
}

由于 Java 编译器的类型推断,orElse 子句应返回与可选项不为空时相同的类型,即ResponseEntity&lt;Dashboard&gt; 而不是ResponseEntity&lt;ResponseMessage&gt;。我试图通过提供不同的返回路径来颠覆这个问题:

if ( dashboard.isPresent() ) {
    return ResponseEntity.ok( dashboard.get() );
} else {
    return createNotFoundResponse( uuid, "No such object" );
}

...但随后 Intellij 突出显示了 dashboard.isPresent() 部分,并大喊此块可以简化为上面的块(这会导致未经检查的警告)。

有没有办法在没有任何编译器警告和@SuppressUnchecked 注释的情况下干净地编写此代码?

【问题讨论】:

  • 两种方法的签名中添加&lt;?&gt;ResponseEntity,导致警告对我来说消失了。
  • "有没有办法在没有任何编译器警告的情况下干净地编写此代码" 是的,使用 if/else。 Intellij 是错误的。 (我的 intellij 不建议“简化”)。
  • @AndyTurner 不同意。 if(x.isPresent() { return f(x.get()); } else {return y;} 是一种反模式。 return x.map(F:f).orElse(y) 是正确的做法。
  • @slim 你只能写如果f 返回x.get()y 的公共超类型。例如,Optional.of("").map(Function.identity()).orElse(new Object()) 不会编译。
  • @AndyTurner 这是真的,但在极少数情况下,你可以投:Optional.of("").map(x -&gt; (Object) x).orElse(new Object())

标签: java spring-mvc generics optional


【解决方案1】:

有没有办法在没有任何编译器警告和@SuppressUnchecked 注释的情况下干净地编写此代码?

我认为在这种情况下您无法摆脱编译器警告。一种可能的干净解决方案(至少,没有编译器警告)是拒绝 Optional.map 的想法,而支持简单的 if/else?: 驱动的策略,但流式接口不可用。

static <T, U> ResponseEntity<?> okOrNotFound(final Optional<T> optional, final Supplier<? extends U> orElse) {
    return okOrNotFound(optional, "Not found", orElse);
}

static <T, U> ResponseEntity<?> okOrNotFound(final Optional<T> optional, final String message, final Supplier<? extends U> orElse) {
    return optional.isPresent()
            ? status(OK).body(optional.get())
            : status(NOT_FOUND).body(new NotFound<>(orElse.get(), message));
}
@RequestMapping(method = GET, value = "/")
ResponseEntity<?> get(
        @RequestParam("user") final String user,
        @RequestParam("uuid") final UUID uuid
) {
    final Optional<Dashboard> dashboard = dashboardService.getDashboard(user, uuid);
    return okOrNotFound(dashboard, () -> uuid);
}

注意orElse 并不是你真正想要的:orElseGet 是惰性的,只有在给定的可选值不存在时才会调用它的供应商。

但是,Spring 提供了一种更好的方式来完成您需要的事情,我相信有一种更简洁的方式来完成类似的事情。看看专为此目的设计的controller advices

// I would prefer a checked exception having a super class like ContractException
// However you can superclass this one into your custom super exception to serve various purposes and contain exception-related data to be de-structured below
final class NotFoundException
        extends NoSuchElementException {

    private final Object entity;

    private NotFoundException(final Object entity) {
        this.entity = entity;
    }

    static NotFoundException notFoundException(final Object entity) {
        return new NotFoundException(entity);
    }

    Object getEntity() {
        return entity;
    }

}

现在 REST 控制器方法变为:

@RequestMapping(method = GET, value = "/")
Dashboard get(
        @RequestParam("user") final String user,
        @RequestParam("uuid") final UUID uuid
) {
    return dashboardService.getDashboard(user, uuid)
            .orElseThrow(() -> notFoundException(uuid));
}

Spring 足够聪明,可以将对象转换为 status(OK).body(T) 本身,因此我们只是抛出一个包含我们感兴趣的单个对象的异常。接下来,示例控制器异常建议可能如下所示:

@ControllerAdvice
final class ExceptionControllerAdvice {

    @ExceptionHandler(NotFoundException.class)
    ResponseEntity<NotFound<?>> acceptNotFoundException(final NotFoundException ex) {
        return status(NOT_FOUND).body(notFound(ex));
    }

}

notFound() 方法的实现如下:

static NotFound<?> notFound(final NotFoundException ex) {
    return notFound(ex, "Not found");
}

static NotFound<?> notFound(final NotFoundException ex, final String message) {
    return new NotFound<>(ex.getEntity(), message);
}

对于我的秒杀项目提供了以下结果:

  • _http://localhost:8080/?user=owner&uuid=00000000-0000-0000-0000-000000000000 - {"description":"dashboard owned by owner"}
  • _http://localhost:8080/?user=user&uuid=00000000-0000-0000-0000-000000000000 - {"entity":"00000000-0000-0000-0000-000000000000","message":"Not found"}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-08-25
    • 2018-09-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多