I getter Java 8 dovrebbero restituire il tipo opzionale?


288

Optional il tipo introdotto in Java 8 è una novità per molti sviluppatori.

Un metodo getter che restituisce il Optional<Foo>tipo al posto del classico è Foouna buona pratica? Supponiamo che il valore possa essere null.


8
Anche se è probabile che ciò attragga risposte supposte, è una buona domanda. Non vedo l'ora di una risposta con fatti reali sull'argomento.
Giustino,

8
La domanda è se la nullablitity sia inevitabile. Un componente può avere una proprietà che può essere nulla, ma il programmatore che utilizza quel componente potrebbe decidere di mantenere rigorosamente tale proprietà null. Quindi il programmatore non dovrebbe occuparsene Optional. O, in altre parole, nullrappresenta davvero l'assenza di un valore come con il risultato di una ricerca (dove Optionalè appropriato) o è nullsolo un membro dell'insieme di valori possibili.
Holger,

1
Si veda anche la discussione sulle @NotNullannotazioni: stackoverflow.com/q/4963300/873282
koppor

Risposte:


517

Certo, le persone faranno ciò che vogliono. Ma avevamo un'intenzione chiara quando si aggiungeva questa funzione, e non doveva essere uno scopo generale, forse tipo, tanto quanto a molte persone sarebbe piaciuto che lo facessimo. La nostra intenzione era quella di fornire un meccanismo limitato per i tipi di restituzione del metodo di libreria in cui doveva esserci un modo chiaro per rappresentare "nessun risultato", e l'utilizzo nullper tali era estremamente probabile che causasse errori.

Ad esempio, probabilmente non dovresti mai usarlo per qualcosa che restituisce una matrice di risultati o un elenco di risultati; restituisce invece un array o un elenco vuoto. Non dovresti quasi mai usarlo come campo di qualcosa o come parametro di metodo.

Penso che usarlo di routine come valore di ritorno per i getter sarebbe sicuramente un uso eccessivo.

Non c'è nulla di sbagliato in Opzionale che dovrebbe essere evitato, non è proprio quello che molte persone desiderano che fosse, e di conseguenza eravamo abbastanza preoccupati per il rischio di un uso eccessivo zelante.

