Option map return Some(null)

Hello, I have mixed java and scala code, but there was an unexpected result … I will give an example:

Java class:

public class A {
    public String getValue(String v) {
        if (v.equals("0")) return null;
        else
            return v;
    }
}
public class B {
    // Let's say that the function can return null
    public A createA() {
         return new A();
    }
}

Scala ( scalaVersion := “2.13.11”) use class java:

object Main {
   def main(args: Array[String]): Unit = {
    val b = new B()

    val r1: Option[String] = Option(b.createA()).map(a => a.getValue("return not null"))
    val r2: Option[String] = Option(b.createA()).map(a => a.getValue("0")) // return Some(null)

    println(r1.getOrElse("empty"))
    println(r2.getOrElse("empty"))

 // or matching
     r2 match {
       case Some(value) => println(value) // value == null
       case None => println("empty")
     }
  }
}

output:
r1: return not null
r2: null
r2 match: null

Why the result of the function .map( Some(f(this.get)) ) immediately wraps in Some?

  @inline final def map[B](f: A => B): Option[B] =
    if (isEmpty) None else Some(f(this.get)) //  function f() can return null

That’s one of common misconceptions about using Option.map which does not automatically protect you against null by wrapping them into None. What you should do instead is to use flatmap with another Option.apply. eg.

Option(b.createA()).flatMap(a => Option(a.getValue("0"))) // return None`
2 Likes

Is there any reason for this implementation of the ‘map’ method?
I thought that the implementation in the ‘map’ method would be similar to the Java ‘Optional .map’.

    public <U> Optional<U> map(Function<? super T, ? extends U> mapper) {
        Objects.requireNonNull(mapper);
        if (!isPresent()) {
            return empty();
        } else {
            return Optional.ofNullable(mapper.apply(value));
        }
    }

The assumption is that you don’t have null in proper Scala code. There is one specific function, Option#apply(), specifically for guarding against null values from Java interop, which must be applied to any potential null value immediately at the Java/Scala boundary. (Perhaps it should rather be called #safeFromJava() or similar, rather than #apply().)

The remainder of the Option API is perfectly consistent and useful in proper Scala with the absence of null values. Littering its implementation with guards against null values would implicitly signal that these are expected and ok in Scala. They aren’t. It’d be a first step in the wrong direction - and down the rabbit hole, as it’s hard to argue why null checks only should be applied in the Option API implementation, once you start doing it at all.

2 Likes

Yes, you are right, I tried to write the implementation of classes A and B on the scala and the compiler immediately threw an error.

Technically, it should be possible to reimplement any Java class in Scala. Your example A class in Scala would be

class A:
  def getValue(v: String): String =
    if (v.equals("0")) null else v

Scala does have null. We don’t really want it, this is what we pay for piggy-backing on the JVM type system and seamless Java interop. But it is strongly discouraged, both in OO and functional style, to use null in Scala code in any way, which includes allowing null values to enter the system from Java interop.

2 Likes