Scala Language
Klasa opcji
Szukaj…
Składnia
klasa Niektóre [+ T] (wartość: T) rozszerza opcję [T]
obiekt Brak rozszerza Opcja [Nic]
Opcja [T] (wartość: T)
Konstruktor, aby utworzyć wartość
Some(value)
lubNone
stosownie do podanej wartości.
Opcje jako kolekcje
Option
mają kilka użytecznych funkcji wyższego rzędu, które można łatwo zrozumieć, przeglądając opcje jako kolekcje z zerową lub jedną pozycją - gdzie None
zachowuje się jak pusta kolekcja, a Some(x)
zachowują się jak kolekcja z pojedynczą pozycją, 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
Korzystanie z opcji zamiast zera
W Javie (i innych językach) użycie null
jest powszechnym sposobem wskazania, że do zmiennej referencyjnej nie przypisano żadnej wartości. W Scali użycie Option
jest lepsze niż użycie null
. Option
zawija wartości, które mogą być null
.
None
jest podklasą Option
zawierającą odwołanie zerowe. Some
są podklasą Option
zawierającą odwołanie inne niż null.
Zawijanie referencji jest łatwe:
val nothing = Option(null) // None
val something = Option("Aren't options cool?") // Some("Aren't options cool?")
Jest to typowy kod podczas wywoływania biblioteki Java, która może zwrócić odwołanie zerowe:
val resource = Option(JavaLib.getResource())
// if null, then resource = None
// else resource = Some(resource)
Jeśli getResource()
zwróci wartość null
, resource
będzie obiektem None
. W przeciwnym razie będzie to obiekt Some(resource)
. Preferowanym sposobem obsługi Option
jest użycie funkcji wyższego rzędu dostępnych w ramach typu Option
. Na przykład, jeśli chcesz sprawdzić, czy twoja wartość nie jest None
(podobnie do sprawdzania, czy value == null
), skorzystasz z funkcji isDefined
:
val resource: Option[Resource] = Option(JavaLib.getResource())
if (resource.isDefined) { // resource is `Some(_)` type
val r: Resource = resource.get
r.connect()
}
Podobnie, aby sprawdzić null
odwołanie, możesz to zrobić:
val resource: Option[Resource] = Option(JavaLib.getResource())
if (resource.isEmpty) { // resource is `None` type.
System.out.println("Resource is empty! Cannot connect.")
}
Preferowane jest traktowanie wykonania warunkowego na opakowanej wartości Option
(bez użycia „wyjątkowej” metody Option.get
), traktując Option
jako monadę i używając 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
Jeśli wymagana jest instancja Resource
(w porównaniu z instancją Option[Resource]
), można nadal używać Option
celu ochrony przed wartościami zerowymi. Tutaj metoda getOrElse
podaje wartość domyślną:
lazy val defaultResource = new Resource()
val resource: Resource = Option(JavaLib.getResource()).getOrElse(defaultResource)
Kod Java nie poradzi sobie łatwo z Option
Scali, więc przy przekazywaniu wartości do kodu Java dobrym rozwiązaniem jest rozpakowanie Option
, przekazanie null
lub rozsądne ustawienie domyślne, w stosownych przypadkach:
val resource: Option[Resource] = ???
JavaLib.sendResource(resource.orNull)
JavaLib.sendResource(resource.getOrElse(defaultResource)) //
Podstawy
Option
to struktura danych, która zawiera albo pojedynczą wartość, albo w ogóle jej nie ma. Option
można traktować jako zbiór zer lub jednego elementu.
Opcja to klasa abstrakcyjna z dwójką dzieci: Some
i None
.
Some
zawierają jedną wartość, a None
nie zawiera żadnej wartości.
Option
jest przydatna w wyrażeniach, które w innym przypadku null
do przedstawienia braku konkretnej wartości. Chroni to przed NullPointerException
i umożliwia składanie wielu wyrażeń, które mogą nie zwracać wartości za pomocą kombinacji, takich jak Map
, FlatMap
itp.
Przykład z mapą
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]
jest zapieczętowana i dlatego nie można jej rozszerzyć. Dlatego jego semantyka jest stabilna i można na niej polegać.
Opcje do zrozumienia
Option
mają metodę flatMap
. Oznacza to, że można je wykorzystać do zrozumienia. W ten sposób możemy podnieść zwykłe funkcje do pracy na Option
bez konieczności ich ponownego definiowania.
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)
Gdy jedną z wartości jest None
końcowym wynikiem obliczeń będzie również 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
Uwaga: wzorzec ten rozciąga się bardziej ogólnie na pojęcia zwane Monad
. (Więcej informacji powinno być dostępnych na stronach związanych ze zrozumieniem i Monad
)
Zasadniczo nie jest możliwe mieszanie różnych monad w celu zrozumienia. Ale ponieważ Option
może być łatwo przekształcony w Iterable
, możemy łatwo mieszać Option
s i Iterable
s wywołując .toIterable
metody.
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)
Mała uwaga: gdybyśmy zdefiniowali nasze do zrozumienia, kompilacja odwrotna doszłaby do skutku, ponieważ nasza opcja byłaby niejawnie przekształcona. Z tego powodu warto zawsze dodawać .toIterable
(lub odpowiednią funkcję w zależności od .toIterable
kolekcji) w celu zachowania spójności.