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