(Annuncio di servizio pubblico: MAI chiamare a Optional.getmeno che non sia possibile dimostrare che non sarà mai nullo; utilizzare invece uno dei metodi sicuri come orElseo ifPresent. A posteriori, avremmo dovuto chiamare getqualcosa di simile getOrElseThrowNoSuchElementExceptiono qualcosa che rendesse molto più chiaro che si trattava di un metodo altamente pericoloso questo ha minato Optionalin primo luogo l'intero scopo . Lezione appresa (AGGIORNAMENTO: Java 10 ha Optional.orElseThrow(), che è semanticamente equivalente a get(), ma il cui nome è più appropriato.))


24
(Riguardo all'ultima parte) ... e quando siamo fiduciosi, che il valore non è mai nullche potremmo usare orElseThrow(AssertionError::new), ahem o orElseThrow(NullPointerException::new)...
Holger,

26
Cosa avresti fatto diversamente se la tua intenzione fosse stata quella di introdurre un tipo Forse o Qualcosa di generale? Esiste un modo in cui Opzionale non rientra nel conto o è solo che l'introduzione di Optionals in una nuova API lo renderebbe non Java?
David Moles,

22
Per "tipo per scopi generici", intendevo integrarlo nel sistema di tipi di linguaggio, piuttosto che fornire una classe di libreria che lo approssimasse. (Alcune lingue hanno tipi per T? (T o null) e T! (T non annullabile)) Opzionale è solo una classe; non possiamo fare conversioni implicite tra Foo e <Foo> opzionale come avremmo potuto avere con il supporto linguistico.
Brian Goetz,

11
Mi sono chiesto per un po 'perché l'enfasi nel mondo Java fosse su Opzionale e non meglio sull'analisi statica. Opzionale ha alcuni vantaggi, ma l'enorme vantaggio che nullha è la compatibilità con le versioni precedenti; Map::getrestituisce un nullable V, non un Optional<V>, e non cambierà mai. @NullableTuttavia, avrebbe potuto essere facilmente annotato . Ora abbiamo due modi per esprimere la mancanza di valore, oltre a meno incentivi per ottenere realmente l'analisi statica, che sembra una posizione peggiore in cui trovarsi.
yshavit,

21
Non avevo idea che usarlo come valore di ritorno per una proprietà non fosse quello che tutti intendevi fino a quando non ho letto questa risposta qui su StackOverflow. In effetti, fui indotto a credere che fosse esattamente quello che tutti intendevi dopo aver letto questo articolo sul sito Web di Oracle: oracle.com/technetwork/articles/java/… Grazie per il chiarimento
Jason Thompson,

73

Dopo aver fatto un po 'di ricerche per conto mio, mi sono imbattuto in una serie di cose che potrebbero suggerire quando questo è appropriato. La più autorevole è la seguente citazione da un articolo Oracle:

"È importante notare che l'intenzione della classe Optional non è quella di sostituire ogni singolo riferimento null . Invece, il suo scopo è di aiutare a progettare API più comprensibili in modo che, leggendo semplicemente la firma di un metodo, si possa dire se si può aspettarsi un valore facoltativo. Ciò ti obbliga a scartare attivamente un Opzionale per far fronte all'assenza di un valore. " - Stanco delle eccezioni del puntatore null? Prendi in considerazione l'utilizzo di Java SE 8 opzionale!

Ho anche trovato questo estratto da Java 8 Opzionale: come usarlo

"Opzionale non è pensato per essere utilizzato in questi contesti, in quanto non ci comprerà nulla:

  • nel livello del modello di dominio (non serializzabile)
  • in DTO (stesso motivo)
  • nei parametri di input dei metodi
  • nei parametri del costruttore "

Che sembra anche sollevare alcuni punti validi.

Non sono riuscito a trovare connotazioni negative o bandiere rosse per suggerire che Optionaldovrebbero essere evitate. Penso che l'idea generale sia, se è utile o migliora l'usabilità della tua API, usala.


11
stackoverflow.com/questions/25693309 - sembra che Jackson lo supporti già, quindi "non serializzabile" non passa più come motivo valido :)
Vlasec,

1
Suggerisco di avere il tuo indirizzo di risposta perché usare i Optionalparametri di input dei metodi (più specificamente i costruttori) "non ci comprerà nulla". dolszewski.com/java/java-8-optional-use-cases contiene una bella spiegazione.
Gili,

Ho scoperto che il curriculum dei risultati opzionali che devono essere trasformati in altre API richiede un parametro opzionale. Il risultato è un'API abbastanza comprensibile. Vedi stackoverflow.com/a/31923105/105870
Karl the Pagan,

1
Il collegamento è interrotto. Potresti per favore aggiornare la risposta?
softarn,

2
Il problema è che spesso non migliora l'API, nonostante le migliori intenzioni dello sviluppatore. Sono stati esempi di raccolta di utilizzi errati di Opzionale , tutti presi dal codice di produzione, che è possibile verificare.
MiguelMunoz,

20

Direi in generale che è una buona idea usare il tipo opzionale per valori di ritorno che possono essere nulli. Tuttavia, per quanto riguarda i framework, suppongo che la sostituzione dei getter classici con tipi opzionali causerà molti problemi quando si lavora con i framework (ad es. Hibernate) che si basano su convenzioni di codifica per getter e setter.


14
Questo consiglio è esattamente il genere di cosa che intendevo per "siamo preoccupati per il rischio di un uso eccessivo zelante" in stackoverflow.com/a/26328555/3553087 .
Brian Goetz,

13

Il motivo è Optionalstato aggiunto a Java perché questo:

return Arrays.asList(enclosingInfo.getEnclosingClass().getDeclaredMethods())
    .stream()
    .filter(m -> Objects.equals(m.getName(), enclosingInfo.getName())
    .filter(m ->  Arrays.equals(m.getParameterTypes(), parameterClasses))
    .filter(m -> Objects.equals(m.getReturnType(), returnType))
    .findFirst()
    .getOrThrow(() -> new InternalError(...));

è più pulito di così:

Method matching =
    Arrays.asList(enclosingInfo.getEnclosingClass().getDeclaredMethods())
    .stream()
    .filter(m -> Objects.equals(m.getName(), enclosingInfo.getName())
    .filter(m ->  Arrays.equals(m.getParameterTypes(), parameterClasses))
    .filter(m -> Objects.equals(m.getReturnType(), returnType))
    .getFirst();
if (matching == null)
  throw new InternalError("Enclosing method not found");
return matching;

Il mio punto è che Opzionale è stato scritto per supportare la programmazione funzionale , che è stata aggiunta a Java contemporaneamente. (L'esempio viene fornito da un blog di Brian Goetz . Un esempio migliore potrebbe usare il orElse()metodo, poiché questo codice genererà comunque un'eccezione, ma ottieni l'immagine.)

Ma ora, le persone usano Opzionale per un motivo molto diverso. Lo stanno usando per correggere un difetto nel design del linguaggio. Il difetto è questo: non c'è modo di specificare quali dei parametri di un'API e i valori restituiti possono essere nulli. Può essere menzionato nei javadocs, ma la maggior parte degli sviluppatori non scrive nemmeno javadocs per il loro codice e non molti controlleranno i javadocs mentre scrivono. Quindi questo porta a un sacco di codice che controlla sempre i valori null prima di usarli, anche se spesso non possono essere nulli perché sono già stati validati ripetutamente nove o dieci volte nello stack di chiamate.

Penso che ci sia stata una vera sete per risolvere questo difetto, perché così tante persone che hanno visto la nuova classe Optional hanno assunto il suo scopo era quello di aggiungere chiarezza alle API. È per questo che le persone fanno domande del tipo "se i vincitori dovrebbero restituire gli Optionals?" No, probabilmente non dovrebbero, a meno che non ti aspetti che il getter venga utilizzato nella programmazione funzionale, il che è molto improbabile. In effetti, se si guarda dove viene utilizzato Opzionale nell'API Java, è principalmente nelle classi Stream, che sono il nucleo della programmazione funzionale. (Non ho controllato molto attentamente, ma le classi Stream potrebbero essere l' unico posto in cui vengono utilizzate.)

Se si prevede di utilizzare un getter in un po 'di codice funzionale, potrebbe essere una buona idea avere un getter standard e un secondo che restituisca Opzionale.

Oh, e se hai bisogno che la tua classe sia serializzabile, non dovresti assolutamente usare Opzionale.

Gli optionals sono una pessima soluzione al difetto dell'API perché a) sono molto dettagliati eb) Non sono mai stati pensati per risolvere quel problema in primo luogo.

Una soluzione molto migliore al difetto dell'API è il Controllo nullità . Questo è un processore di annotazione che ti consente di specificare quali parametri e valori di ritorno possono essere nulli annotandoli con @Nullable. In questo modo, il compilatore può eseguire la scansione del codice e capire se un valore che può essere effettivamente null viene passato a un valore in cui null non è consentito. Per impostazione predefinita, presuppone che nulla sia autorizzato a essere null a meno che non sia annotato in tal modo. In questo modo, non devi preoccuparti di valori null. Passare un valore null a un parametro comporterà un errore del compilatore. Testare un oggetto per null che non può essere null produce un avviso del compilatore. L'effetto di questo è di cambiare NullPointerException da un errore di runtime a un errore di compilazione.

Questo cambia tutto.

Per quanto riguarda i tuoi getter, non usare Opzionale. E prova a progettare le tue classi in modo che nessuno dei membri possa essere nullo. E forse prova ad aggiungere Nullness Checker al tuo progetto e a dichiarare i tuoi getter e parametri setter @Nullable se ne hanno bisogno. L'ho fatto solo con nuovi progetti. Probabilmente produce molti avvertimenti nei progetti esistenti scritti con molti test superflui per null, quindi potrebbe essere difficile aggiornarlo. Ma catturerà anche molti bug. Lo adoro. Il mio codice è molto più pulito e più affidabile per questo.

(C'è anche un nuovo linguaggio che si rivolge a questo. Kotlin, che si compila in codice byte Java, ti permette di specificare se un oggetto può essere nullo quando lo dichiari. È un approccio più pulito.)

Addendum al messaggio originale (versione 2)

Dopo averci riflettuto molto, sono giunto con riluttanza alla conclusione che è accettabile restituire Opzionale a una condizione: che il valore recuperato potrebbe effettivamente essere nullo. Ho visto un sacco di codice in cui le persone restituiscono routinariamente Facoltativamente da getter che non possono restituire null. Vedo questo come una brutta pratica di codifica che aggiunge complessità al codice, il che rende più probabili i bug. Ma quando il valore restituito potrebbe effettivamente essere nullo, andare avanti e racchiuderlo in un Opzionale.

Tieni presente che i metodi progettati per la programmazione funzionale e che richiedono un riferimento di funzione, saranno (e dovrebbero) essere scritti in due forme, una delle quali utilizza Opzionale. Ad esempio, Optional.map()ed Optional.flatMap()entrambi accettano riferimenti a funzioni. Il primo prende un riferimento a un normale getter e il secondo ne prende uno che ritorna Opzionale. Quindi non stai facendo un favore a nessuno restituendo un Opzionale in cui il valore non può essere nullo.

Detto questo, vedo ancora che l'approccio utilizzato da Nullness Checker è il modo migliore per gestire i valori null, poiché trasformano NullPointerExceptions da bug di runtime in errori di compilazione.


2
Questa risposta mi sembra più appropriata. Opzionale è stato aggiunto in java 8 solo quando sono stati aggiunti stream. E solo le funzioni di streaming tornano opzionali per quanto ho visto.
Archit

1
Penso che questa sia una delle migliori risposte. Le persone sanno cos'è Opzionale e come funziona. Ma la parte più confusa è / era dove usarla e come usarla. leggendo questo chiarisce molti dubbi.
ParagFlume,

3

Se stai utilizzando serializzatori moderni e altri framework che comprendono, Optionalho scoperto che queste linee guida funzionano bene durante la scrittura di Entitybean e livelli di dominio:

  1. Se il livello di serializzazione (in genere un DB) consente un nullvalore per una cella nella colonna BARnella tabellaFOO , il getter Foo.getBar()può restituire Optionalindicando allo sviluppatore che si può ragionevolmente ritenere che questo valore sia nullo e dovrebbero gestirlo. Se il DB garantisce che il valore non sarà nullo, il getter non dovrebbe racchiuderlo in un file Optional.
  2. Foo.bardovrebbe essere privatee non essere Optional. Non c'è davvero motivo per cui sia Optionalse lo è private.
  3. Il setter Foo.setBar(String bar)dovrebbe prendere il tipo di bare non Optional . Se è corretto utilizzare un nullargomento, dichiararlo nel commento JavaDoc. Se non è corretto utilizzare nulluna IllegalArgumentExceptiono qualche logica aziendale appropriata, IMHO è più appropriato.
  4. I costruttori non hanno bisogno Optional argomenti (per ragioni simili al punto 3). Generalmente includo solo argomenti nel costruttore che devono essere non nulli nel database di serializzazione.

Per rendere quanto sopra più efficiente, potresti voler modificare i tuoi modelli IDE per la generazione di getter e modelli corrispondenti per toString(), equals(Obj o)ecc. O utilizzare i campi direttamente per quelli (la maggior parte dei generatori IDE hanno già a che fare con null).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.