One more word on the Option
type. While Option
allows you a more type-safe alternative to null
s, it does not itself handle null
s. In other words, this …
val s = Some(null)
… gets you a Some
containing a null
. That doesn’t help so much if our aim is to get away from null
, not kick it down the road.
The Java Way
In version 8, Java introduced its own Optional
type, no doubt inspired by the Option
and Maybe
types available in the functional languages. It does handle null
s. Take the following classes as examples:
public class User { private String username; private String name; public User(String username) { this(username, null); } public User(String username, String name) { this.username = username; this.name = name; } public String getUsername() { return username; } public String getName() { return name; } } public class Session { private User user; public Session() { this(null); } public Session(User u) { this.user = u; } public User getUser() { return user; } } public class Application { private Session session; public Application() { this(null); } public Application(Session s) { this.session = s; } public Session getSession() { return session; } }
Because Optional
handles null
in its map
operation, this works:
Application app = new Application(new Session()); String name = Optional.of(app) .map(Application::getSession) .map(Session::getUser) .map(User::getName) .orElse("n/a"); // name = "n/a"
In our example, the Session
has a null
User
property. But that’s OK, because the map
operation on line 4 (highlighted above) takes that null
and converts it to a None
, or more accurately, an Empty
per Java parlance.
Back in Scala Land
We cannot do quite the same thing in Scala. Now we, as enlightened Scala developers, would never code up something that uses null
s all over the place like the Java classes above. But the Java world is more comfortable with null
s, so let’s say that you are working with a library that contains those Java classes. You pull the JAR into your Scala project and write something like this:
val app = new Application(new Session()) val name = Option(app) .map(_.getSession) .map(_.getUser) .map(_.getName) .getOrElse("n/a") // NullPointerException!!!
But that breaks down pretty quickly. Line 4 (highlighted above) yields an exception because _.getUser
is null
. Option.map
in Scala does not convert a null
to a None
. So then, what do we do?
Well, you can instantiate an Option
by applying the Option
object. Option()
does take an object reference and return a None
if it is a null
, or a Some
if it is not. Also, remember that Option.flatMap
takes an operation that returns an Option
and flattens it so that it does not return a nested Option
, but just an Option
. Therefore, you can change your code to this:
let app = Application(Session()) val name = Option(app) .flatMap(a => Option(a.getSession)) .flatMap(s => Option(s.getUser)) .flatMap(u => Option(u.getName)) .getOrElse("n/a") // name : String = "n/a"
It’s a bit more headache than you’d like, but that’s what you get for using flaky Java code, right?