15

I have a method that returns a String. I want to substitute it with a default value such as "<empty>" if it returns an empty string or null. Let's assume its name is getSomeString, it is an expensive operation so I can call it only once, and I can't change its return type to Option[String]. For now, I'm doing the following:

val myStr = {
  val s = getSomeString
  if (s == null || s.isEmpty) "<empty>" else s
}

Is there a simpler way to achieve the same thing?

1
  • If it wasn't for the isEmpty test, you could just go Option(getSomeString) to convert it to an Option[String]. Do you get both null and empty strings in practice? Commented Aug 24, 2013 at 10:40

6 Answers 6

23
val myStr = Option(getSomeString).filterNot(_.isEmpty).getOrElse("<empty>")

Updated

I posted this code because I think the intent in this code is clear than if/else or pattern matching version, but I didn't consider the performance issue.

As the others in comments mentioned, this code is much slower than simple if / else or pattern matching(this line will create a lot new objects which is an expensive operation), so please do not use this code when performance is an issue.

Sign up to request clarification or add additional context in comments.

4 Comments

Doesn't look much simpler compared with original code (and moreover, this piece of code will be significantly slower than the former one).
I feel its lot cleaner.
It's not as clean and cheap as (ahem) my answer. Check out the carbon footprint.
@som-snytt You can be pretty obnoxious! Did no one consider a custom extractor?
15

Given an expensive function:

scala> def s(i: Int): String = i match { case 0=>null case 1=>"" case 2=>"hi" }
s: (i: Int)String

I think this is easy to read and free of overhead, cf this in the wild:

scala> def q(i: Int) = s(i) match { case ""|null => "<empty>" case x => x }
q: (i: Int)String

scala> q(0)
res3: String = <empty>

scala> q(1)
res4: String = <empty>

scala> q(2)
res5: String = hi

To my eyes, this is not as expressive, even with minimalist punctuation:

scala> Option(s(0)) filterNot (_.isEmpty) getOrElse "<empty>"
res6: String = <empty>

Moreover, contrast the cost in anonfun classes for the closures and additional method invocations:

scala> :javap -
  Size 1161 bytes
  MD5 checksum 765f5f67b0c574252b059c8adfab1cf0
  Compiled from "<console>"
[...]
         9: getstatic     #26                 // Field scala/Option$.MODULE$:Lscala/Option$;
        12: getstatic     #31                 // Field .MODULE$:L;
        15: iconst_0      
        16: invokevirtual #35                 // Method .s:(I)Ljava/lang/String;
        19: invokevirtual #39                 // Method scala/Option$.apply:(Ljava/lang/Object;)Lscala/Option;
        22: new           #41                 // class $anonfun$1
        25: dup           
        26: invokespecial #42                 // Method $anonfun$1."<init>":()V
        29: invokevirtual #48                 // Method scala/Option.filterNot:(Lscala/Function1;)Lscala/Option;
        32: new           #50                 // class $anonfun$2
        35: dup           
        36: invokespecial #51                 // Method $anonfun$2."<init>":()V
        39: invokevirtual #55                 // Method scala/Option.getOrElse:(Lscala/Function0;)Ljava/lang/Object;
        42: checkcast     #57                 // class java/lang/String
        45: putfield      #17                 // Field res6:Ljava/lang/String;

The pattern match is generally just an if-else, smaller and faster (even considering that it doesn't optimise s == "" to s.isEmpty):

scala> :javap -r #q
  public java.lang.String q(int);
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=5, args_size=2
         0: getstatic     #19                 // Field $line3/$read$$iw$$iw$.MODULE$:L$line3/$read$$iw$$iw$;
         3: iload_1       
         4: invokevirtual #22                 // Method $line3/$read$$iw$$iw$.s:(I)Ljava/lang/String;
         7: astore_3      
         8: ldc           #24                 // String 
        10: aload_3       
        11: invokevirtual #28                 // Method java/lang/Object.equals:(Ljava/lang/Object;)Z
        14: ifeq          22
        17: iconst_1      
        18: istore_2      
        19: goto          33
        22: aload_3       
        23: ifnonnull     31
        26: iconst_1      
        27: istore_2      
        28: goto          33
        31: iconst_0      
        32: istore_2      
        33: iload_2       
        34: ifeq          44
        37: ldc           #30                 // String <empty>
        39: astore        4
        41: goto          47
        44: aload_3       
        45: astore        4
        47: aload         4
        49: areturn       

But inspired by the other answer, even if I would never take this code home to meet my parents (because it incorrectly converts the value "null" if the expensive function returns it -- though maybe it's a feature to do that), here is a regex:

scala> def p(i: Int) = "" + s(i) replaceAll ("^null$|^$", "<empty>")
p: (i: Int)String

The "" + s(i) is a shorthand for String.valueOf, which of course produces the String "null" for a null reference value. I appreciate SO's ability not only to generate quick answers to questions, but to encourage some out-of-the-box thinking.

5 Comments

The javap makes it hard to read your post and see your point. What is the tl;dr? That if else pattern match is faster?
@huynhjl Thx for the feedback. I edited so as not to bury the lead. (Newspaper term, not the metal.) Not that I expect to win hearts/minds on SO.
@huynhjl In fact, to put it more strongly, this is a no-brainer. But if the kids think it's fun to use Option, then there are worse things they could be doing like rioting in the streets and causing real mayhem.
Didn't know pattern matching can also match null. Thanks!
News editors intentionally misspell it, "bury the lede", I learned from webster's on twitter.
2

You could replace null with empty string in first step using Option and then substitute with default text if result is empty (whether because it was empty originally or because it was null):

Option(getSomeString).getOrElse("").replaceAll("^$","<empty>")

Comments

2

You could add a method to String using an implicit value class

object ImplicitClassContainer {
  implicit class RichString(val s: String) extends AnyVal {
    def getOrDefault(defaultValue: String): String = {
      s match {
        case null | "" => defaultValue
        case x => x
      }
    }
  }

to be used like this

import ImplicitClassContainer._

println("hi".getOrDefault("<empty1>"))

println("".getOrDefault("<empty2>"))

val s: String = null
println(s.getOrDefault("<empty3>"))

so even the method call on null is handled gracefully (Scala 2.10.1).

2 Comments

@axaluss I do not understand your comment: The answer uses implicit value classes: It does extend AnyVal
i just wanted to add the doc link.
2

If you don't need changes:

Option(getSomeString).fold("<empty>")(s => s)

If you need to modify if for nonEmpty result. Simple example:

Option(getSomeString).fold("<empty>")(str => s"xxxxxxxx$str")

Comments

1
val myStr = getSomeString match { case ""|null => "<empty>" case s => s }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.