Scala Language
Alternativsklass
Sök…
Syntax
klass Vissa [+ T] (värde: T) utökar Alternativ [T]
objekt Ingen utökar Alternativ [Ingenting]
Alternativ [T] (värde: T)
Konstruktör för att skapa antingen ett
Some(value)
ellerNone
som är lämpligt för det angivna värdet.
Alternativ som samlingar
Option
har några användbara funktioner med högre ordning som lätt kan förstås genom att visa alternativ som samlingar med noll eller ett objekt - där None
uppför sig som den tomma samlingen, och Some(x)
uppför sig som en samling med ett enda objekt, 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
Använda alternativ istället för noll
I Java (och andra språk) är användning av null
ett vanligt sätt att indikera att det inte finns något värde kopplat till en referensvariabel. I Scala föredras att använda Option
framför att använda null
. Option
slår in värden som kan vara null
.
None
är en underklass av Option
innehåller en nollreferens. Some
är en underklass av Option
innehåller en referens som inte är noll.
Att lägga in en referens är lätt:
val nothing = Option(null) // None
val something = Option("Aren't options cool?") // Some("Aren't options cool?")
Detta är en typisk kod när du ringer ett Java-bibliotek som kan returnera en nollreferens:
val resource = Option(JavaLib.getResource())
// if null, then resource = None
// else resource = Some(resource)
Om getResource()
returnerar null
värde resource
kommer att bli en None
objekt. Annars kommer det att vara ett Some(resource)
objekt. Det föredragna sättet att hantera ett Option
är att använda högre ordningsfunktioner tillgängliga inom Option
. Om du till exempel vill kontrollera om ditt värde inte är None
(liknande att kontrollera om value == null
) använder isDefined
funktionen isDefined
:
val resource: Option[Resource] = Option(JavaLib.getResource())
if (resource.isDefined) { // resource is `Some(_)` type
val r: Resource = resource.get
r.connect()
}
På samma sätt kan du göra för att leta efter en null
:
val resource: Option[Resource] = Option(JavaLib.getResource())
if (resource.isEmpty) { // resource is `None` type.
System.out.println("Resource is empty! Cannot connect.")
}
Det föredras att du behandlar villkorad exekvering på det inslagna värdet för ett Option
(utan att använda den "exceptionella" Option.get
metoden) genom att behandla Option
som en monad och använda 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
Om en Resource
krävs (kontra en instans för Option[Resource]
) kan du fortfarande använda Option
att skydda mot nollvärden. Här ger getOrElse
metoden ett standardvärde:
lazy val defaultResource = new Resource()
val resource: Resource = Option(JavaLib.getResource()).getOrElse(defaultResource)
Java-kod hanterar inte lätt Scalas Option
, så när du överför värden till Java-kod är det bra form att ta bort ett Option
, vidarebefordra null
eller ett förnuftigt standard där det är lämpligt:
val resource: Option[Resource] = ???
JavaLib.sendResource(resource.orNull)
JavaLib.sendResource(resource.getOrElse(defaultResource)) //
Grunderna
Ett Option
är en datastruktur som innehåller antingen ett enda värde eller inget värde alls. Ett Option
kan betraktas som samlingar av noll eller ett element.
Alternativet är en abstrakt klass med två barn: Some
och None
.
Some
innehåller ett enda värde, och None
innehåller inget värde.
Option
är användbart i uttryck som annars skulle använda null
att representera bristen på ett konkret värde. Detta skyddar mot en NullPointerException
och tillåter sammansättningen av många uttryck som kanske inte returnerar ett värde med hjälp av kombinatorer som Map
, FlatMap
, etc.
Exempel med karta
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]
är förseglat och kan därför inte förlängas. Därför är det semantik som är stabilt och kan lita på.
Alternativ för förståelser
Option
har en flatMap
metod. Det betyder att de kan användas i en förståelse. På detta sätt kan vi lyfta regelbundna funktioner för att arbeta med Option
utan att behöva omdefiniera dem.
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)
När ett av värdena är ett None
slutresultatet av beräkningen också 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
Obs: detta mönster sträcker sig mer generellt för begrepp som kallas Monad
. (Mer information bör finnas tillgänglig på sidor som rör förståelser och Monad
)
I allmänhet är det inte möjligt att blanda olika monader i en förståelse. Men eftersom Option
kan lätt omvandlas till en Iterable
, kan vi enkelt blanda Option
s och Iterable
s genom att anropa .toIterable
metoden.
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)
En liten anmärkning: om vi hade definierat vårt för att förstå det andra sättet för förståelsen skulle sammanställas eftersom vårt alternativ skulle omvandlas implicit. Av den anledningen är det användbart att lägga till den här .toIterable
(eller motsvarande funktion beroende på vilken samling du använder) för konsistens.