[scala基础]--Option/Some/None

本文深入探讨了Scala中Option类型的基本用法,包括Some和None的使用场景,如何通过Option进行错误处理,以及如何结合Scala集合操作来高效地处理可能为空的数据。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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.)








评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

oo寻梦in记

你的鼓励将是我创作的最大动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值