Scala的Option/Some/None操作:
一、環境:Idea 16+Jdk-1.7+Scala-2.10.4
二、測試代碼:
import scala.io.Source
import scala.util.{Try,Success,Failure}
/**
* Document:本類作用---->測試Options、Some、None
* User: yangjf
* Date: 2016/9/25 12:48
*/
object Options {
def main(args: Array[String]) {
//1、擷取操作後的傳回值
println(toInt("23"))
println(toInt("23").get)
println(toInt("abc"))
// println(toInt("abc").get) ---->報錯誤,因為沒有傳回值
val xx = if (toInt("foo").isDefined) toInt("foo") else 0
println("處理結果:"+xx)
//2、Use getOrElse--->如果方法執行成功,則傳回實際值,否則傳回失敗
println(toInt("1").getOrElse(2)) //傳回1
println(toInt("as").getOrElse(2)) //執行失敗,傳回2
// 3、Use foreach
//有傳回值
toInt("1").foreach{ i =>
println(s"Got an int: $i")
}
//沒有傳回值
toInt("abc").foreach{ i =>
println(s"Got an int: $i")
}
//4、Use a match expression
toInt("1") match {
case Some(i) => println(i)
case None => println("That didn't work.")
}
//5、測試過濾不需要的值,傳回List
my_test
//6、一行一行地讀取檔案
println("檔案讀取:"+readTextFile("E:/idea16/my-hbase-test/txt/test.txt"))
println("檔案讀取2:"+readTextFile2("E:/idea16/my-hbase-test/txt/test.txt"))
//7、除法計算産生異常
println("除法産生異常:"+divideXByY(1,3))
println("除法産生異常:"+divideXByY(3,1))
println("除法産生異常:"+divideXByY(3,0))
println("除法産生異常:"+divideXByY(0,3))
val x = divideXByY(1, 1).getOrElse(0)
val y = divideXByY(1, 0).getOrElse(0)
println("x---->"+x)
println("y---->"+y)
divideXByY(1, 1).foreach(println)
divideXByY(1, 0).foreach(println)
divideXByY(1, 1) match {
case Success(i) => println(s"Success, value is: $i")
case Failure(s) => println(s"Failed, message is: $s")
}
//yield操作
val z = for {
a <- Try(x.toInt)
b <- try="" y="" toint="" yield="" a="" b="" val="" answer="z.getOrElse(0)" 2="" println="" answer="" is="" answer="" 8="" println="" dividexbyy2="" 1="" 0="" println="" 2="" dividexbyy2="" 6="" 2="" val="" m="divideXByY2(1," 1="" right="" getorelse="" 0="" returns="" 1="" val="" n="divideXByY2(1," 0="" right="" getorelse="" 0="" returns="" 0="" println="" m="" m="" println="" n="" n="" prints="" answer:="" dude="" can="" t="" divide="" by="" 0="" dividexbyy2="" 1="" 0="" match="" case="" left="" s=""> println("Answer: " + s)
case Right(i) => println("Answer: " + i)
}
}
//Using Option with Scala collections
def my_test()={
//options非常好的特性
val bag = List("1", "2", "foo", "3", "bar")
val res: List[Option[Int]] =bag.map(toInt) //傳回處理後的值
println("res list: "+res)
//可以添加其中的int為map,使用flatten方法
/**
* * val xs = List(
* Set(1, 2, 3),
* Set(1, 2, 3)
* ).flatten
* // xs == List(1, 2, 3, 1, 2, 3)
* val ys = Set(
* List(1, 2, 3),
* List(3, 2, 1)
* ).flatten
* // ys == Set(1, 2, 3)
*/
val ends: List[Int] =res.flatten
println("flatten轉換後的結果:"+ends)
val ends2=bag.flatMap(toInt)
println("flatMap轉換後的結果2:"+ends2)
//collect方法同樣可以實作以上效果
val ends3: List[Int] =bag.map(toInt).collect{case Some(i) => i} //匿名函數忽略了沒有的值
println("collect實作的效果:"+ends3)
}
//Returning an Option from a method
//Getting the value from an Option
def toInt(s: String): Option[Int] = {
try {
Some(Integer.parseInt(s.trim))
} catch {
case e: Exception => None
}
}
//Using Option with other frameworks
// def getAll() : List[Stock] = {
// DB.withConnection { implicit connection =>
// sqlQuery().collect {
// // the 'company' field has a value
// case Row(id: Int, symbol: String, Some(company: String)) =>
// Stock(id, symbol, Some(company))
// // the 'company' field does not have a value
// case Row(id: Int, symbol: String, None) =>
// Stock(id, symbol, None)
// }.toList
// }
// }
// verifying("If age is given, it must be greater than zero",
// model =>
// model.age match {
// case Some(age) => age < 0
// case None => true
// }
// )
import scala.util.control.Exception._
//檔案讀取
def readTextFile(f: String): Option[List[String]] =
allCatch.opt(Source.fromFile(f).getLines.toList) //直接就擷取所有異常資訊
def readTextFile2(filename: String): Try[List[String]] = {
Try(Source.fromFile(filename).getLines.toList)
}
//Using Try, Success, and Failure
import scala.util.{Try,Success,Failure}
def divideXByY(x: Int, y: Int): Try[Int] = {
Try(x / y)
}
//Using Either, Left, and Right
def divideXByY2(x: Int, y: Int): Either[String, Int] = {
if (y == 0) Left("Dude, can't divide by 0")
else Right(x / y)
}
}
三、列印結果:
Some(23)
23
None
處理結果:0
1
2
Got an int: 1
1
res list: List(Some(1), Some(2), None, Some(3), None)
flatten轉換後的結果:List(1, 2, 3)
flatMap轉換後的結果2:List(1, 2, 3)
collect實作的效果:List(1, 2, 3)
檔案讀取:Some(List(12, 23, 23, 45, 34, 56))
檔案讀取2:Success(List(12, 23, 23, 45, 34, 56))
除法産生異常:Success(0)
除法産生異常:Success(3)
除法産生異常:Failure(java.lang.ArithmeticException: / by zero)
除法産生異常:Success(0)
x---->1
y---->0
1
Success, value is: 1
answer is 0
比對結果:Left(Dude, can't divide by 0)
比對結果2:Right(3)
m 1
n 0
Answer: Dude, can't divide by 0
四、對應的英文原文如下:
20.6. Using the Option/Some/None Pattern
Problem
For a variety of reasons, including removing null values from your code, you want to
use what I call the Option / Some / None pattern. Or, if you’re interested in a problem (ex‐
ception) that occurred while processing code, you may want to return Try / Success /
Failure from a method instead of Option / Some / None .
Solution
There is some overlap between this recipe and the previous recipe, “Eliminate null Val‐
ues from Your Code”. That recipe shows how to use Option instead of null in the
following situations:
• Using Option in method and constructor parameters
• Using Option to initialize class fields (instead of using null )
• Converting null results from other code (such as Java code) into an Option
See that recipe for examples of how to use an Option in those situations.
This recipe adds these additional solutions:
• Returning an Option from a method
• Getting the value from an Option
• Using Option with collections
• Using Option with frameworks
• Using Try / Success / Failure when you need the error message (Scala 2.10 and
newer)
• Using Either / Left / Right when you need the error message (pre-Scala 2.10)
Returning an Option from a method
The toInt method used in this book shows how to return an Option from a method. It
takes a String as input and returns a Some[Int] if the String is successfully converted
to an Int , otherwise it returns a None :
def toInt(s: String): Option[Int] = {
try {
Some(Integer.parseInt(s.trim))
} catch {
case e: Exception => None
}
}
Although this is a simple method, it shows the common pattern, as well as the syntax.
For a more complicated example, see the readTextFile example in Recipe 20.5.
This is what toInt looks like in the REPL when it succeeds and returns a Some :
scala> val x = toInt("1")
x: Option[Int] = Some(1)
This is what it looks like when it fails and returns a None :
scala> val x = toInt("foo")
x: Option[Int] = None
Getting the value from an Option
The toInt example shows how to declare a method that returns an Option . As a con‐
sumer of a method that returns an Option , there are several good ways to call it and
access its result:
• Use getOrElse
• Use foreach
• Use a match expression
20.6. Using the Option/Some/None Pattern
To get the actual value if the method succeeds, or use a default value if the method fails,
use getOrElse :
scala> val x = toInt("1").getOrElse(0)
x: Int = 1
Because an Option is a collection with zero or one elements, the foreach method can
be used in many situations:
toInt("1").foreach{ i =>
println(s"Got an int: $i")
}
That example prints the value if toInt returns a Some , but bypasses the println state‐
ment if toInt returns a None .
Another good way to access the toInt result is with a match expression:
toInt("1") match {
case Some(i) => println(i)
case None => println("That didn't work.")
}
Using Option with Scala collections
Another great feature of Option is that it plays well with Scala collections. For instance,
starting with a list of strings like this:
val bag = List("1", "2", "foo", "3", "bar")
imagine you want a list of all the integers that can be converted from that list of strings.
By passing the toInt method into the map method, you can convert every element in
the collection into a Some or None value:
scala> bag.map(toInt)
res0: List[Option[Int]] = List(Some(1), Some(2), None, Some(3), None)
This is a good start. Because an Option is a collection of zero or one elements, you can
convert this list of Int values by adding flatten to map :
scala> bag.map(toInt).flatten
res1: List[Int] = List(1, 2, 3)
As shown in Recipe 10.16, “Combining map and flatten with flatMap”, this is the same
as calling flatMap :
scala> bag.flatMap(toInt)
res2: List[Int] = List(1, 2, 3)
The collect method provides another way to achieve the same result:
scala> bag.map(toInt).collect{case Some(i) => i}
res3: List[Int] = List(1, 2, 3)
That example works because the collect method takes a partial function, and the
anonymous function that’s passed in is only defined for Some values; it ignores the None
values.
These examples work for several reasons:
• toInt is defined to return Option[Int] .
• Methods like flatten , flatMap , and others are built to work well with Option
values.
• You can pass anonymous functions into the collection methods.
Using Option with other frameworks
Once you begin working with third-party Scala libraries, you’ll see that Option is used
to handle situations where a variable may not have a value. For instance, they’re baked
into the Play Framework’s Anorm database library, where you use Option / Some / None
for database table fields that can be null . In the following example, the third field may
be null in the database, so it’s handled using Some and None , as shown:
def getAll() : List[Stock] = {
DB.withConnection { implicit connection =>
sqlQuery().collect {
// the 'company' field has a value
case Row(id: Int, symbol: String, Some(company: String)) =>
Stock(id, symbol, Some(company))
// the 'company' field does not have a value
case Row(id: Int, symbol: String, None) =>
Stock(id, symbol, None)
}.toList
}
}
The Option approach is also used extensively in Play validation methods:
verifying("If age is given, it must be greater than zero",
model =>
model.age match {
case Some(age) => age < 0
case None => true
}
)
The scala.util.control.Exception object gives you another way to
use an Option , depending on your preferences and needs. For in‐
stance, the try / catch block was removed from the following method
and replaced with an allCatch method:
import scala.util.control.Exception._
def readTextFile(f: String): Option[List[String]] =
allCatch.opt(Source.fromFile(f).getLines.toList)
allCatch is described as a Catch object “that catches everything.” The
opt method returns None if an exception is caught (such as a
FileNotFoundException ), and a Some if the block of code succeeds.
Other allCatch methods support the Try and Either approaches. See
the Exception object Scaladoc for more information.
If you like the Option / Some / None approach, but want to write a method that returns
error information in the failure case (instead of None , which doesn’t return any error
information), there are two similar approaches:
• Try , Success , and Failure (introduced in Scala 2.10)
• Either , Left , and Right
I prefer the new Try / Success / Failure approach, so let’s look at it next.
Using Try, Success, and Failure
Scala 2.10 introduced scala.util.Try as an approach that’s similar to Option , but re‐
turns failure information rather than a None .
The result of a computation wrapped in a Try will be one of its subclasses: Success or
Failure . If the computation succeeds, a Success instance is returned; if an exception
was thrown, a Failure will be returned, and the Failure will hold information about
what failed.
To demonstrate this, first import the new classes:
import scala.util.{Try,Success,Failure}
Then create a simple method:
def divideXByY(x: Int, y: Int): Try[Int] = {
Try(x / y)
}
This method returns a successful result as long as y is not zero. When y is zero, an
ArithmeticException happens. However, the exception isn’t thrown out of the method;
it’s caught by the Try , and a Failure object is returned from the method.
The method looks like this in the REPL:
scala> divideXByY(1,1)
res0: scala.util.Try[Int] = Success(1)
scala> divideXByY(1,0)
res1: scala.util.Try[Int] = Failure(java.lang.ArithmeticException: / by zero)
As with an Option , you can access the Try result using getOrElse , a foreach method,
or a match expression. If you don’t care about the error message and just want a result,
use getOrElse :
// Success
scala> val x = divideXByY(1, 1).getOrElse(0)
x: Int = 1
// Failure
scala> val y = divideXByY(1, 0).getOrElse(0)
y: Int = 0
Using a foreach method also works well in many situations:
scala> divideXByY(1, 1).foreach(println)
1
scala> divideXByY(1, 0).foreach(println)
(no output printed)
If you’re interested in the Failure message, one way to get it is with a match expression:
divideXByY(1, 1) match {
case Success(i) => println(s"Success, value is: $i")
case Failure(s) => println(s"Failed, message is: $s")
}
Another approach is to see if a Failure was returned, and then call its toString method
(although this doesn’t really follow the “Scala way”):
scala> if (x.isFailure) x.toString
res0: Any = Failure(java.lang.ArithmeticException: / by zero)
The Try class has the added benefit that you can chain operations together, catching
exceptions as you go. For example, the following code won’t throw an exception, re‐
gardless of what the values of x and y are:
val z = for {
a <- Try(x.toInt)
b <- try="" y="" toint="" yield="" a="" b="" val="" answer="z.getOrElse(0)" 2="" if="" x="" and="" y="" are="" string="" values="" like="" 1="" and="" 2="" this="" code="" works="" as="" expected="" with="" answer="" resulting="" in="" an="" int="" value="" if="" x="" or="" y="" is="" a="" string="" that="" can="" t="" be="" converted="" to="" an="" int="" z="" will="" have="" this="" value:="" z:="" scala="" util="" try="" int="" failure="" java="" lang="" numberformatexception:="" for="" input="" string:="" one="" if="" x="" or="" y="" is="" null="" z="" will="" have="" this="" value:="" z:="" scala="" util="" try="" int="" failure="" java="" lang="" numberformatexception:="" null="" in="" either="" failure="" case="" the="" getorelse="" method="" protects="" us="" returning="" the="" default="" value="" of="" 0="" the="" readtextfile="" method="" in="" recipe="" 20="" 5="" shows="" another="" try="" example="" the="" method="" from="" that="" example="" is="" repeated="" here:="" def="" readtextfile="" filename:="" string="" :="" try="" list="" string="" try="" source="" fromfile="" filename="" getlines="" tolist="" if="" the="" readtextfile="" method="" runs="" successfully="" the="" lines="" from="" the="" etc="" passwd="" file="" are="" printed="" but="" if="" an="" exception="" happens="" while="" trying="" to="" open="" and="" read="" the="" file="" the="" failure="" line="" in="" the="" match="" expression="" prints="" the="" error="" like="" this:="" java="" io="" filenotfoundexception:="" foo="" bar="" no="" such="" file="" or="" directory="" the="" try="" class="" includes="" a="" nice="" collection="" of="" methods="" that="" let="" you="" handle="" situations="" in="" many="" ways="" including:="" collection-like="" implementations="" of="" filter="" flatmap="" flatten="" foreach="" and="" map="" get="" getorelse="" and="" orelse="" tooption="" which="" lets="" you="" treat="" the="" result="" as="" an="" option="" recover="" recoverwith="" and="" transform="" which="" let="" you="" gracefully="" handle="" success="" and="" failure="" results="" as="" you="" can="" see="" try="" is="" a="" powerful="" alternative="" to="" using="" option="" some="" none="" using="" either="" left="" and="" right="" prior="" to="" scala="" 2="" 10="" an="" approach="" similar="" to="" try="" was="" available="" with="" the="" either="" left="" and="" right="" classes="" with="" these="" classes="" either="" is="" analogous="" to="" try="" right="" is="" similar="" to="" success="" and="" left="" is="" similar="" to="" failure="" the="" following="" method="" demonstrates="" how="" to="" implement="" the="" either="" approach:="" def="" dividexbyy="" x:="" int="" y:="" int="" :="" either="" string="" int="" if="" y="=" 0="" left="" dude="" can="" t="" divide="" by="" 0="" else="" right="" x="" y="" as="" shown="" your="" method="" should="" be="" declared="" to="" return="" an="" either="" and="" the="" method="" body="" should="" return="" a="" right="" on="" success="" and="" a="" left="" on="" failure="" the="" right="" type="" is="" the="" type="" your="" method="" returns="" when="" it="" runs="" successfully="" an="" int="" in="" this="" case="" and="" the="" left="" type="" is="" typically="" a="" string="" because="" that="" s="" how="" the="" error="" message="" is="" returned="" as="" with="" option="" and="" try="" a="" method="" returning="" an="" either="" can="" be="" called="" in="" a="" variety="" of="" ways="" including="" getorelse="" or="" a="" match="" expression:="" val="" x="divideXByY(1," 1="" right="" getorelse="" 0="" returns="" 1="" val="" x="divideXByY(1," 0="" right="" getorelse="" 0="" returns="" 0="" prints="" answer:="" dude="" can="" t="" divide="" by="" 0="" dividexbyy="" 1="" 0="" match="" case="" left="" s=""> println("Answer: " + s)
case Right(i) => println("Answer: " + i)
}
You can also access the error message by testing the result with isLeft , and then ac‐
cessing the left value, but this isn’t really the Scala way:
scala> val x = divideXByY(1, 0)
x: Either[String,Int] = Left(Dude, can't divide by 0)
scala> x.isLeft
res0: Boolean = true
scala> x.left
res1: scala.util.Either.LeftProjection[String,Int] =
LeftProjection(Left(Dude, can't divide by 0))
Although the Either classes offered a potential solution prior to Scala 2.10, I now use
the Try classes in all of my code instead of Either .
Discussion
As shown in the Solution, if there’s a weakness of using Option , it’s that it doesn’t tell
you why something failed; you just get a None instead of a Some . If you need to know
why something failed, use Try instead of Option .
Don’t use the get method with Option
When you first come to Scala from Java, you may be tempted to use the get method to
access the result:
scala> val x = toInt("5").get
x: Int = 5
However, this isn’t any better than a NullPointerException :
scala> val x = toInt("foo").get
java.util.NoSuchElementException: None.get
// long stack trace omitted ...
Your next thought might be to test the value before trying to access it:
// don't do this
scala> val x = if (toInt("foo").isDefined) toInt("foo") else 0
x: Any = 0
As the comment says, don’t do this. In short, it’s a best practice to never call get on an
Option . The preferred approaches are to use getOrElse , a match expression, or
foreach . (As with null values, I just imagine that get doesn’t exist.)