Scalar에는 Option Class가 존재한다.
이 클래스는 Java언어에서 특정 Method가 Null을 return할 경우, 매번 확인해야하는 Bolier plate 코드를 계속 삽입해야하는 불편함과 Null dereference오류를 방지하기 위해 고안된 클래스다.
이 클래스를 설명한 Scalar Doc을 참고하면
"Represents option values, Instances of Option are either an instance of Some or the Object None.
The Most idiomatic way to use an Option instance is to treat it as a collection or monad and use map, flatMap, filter, or foreach"
설명에 Option 인스턴스는 collection이나 monad로 취급하여 map, flatMap, filter 혹은 foreach를 사용하라는 것이다.
유용한 method인 map을 살펴보기전에 먼저 get, getOrElse 라는 method를 먼저 살펴보아야겠다.
먼저 get을 살펴보자. 아래의 scalar interpreter shell에서 테스트를 아래와 같이 해보자
scala> val opt =
| Some(1)
opt: Some[Int] = Some(1)
scala> opt.get
res0: Int = 1
scala> val none = None
none: None.type = None
scala> none.get
java.util.NoSuchElementException: None.get
at scala.None$.get(Option.scala:347)
... 48 elided
scala>
위와 같이 get method는 특정 Option의 인스턴스가 None값을 가지고 있을 경우 NoSuchElementException을 발생시킨다.
그러므로 get method를 사용하기에 앞서 isEmpty, isDefined라는 메소드로 값이 None이 아닌지를 검사해야한다.
하지만 getOrElse를 사용할 경우, get으로 값을 가져올 수 없을 경우, default값을 지정할 수 있으므로 NosuchElementExcption을 발생시키지 않을 수 있다. 그러므로 상황에 따라 유연하게 get 과 getOrElse를 잘 사용하는 것이 유리하다.
scala> opt.getOrElse(2)
res2: Int = 1
scala> none.getOrElse(2)
res3: Int = 2
map은 보통 Option 인스턴스안에 들어가는 값이 특정 클래스의 인스턴스인 경우, 인스턴스의 필드값을 바로 접근하기 위해서 많이 쓰인다.
예를 들어보면 아래와 같이 Person이라는 case class를 정의하고 Option class의 Some을 이용하여 optPerson을 나타낸다.
scala> case class Person(age:Int, gender:String)
defined class Person
scala> val optPerson = Some(new Person(17, "male"))
optPerson: Some[Person] = Some(Person(17,male))
그리고 아래와 같이 코드를 작성할 경우 문제가 생길 수 있다. 왜냐하면 Persons는 None도 가지고 있기 때문이다.
scala> val persons = Array(Some(new Person(5, "femail")), None, Some(new Person(28, "female")))
persons: Array[Option[Person]] = Array(Some(Person(5,female)), None, Some(Person(28,female)))
scala> persons.foreach{p=>println(p.get)}
Person(5,female)
java.util.NoSuchElementException: None.get
at scala.None$.get(Option.scala:347)
at scala.None$.get(Option.scala:345)
at $anonfun$1.apply(<console>:28)
at $anonfun$1.apply(<console>:28)
at scala.collection.IndexedSeqOptimized$class.foreach(IndexedSeqOptimized.scala:33)
at scala.collection.mutable.ArrayOps$ofRef.foreach(ArrayOps.scala:186)
... 48 elided
하지만 아래와 같이 코드를 작성하면 안전한 코드가 된다. persons에 있는 None값은 자동으로 처리가 된다.
scala> persons.foreach{p=>p.map(println)}
Person(5,femail)
Person(28,female)
Option을 사용할 때 get, getOrElse보다 map 이 더 낫다.
값을 Option 컬렉션에 감싸서 반환하는 함수는 그 함수가 입력값에 적용되지 않을 수도 있으며, 그에 따라 유요한 결과를 반환하지 못할 수도 있음을 의미한다.
Option은 함수 결과를 처리하는 데 타입에 안전한(type-safe) 방식을 제공하며, 이 방식이 누락된 값을 의미하는 널 값을 반환하는 자바 표준보다 더 안전하다.
스칼라 컬렉션은 이렇게 Option 타입을 사용하여 빈 컬렉션이 발생하는 상황을 처리하는 안전한 연산을 제공할 수 있다. 예를 들어, head 연산이 비어 있지 않은 리스트에 동작하지만, 빈 리스트에 대해서는 에러를 발생시킨다. 더 안전한 방식은 빈 리스트에도 동작할 수 있도록 Option에 헤드 요소를 감싸서 반환하는 headOption을 사용하는 것이다
scala> val evens = odds filter(_%2==0)
evens: List[Int] = List()
scala> val firstEven = evens.headOption
<console>:11: error: not found: value events
val firstEven = evens.headOption
^
scala> val firstEven = evens.headOption
firstEven: Option[Int] = None
scala> val words = List("risivle", scavenger", "gist")
words: List[String] = List(risivle, scavenger, gist)
scala> val uppercase = words.find(w => w == w.toLowerCase)
lowercase: Option[String] = Some(risivle)
lowercase.fold(x)(x=>x)
res0:String = risivle
어떤 의미에서는 우리 리스트 축소 연산을 사용하여 컬렉션을 단일 Option으로 축소하였다.
하지만 Option 자체가 컬렉션이기 때문에 계속해서 이를 변환할 수 있다.
예를 들어, 우리는 filter와 map을 사용하여 'lowercase'결과를 값을 유지하는 방식으로, 또는 그 값을 잃어버리는 방식으로도 변환할 수 있다.
각 연산은 타입에 안전하며 널 포인터 예외를 일으키지 않을 것이다.
모나딕 컬렉션으로서의 Option의 일련의 연산에서 안전하게 실행될 수 있는 하나의 단위를 제공한다.
연산은 현재 값(Some)에 적용되고 누락된 값(None)에는 적용되지 않지만, 결과 타입은 여전히 마지막 연산의 타입과 일치 할 것이다
Option으로부터 값 추출하기
scala> def nextOption = if (util.Random.nextInt > 0) Some(1) else None
nextOption: Option[Int]
scala> val a = nextOption
a: Option[Int] = None
scala> val b = nextOption
b: Option[Int] = None
scala> b getOrElse { println("Error!!"); -1 }
Error!!
res12: Int = -1
안전한 Option 추출
이름 | 예제 | 설명 |
fold | nextOption.fold(-1)(x => x) | Some 인 경우 주어진 함수로부터 추출한 값을, None인 경우 시작값을 반환함. foldLeft, foldRight, reduceXXX 메소드로도 Option을 그 내장된 값이 아니면 계산된 값으로 축소할 수 있음. |
getOrElse | nextOption getOrElse 5 또는 nextOption getOrElse { println("error!"); -1 } | Some의 값을 반환하거나 아니면 이름 매개변수의 결과를 반환함. |
orElse | nextOption orElse nextOption | 실제로 값을 추출하지는 않지만, None인 경우 값을 채우려 함. Option이 비어있지 않으면 Option을 반환하고, 그렇지 않은 경우 주어진 이름 매개변수로 부터 Option을 반환함. |
매치 표현식 | nextOption match { case Some(x) => x; case None => -1 } | 값이 있는 경우 매치 표현식을 사용하여 그 값을 처리함. Some(x) 표현식은 그 데이터를 추출하여 매치 표현식의 결괏값으로 사용되거나 또 다른 변환에 재사용할 수 있는 지정된 값 'x'에 넣음 |
잠재적인 값을 위한 범용적인 모나딕 컬렉션은 Option은 그 타입 매개변수에서 지정한 모든 타입의 값을 포함할 수 있다