Scala Language
Opzione Classe
Ricerca…
Sintassi
class Alcuni [+ T] (valore: T) estende l'opzione [T]
oggetto Nessuno estende l'opzione [Nothing]
Opzione [T] (valore: T)
Costruttore per creare un
Some(value)oNonesecondo il valore fornito.
Opzioni come raccolte
Option hanno alcune utili funzioni di ordine superiore che possono essere facilmente comprese visualizzando opzioni come raccolte con zero o un elemento - dove None si comporta come la raccolta vuota, e Some(x) si comporta come una raccolta con un singolo elemento, 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
Utilizzo dell'opzione anziché di null
In Java (e in altre lingue), l'utilizzo di null è un modo comune per indicare che non esiste alcun valore collegato a una variabile di riferimento. In Scala, l'uso Option è preferito rispetto all'utilizzo di null . Option include valori che potrebbero essere null .
None è una sottoclasse di Option racchiude un riferimento null. Some è una sottoclasse di Option racchiude un riferimento non nullo.
Avvolgere un riferimento è facile:
val nothing = Option(null) // None
val something = Option("Aren't options cool?") // Some("Aren't options cool?")
Questo è un codice tipico quando si chiama una libreria Java che potrebbe restituire un riferimento null:
val resource = Option(JavaLib.getResource())
// if null, then resource = None
// else resource = Some(resource)
Se getResource() restituisce un valore null , la resource sarà un oggetto None . Altrimenti sarà un Some(resource) oggetto Some(resource) . Il modo migliore per gestire Option è l'utilizzo di funzioni di ordine superiore disponibili all'interno del tipo di Option . Ad esempio, se si desidera verificare se il proprio valore non è None (simile alla verifica del value == null ), si utilizzerà la funzione isDefined :
val resource: Option[Resource] = Option(JavaLib.getResource())
if (resource.isDefined) { // resource is `Some(_)` type
val r: Resource = resource.get
r.connect()
}
Allo stesso modo, per verificare la presenza di un riferimento null puoi farlo:
val resource: Option[Resource] = Option(JavaLib.getResource())
if (resource.isEmpty) { // resource is `None` type.
System.out.println("Resource is empty! Cannot connect.")
}
È preferibile che si tratti l'esecuzione condizionale sul valore avvolto di Option (senza utilizzare il metodo 'eccezionale' Option.get ) trattando l' Option come monade e utilizzando 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
Se è richiesta un'istanza Resource (rispetto a un'istanza Option[Resource] , è comunque possibile utilizzare Option per proteggere da valori null. Qui il metodo getOrElse fornisce un valore predefinito:
lazy val defaultResource = new Resource()
val resource: Resource = Option(JavaLib.getResource()).getOrElse(defaultResource)
Il codice Java non gestirà prontamente l' Option di Scala, quindi quando si passano i valori al codice Java è buona norma scartare Option , passando a un valore null o ad un valore predefinito appropriato, ove appropriato:
val resource: Option[Resource] = ???
JavaLib.sendResource(resource.orNull)
JavaLib.sendResource(resource.getOrElse(defaultResource)) //
Nozioni di base
Option è una struttura di dati che contiene un singolo valore o nessun valore. Option può essere pensata come una raccolta di zero o di un elemento.
L'opzione è una classe astratta con due figli: Some e None .
Some contengono un singolo valore e None non contiene alcun valore.
Option è utile in espressioni che altrimenti utilizzerebbero null per rappresentare la mancanza di un valore concreto. Questo protegge contro una NullPointerException e consente la composizione di molte espressioni che potrebbero non restituire un valore utilizzando combinatori come Map , FlatMap , ecc.
Esempio con la mappa
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] è sigillata e quindi non può essere estesa. Quindi la semantica è stabile e può essere invocata.
Opzioni per le comprensioni
Option s hanno un metodo flatMap . Ciò significa che possono essere utilizzati in a comprensione. In questo modo possiamo sollevare funzioni regolari per lavorare su Option s senza doverle ridefinire.
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)
Quando uno dei valori è un None il risultato finale del calcolo sarà 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
Nota: questo modello si estende più in generale per i concetti denominati Monad s. (Ulteriori informazioni dovrebbero essere disponibili sulle pagine relative a comprensione e Monad s)
In generale non è possibile mescolare diverse monadi in a comprensione. Ma poiché Option può essere facilmente convertita in Iterable , possiamo facilmente mescolare Option s e Iterable chiamando il metodo .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)
Una piccola nota: se avessimo definito la nostra comprensione, l'altro modo di comprensione sarebbe stato compilato poiché la nostra opzione sarebbe stata convertita implicitamente. Per questo motivo è utile aggiungere sempre questa .toIterable (o la funzione corrispondente a seconda della raccolta che si sta utilizzando) per coerenza.