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)
oNone
secondo 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.