Scala Language
Класс опций
Поиск…
Синтаксис
class Some [+ T] (значение: T) расширяет опцию [T]
объект None extends Option [Nothing]
Опция [T] (значение: T)
Конструктор создает либо
Some(value)
либоNone
если это необходимо для предоставленного значения.
Варианты в виде коллекций
Option
s имеет некоторые полезные функции более высокого порядка, которые можно легко понять, просматривая параметры как коллекции с нулевым или одним элементом - где None
ведет себя как пустая коллекция, а Some(x)
ведет себя как коллекция с одним элементом x
.
val option: Option[String] = ???
option.map(_.trim) // None if option is None, Some(s.trim) if Some(s)
option.foreach(println) // prints the string if it exists, does nothing otherwise
option.forall(_.length > 4) // true if None or if Some(s) and s.length > 4
option.exists(_.length > 4) // true if Some(s) and s.length > 4
option.toList // returns an actual list
Использование опции вместо Null
В Java (и других языках) использование null
является обычным способом указания того, что для ссылочной переменной не существует значения. В Scala использование Option
предпочтительнее использования null
. Option
обнуляет значения, которые могут быть null
.
None
является подклассом Option
обменивает нулевую ссылку. Some
них являются подклассом Option
обертывающей ненулевую ссылку.
Облегчение ссылки легко:
val nothing = Option(null) // None
val something = Option("Aren't options cool?") // Some("Aren't options cool?")
Это типичный код при вызове библиотеки Java, который может возвращать нулевую ссылку:
val resource = Option(JavaLib.getResource())
// if null, then resource = None
// else resource = Some(resource)
Если getResource()
возвращает null
значение, resource
будет объектом None
. В противном случае это будет объект Some(resource)
. Предпочтительным способом обработки Option
является использование функций более высокого порядка, доступных в типе Option
. Например, если вы хотите проверить, не является ли ваше значение None
(аналогично проверке, если value == null
), вы должны использовать функцию isDefined
:
val resource: Option[Resource] = Option(JavaLib.getResource())
if (resource.isDefined) { // resource is `Some(_)` type
val r: Resource = resource.get
r.connect()
}
Аналогично, для проверки null
ссылки вы можете сделать это:
val resource: Option[Resource] = Option(JavaLib.getResource())
if (resource.isEmpty) { // resource is `None` type.
System.out.println("Resource is empty! Cannot connect.")
}
Предпочтительно, чтобы вы лечить условное выполнение на обернутом значении в Option
(без использования «» исключительного Option.get
метода) путем обработки Option
как монада и используя foreach
:
val resource: Option[Resource] = Option(JavaLib.getResource())
resource foreach (r => r.connect())
// if r is defined, then r.connect() is run
// if r is empty, then it does nothing
Если требуется экземпляр Resource
(по сравнению с экземпляром Option[Resource]
), вы все равно можете использовать Option
для защиты от нулевых значений. Здесь метод getOrElse
предоставляет значение по умолчанию:
lazy val defaultResource = new Resource()
val resource: Resource = Option(JavaLib.getResource()).getOrElse(defaultResource)
Java - код не будет легко обращаться в Scala Option
, поэтому при передаче значений в Java коде это хорошая форма разворачивать в Option
, передавая null
или толкового по умолчанию в соответствующих случаях:
val resource: Option[Resource] = ???
JavaLib.sendResource(resource.orNull)
JavaLib.sendResource(resource.getOrElse(defaultResource)) //
основы
Option
представляет собой структуру данных, которая содержит либо одно значение, либо вообще не имеет значения. Option
можно рассматривать как коллекции нулевого или одного элемента.
Вариант - абстрактный класс с двумя детьми: Some
и None
.
Some
содержат одно значение, а None
содержит значения.
Option
полезна в выражениях, которые в противном случае использовали бы null
для представления отсутствия конкретного значения. Это защищает от NullPointerException
и позволяет создавать множество выражений, которые могут не возвращать значение с использованием комбинаторов, таких как Map
, FlatMap
и т. Д.
Пример с картой
val countries = Map(
"USA" -> "Washington",
"UK" -> "London",
"Germany" -> "Berlin",
"Netherlands" -> "Amsterdam",
"Japan" -> "Tokyo"
)
println(countries.get("USA")) // Some(Washington)
println(countries.get("France")) // None
println(countries.get("USA").get) // Washington
println(countries.get("France").get) // Error: NoSuchElementException
println(countries.get("USA").getOrElse("Nope")) // Washington
println(countries.get("France").getOrElse("Nope")) // Nope
Option[A]
запечатана и, следовательно, не может быть расширена. Поэтому семантика стабильна и на нее можно положиться.
Варианты для понимания
Option
s имеет метод flatMap
. Это означает, что они могут использоваться для понимания. Таким образом, мы можем поднять регулярные функции для работы с Option
s без необходимости их переопределения.
val firstOption: Option[Int] = Option(1)
val secondOption: Option[Int] = Option(2)
val myResult = for {
firstValue <- firstOption
secondValue <- secondOption
} yield firstValue + secondValue
// myResult: Option[Int] = Some(3)
Когда одно из значений равно None
конечный результат вычисления также будет None
.
val firstOption: Option[Int] = Option(1)
val secondOption: Option[Int] = None
val myResult = for {
firstValue <- firstOption
secondValue <- secondOption
} yield firstValue + secondValue
// myResult: Option[Int] = None
Примечание. Этот шаблон распространяется в более общем плане на понятия, называемые Monad
s. (Дополнительная информация должна быть доступна на страницах, относящихся к пониманию и Monad
s)
В общем случае невозможно смешивать разные монады в понимании. Но поскольку Option
можно легко преобразовать в Iterable
, мы можем легко смешать Option
s и Iterable
s, вызвав метод .toIterable
.
val option: Option[Int] = Option(1)
val iterable: Iterable[Int] = Iterable(2, 3, 4, 5)
// does NOT compile since we cannot mix Monads in a for comprehension
// val myResult = for {
// optionValue <- option
// iterableValue <- iterable
//} yield optionValue + iterableValue
// It does compile when adding a .toIterable on the option
val myResult = for {
optionValue <- option.toIterable
iterableValue <- iterable
} yield optionValue + iterableValue
// myResult: Iterable[Int] = List(2, 3, 4, 5)
Небольшая заметка: если бы мы определили наш подход к пониманию, то другой путь вокруг понимания будет скомпилирован, поскольку наш вариант будет преобразован неявно. По этой причине полезно всегда добавлять эту .toIterable
(или соответствующую функцию в зависимости от того, какую коллекцию вы используете) для согласованности.