Search code examples
javascalaoption-typescala-option

How do I rewrite this java function in scala keeping the same Optional input parameter?


I have the following method in java

protected <T> T getObjectFromNullableOptional(final Optional<T> opt) {
    return Optional.ofNullable(opt).flatMap(innerOptional -> innerOptional).orElse(null);
}

It takes a java Optional that can itself be null (I know, this is really bad and we're going to fix this eventually). And it wraps it with another Optional so it becomes either Some(Optional<T>) or None. Then it's flatMapped, so we get back Optional<T> or None, and finally apply orElse() to get T or null.

How do I write the same method with the same java.util.Optional in scala?

protected def getObjectFromNullableOptional[T >: Null](opt : Optional[T]): T =
    ???

I tried

protected def getObjectFromNullableOptional[T >: Null](opt : Optional[T]): T =
  Optional.ofNullable(opt).flatMap(o  => o).orElse(null)

But this gives me a Type mismatch error

Required: Function[_ >: Optional[T], Optional[NotInferedU]]

Found: Nothing => Nothing

I tried

protected def getObjectFromNullableOptional[T >: Null](opt : Optional[T]): T =
  Option(opt).flatMap(o => o).getOrElse(null)

But this gives me

Cannot resolve overloaded method 'flatMap'

Edit I neglected to mention I'm using scala 2.11. I believe @tefanobaghino's solution is for scala 2.13 but it guided me towards the right path. I put my final solution in comments under this solution


Solution

  • The last error raises a few suspicions: it looks like you're wrapping a Java Optional in a Scala Option. I would have instead have expected this to have failed because you're trying to flatMap to a different type, something like

    error: type mismatch;
     found   : java.util.Optional[T] => java.util.Optional[T]
     required: java.util.Optional[T] => Option[?]
    

    This seems to fulfill your requirement:

    import java.util.Optional
    
    def getObjectFromNullableOptional[T](opt: Optional[T]): T =
      Optional.ofNullable(opt).orElse(Optional.empty).orElse(null.asInstanceOf[T])
    
    assert(getObjectFromNullableOptional(null) == null)
    assert(getObjectFromNullableOptional(Optional.empty) == null)
    assert(getObjectFromNullableOptional(Optional.of(1)) == 1)
    

    You can play around with this here on Scastie.

    Note that asInstanceOf is compiled to a cast, not to an actual method call, so this code will not throw a NullPointerException.

    You can also go into something closer to your original solution by helping Scala's type inference a bit:

    def getObjectFromNullableOptional[T](opt: Optional[T]): T =
      Optional.ofNullable(opt).flatMap((o: Optional[T]) => o).orElse(null.asInstanceOf[T])
    

    Or alternatively using Scala's identity:

    def getObjectFromNullableOptional[T](opt: Optional[T]): T =
      Optional.ofNullable(opt).flatMap(identity[Optional[T]]).orElse(null.asInstanceOf[T])
    

    For a solution using Scala's Option you can do something very close:

    def getObjectFromNullableOption[T](opt: Option[T]): T =
      Option(opt).getOrElse(None).getOrElse(null.asInstanceOf[T])
    

    Note that going to your flatMap solution with Scala's Option allows you to avoid having to be explicit about the function type:

    def getObjectFromNullableOption[T](opt: Option[T]): T =
      Option(opt).flatMap(identity).getOrElse(null.asInstanceOf[T])
    

    I'm not fully sure about the specifics, but I believe the issue is that, when using java.util.Optional you are passing a Scala Function to Optional.flatMap, which takes a Java Function. The Scala compiler can convert this automatically for you but apparently you have to be specific and explicit about type for this to work, at least in this case.

    A note about your original code: you required T to be a supertype of Null but this is not necessary.

    You have a better context regarding what you are doing, but as a general advice it's usually better to avoid having nulls leak in Scala code as much as possible.