简体   繁体   中英

How to implicitly wrap a value that can be null or an array into an Scala Option

I have this Java class in a Jar file included as a dependency of an Scala program (like the Axis jar):

class MyClass {
    private String[] someStrings;
    public String[] getSomeStrings() { return someStrings; }
}

In my Scala program I have a Java API that return an instance of MyClass instance of MyClass to my program in Scala:

val myInstance = JavaAPI.getInstanceOfMyClass()

Then I try to use the someStrings array in my Scala program but it's null (let say that it wasn't properly initialized)

for(str <- myInstance.getSomeStrings()) ...

So this throws a NullPointerException .

I've found that in order to use it in the for comprehension I can wrap it into an Option so that it handles the NPE properly.

for(str <- Option[Array[String]](myInstance.getSomeStrings).getOrElse(Array[String]())

But that doesn't look OK to me.

Is there a way to create like an implicit method that takes that value even if it's null and wrap it into the Option, like:

implicit def wrapNull(a: Null): Option[Nothing] = None
implicit def wrapArray(a: Array[String]): Option[Array[String]] = Some(a)

So that when I do:

for(str <- myInstance.getSomeStrings())

I don't get the NPE

Thanks in advance!

edit:

A map as well as a flatMap always have to return the same type, on which they are called. If you have a List, you will always get a List back from map . The same is true for an Option. If you try to mix 2 types in a flatMap, this will most likely not work. What should

Some(Array(1,2)).flatMap { x => 
  x.map { _ * 2 }
}

return? Some(2,4) is not possible. So you get a type error. For this reason you have to do a nested map { map } instead of flatMap { map } .

In your case it would work like this:

case class A(b: B)
case class B(c: String)

val result = for(as <- Option(Array(A(B("foo")), A(B("bar"))))) yield {
  for(a <- as; b <- Option(a.b); c <- Option(b.c)) yield {
    c
  }
}

The first for takes an Option[Array[A]] and returns an Option[Array[String]] . The nested for takes an Array[A] and returns an Array[String] . They both satisfy the monad laws. At the end you can safely call getOrElse on result to unwrap the value if you want to.

original:

You could just do

val result = Option(myInstance.getSomeStrings).map { x =>
  x.map { y =>
    // do stuff with strings
  }
}

or

val result = for(x <- Option(myInstance.getSomeStrings)) yield {
  x.map { y =>
    // do stuff with strings
  }
}

You don't need to write the types because of the type inference and you don't need getOrElse , because the map will not be executed for None . You can then simply do a getOrElse on result, if you need to unwrap the value.

I don't think your version with getOrElse is that bad (you can make it a little shorter by removing the [Array[String]] after Option , since that can be inferred). If you want something even more concise, the following works:

for (str <- Option(myInstance.getSomeStrings).flatten) ...

You could also use the fact that Option has a foreach :

for {
  strings <- Option(myInstance.getSomeStrings)
  str <- strings
} ...

Note that you can't use yield here, for the reason that drexin highlights in a comment below.

Or you could pimp MyClass :

implicit def withNullWrapper(c: MyClass) = new {
  def getSomeStringsOrNot() = Option(c.getSomeStrings).getOrElse(Array[String]())
}

for (str <- myInstance.getSomeStringsOrNot) ...

Simple rule: where there's a null , make it an Option . So:

for {
  array <- Option(myInstance.getSomeStrings)
  element <- array
  thingy <- Option(element.method)
} yield thingy

Only that won't work. Because of the array , it will return multiple elements, but because the first generator is an Option , it will return an Option . These two elements are adverse: you can't return an Option of multiple elements.

The simplest alternative to fix the problem is to convert the Option into an iterator or a collection (according to your taste). Like this:

for {
  array <- Option(myInstance.getSomeStrings).toSeq
  element <- array
  thingy <- Option(element.method)
} yield thingy

Note that the second Option need not be touched: the one that caused the problem was the one as the first generator. Option anywhere other than the first generator is not a problem.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM