Reputation: 2437
Let's say that I have a List (or the values in a Map), and i want to perform an operation on each item. But unfortunately, for whatever reason, this list of values can contain nulls.
scala> val players = List("Messi", null, "Xavi", "Iniesta", null)
players: List[java.lang.String] = List(Messi, null, Xavi, Iniesta, null)
In order to avoid blowing up with a NPE, i need to do the following:
scala> players.filterNot(_ == null ).map(_.toUpperCase)
res84: List[java.lang.String] = List(MESSI, XAVI, INIESTA)
Is there any better way of doing this?
Ideally something like:
players.safeMap(_.toUpperCase)
On the scala-language mailing list, Simon proposed this:
players.filter ( null !=).map(_.toUpperCase )
which is shorter version of my original take, and as short as you can get without a dedicated method.
Even better, Stefan and Kevin proposed the method withFilter which will return a lazy proxy, so both operations can be merged.
players.withFilter ( null !=).map(_.toUpperCase )
Upvotes: 2
Views: 2590
Reputation: 297155
I'd do this:
players flatMap Option map (_.toUpperCase)
But that's worse than collect
. filter
+ map
is always better done with collect
.
Upvotes: 2
Reputation: 52681
You could convert to a list of Option[String]
:
scala> val optionPlayers = players.map(Option(_))
optionPlayers: List[Option[java.lang.String]] = List(Some(Messi), None, Some(Xavi), Some(Iniesta), None)
Option
is universally preferred to null
and it gives you a lot of flexibility in how you can safely handle the data. Here's are thee easy ways to get the result you were looking for:
scala> optionPlayers.collect { case Some(s) => s.toUpperCase }
res0: List[java.lang.String] = List(MESSI, XAVI, INIESTA)
scala> optionPlayers.flatMap(_.map(_.toUpperCase))
res1: List[java.lang.String] = List(MESSI, XAVI, INIESTA)
scala> optionPlayers.flatten.map(_.toUpperCase)
res2: List[java.lang.String] = List(MESSI, XAVI, INIESTA)
You can find a lot more information about Option
in other StackOverflow questions or by searching the web.
Or, you can always just define that safeMap
method you wanted as an implicit on List
:
implicit def enhanceList[T](list: List[T]) = new {
def safeMap[R](f: T => R) = list.filterNot(_ == null).map(f)
}
so you can do:
scala> players.safeMap(_.toUpperCase)
res4: List[java.lang.String] = List(MESSI, XAVI, INIESTA)
Though if you define an implicit, you might want to use a CanBuildFrom
style like the basic collections do to make it work on more than just List
. You can find more information about that elsewhere.
Upvotes: 1
Reputation: 9663
If you can’t avoid null
s (e.g. if you get your list from Java code), another alternative is to use collect
instead of map
:
scala> players.collect { case player if player != null => player.toUpperCase }
res0: List[java.lang.String] = List(MESSI, XAVI, INIESTA)
Upvotes: 3