Perché Java ha metodi `void`?


51

/ Perché Java deve avere voidmetodi? Riferimento :

Qualsiasi metodo dichiarato nullo non restituisce un valore.

Per quanto posso pensare, ogni uso di voidsarebbe meglio servito restituendo un flag di stato, l'oggetto invocato, o null.

Ciò renderebbe ogni chiamata un'assegnazione assegnabile e faciliterebbe i modelli di builder e il concatenamento dei metodi. I metodi che vengono invocati solo per i loro effetti di solito restituiscono un tipo booleano o generico Successo generano un'eccezione in caso di errore.


In modo che non abbiamo bisogno di una sintassi speciale per scrivere subroutine. Possiamo usare le stesse funzioni utilizzate.
candied_orange

Risposte:


158

Perché esiste una differenza tra "Questa funzione può avere successo o fallire ed è abbastanza autocosciente da poter dire la differenza" e "Non ci sono feedback sull'effetto di questa funzione". Senza void, controlleresti all'infinito i codici di successo e crederesti di scrivere un software robusto, mentre in realtà non stai facendo nulla del genere.


10
Ma se ha avuto successo o meno è qualcosa da esprimere lanciando o non lanciando un'eccezione, non restituendo una bandiera. E il ritorno voidnon dice nulla sul fatto che il metodo o la funzione sia abbastanza autocosciente da lanciare nel modo appropriato.
Volker Siegel,

12
@VolkerSiegel: il contesto della risposta è la domanda. L'OP ha suggerito che in tutti i casi in cui viene utilizzato void la funzione dovrebbe invece restituire un flag di stato che indica il successo dell'errore. In quel contesto il ritorno del vuoto in effetti dice che la funzione non ha letteralmente nulla da restituire. Forzare una tale funzione a restituire sempre 0 per indicare il successo darà agli utenti della funzione una falsa fiducia che stanno controllando gli errori quando in realtà il valore di ritorno è sempre 0 indipendentemente da successo o fallimento.
slebetman,

19
@VolkerSiegel C'è un gran numero di situazioni in cui le eccezioni non sono il modo corretto di fare le cose. In effetti direi che è più raro che un'eccezione sia giusta che sbagliata: un'eccezione significa che è accaduto qualcosa di orribile che deve essere giustificato. Un caso di fallimento previsto non dovrebbe mai usare un'eccezione.
Gabe Sechan,

35
@GabeSechan No, dovrebbero essere generate eccezioni se il metodo non è in grado di adempiere al suo contratto. Se il metodo richiede un riferimento non nullo ma ne ottiene uno, questo è un errore previsto e dovrebbe essere lanciato.
Andy,

5
Esistono molte sintassi alternative per risolverlo. Il motivo per cui hanno scelto questa soluzione (imbarazzante) è perché C la utilizza.
Marco van de Voort,

95
  1. Perché C ha un voidtipo e Java è stato progettato per seguire molte delle convenzioni della famiglia del linguaggio C.
  2. Esistono molte funzioni che non si desidera che restituiscano un valore. Che cosa hai intenzione di fare con "un Successtipo generico " comunque? In effetti, i valori di ritorno per indicare il successo sono ancora meno importanti in Java che in C, perché Java ha delle eccezioni per indicare l'errore e C no.

8
> "Che cosa hai intenzione di fare con" un tipo di successo generico "comunque?" - quando si scrive un codice generico, un tipo di valore singolo (si chiama tipo di unità tra l'altro) è molto utile perché elimina il caso speciale di funzioni che "restituiscono" ma non restituiscono nulla in particolare. Ma quando Java è stato creato per la prima volta, ha un sistema di tipi ancora meno espressivo (senza parametri di tipo), quindi non c'erano molte differenze pratiche. E ora è troppo tardi per cambiare. I linguaggi più moderni in realtà evitano il "tipo" vuoto (in realtà non è nemmeno un tipo, solo un marcatore speciale per i metodi) e usano invece il tipo di unità.
Sarge Borsch,

9
@SargeBorsch Il Voidtipo di Java agisce come un tipo di unità (ad esempio per Generics), sebbene purtroppo sia diverso da void(nota a margine: un gattino coccolone muore ogni volta che inventi un nuovo tipo per correggere il tipo che hai incasinato nella versione precedente). Se qualcuno non ha familiarità con i tipi di unità, questa è
un'introduzione

1
@ OgrePsalm33 in realtà non agisce come un tipo di unità (almeno in termini pratici - si deve ancora scrivere "return null;" per tornare dal metodo che ha il tipo di ritorno Void e il compilatore AFAIK alloca spazio nello stack per riferimenti Void , non approfittando del fatto che non ha mai senso leggere questi valori), è semplicemente una convenzione usarlo dove un tipo di unità "corretto" sarebbe più appropriato.
Sarge Borsch,

3
@immibis perché il tipo di unità per definizione deve avere esattamente un valore e Void non ha valori. Sì, è possibile utilizzare null(in realtà questa è l'unica scelta), ma null è una cosa speciale, qualsiasi valore del tipo di riferimento in Java può essere nullo (questo è un altro errore nella progettazione del linguaggio). E, come ho detto prima, se stai usando il Voidtipo return invece che voidmark, il compilatore Java non è tuo amico.
Sarge Borsch,

5
@SargeBorsch Void ha esattamente un valore, che è null. Il fatto che nullsia un membro della maggior parte dei tipi non ha importanza se si Voidtratta di un tipo di unità.
user253751

65

Il motivo originale per cui la lingua ha un voidtipo è perché, come in C, i creatori della lingua non volevano complicare inutilmente la sintassi della lingua con procedure e funzioni come ha fatto Pascal.

Questa era la ragione originale.

I tuoi suggerimenti:

restituendo un flag di stato

Questo è un no-no. Non utilizziamo flag di stato. Se qualcosa va storto, lo segnaliamo tramite eccezioni.

l'oggetto che viene invocato

Lo stile fluido delle invocazioni è uno sviluppo recente. Molti dei programmatori che oggi usano molto volentieri fluentemente e rotolano gli occhi se mai dovessero usare un'interfaccia che non la supporta non sono nemmeno nati al momento della creazione di Java.

restituendo null

Ciò ti costringerebbe a dichiarare un metodo come restituire qualcosa, quando in realtà non restituisce nulla, quindi sarebbe molto confuso per qualcuno che sta guardando un'interfaccia cercando di capire cosa fa. Le persone inevitabilmente inventerebbero una classe altrimenti inutile che sta per "nessun valore di ritorno" in modo che tutte le funzioni che non hanno nulla da restituire possano restituire un riferimento nullo a tale classe. Sarebbe goffo. Fortunatamente, c'è una soluzione a questo, si chiama void. E questa è la risposta alla tua domanda.


3
Fornisci una fonte per "Il motivo originale ...". Per quanto ne so, il vero motivo è che C è stato progettato per essere un assemblatore portatile.
Thorbjørn Ravn Andersen,

6
@ ThorbjørnRavnAndersen mi stai davvero chiedendo di fornire una fonte per l'affermazione che la sintassi di Java è derivata dalla sintassi C?
Mike Nakis,

3
@ ThorbjørnRavnAndersen oh, vedo, ho capito male. Quindi, vuoi una fonte per la mia affermazione su C, non su Java. Ok, mi dispiace, non ho alcuna fonte per quello. Ma penso che sia piuttosto ovvio. (Ho programmato a Pascal nel 1987, quando sono passato a C. Mio Dio, è stato 30 anni fa.)
Mike Nakis

6
Non è ovvio. C è stato sviluppato all'incirca nello stesso momento di pascal da persone che molto probabilmente non sapevano nemmeno che esistesse. Si prega di non indicare ipotesi come fatti.
Thorbjørn Ravn Andersen,

12
Il vero motivo originale era che per impostazione predefinita le funzioni C venivano restituite int, quindi una parola chiave è stata aggiunta dopo K&R per indicare "nessun tipo di restituzione".
user207421

20

Alcuni metodi, come ad esempio System.out.println, non restituiscono nulla di utile, ma vengono chiamati puramente per questo effetto collaterale. voidè un indicatore utile per il compilatore e per il lettore di codice che non viene restituito alcun valore utile.

Restituire nullinvece di voidsignifica che avresti solo NullPointerExceptionil momento in cui usi questo valore per qualsiasi cosa. Quindi scambia un errore di compilazione con un errore di runtime che è molto peggio. Inoltre, dovresti definire il tipo di ritorno come Object, che sarebbe fuorviante e confuso. (E il concatenamento non funzionerebbe ancora.)

I codici di stato vengono generalmente utilizzati per indicare condizioni di errore, ma Java ha delle eccezioni a tale scopo.

Il ritorno thisnon è possibile da metodi statici.

Restituire un Successoggetto generico non avrebbe scopi utili.


1
Totalmente d'accordo con te, la risposta più semplice e concisa.
webo80,

11

Uno dei motivi è che può essere fuorviante per tornare sempre qualcosa di diverso, per esempio, null. Esempio:

Cosa dovrebbe Arrays.sort(a)tornare?

Se si sostiene che adovrebbe essere restituito in modo che la chiamata possa essere concatenata (che sembra essere la risposta a giudicare dalla propria domanda), non è più chiaro se il valore restituito sia una copia dell'oggetto originale o dell'oggetto originale stesso . Entrambi sono possibili. E sì, potresti metterlo nella documentazione, ma è sufficientemente ambiguo che non dovresti creare l'ambiguità in primo luogo.

D'altra parte, se si sostiene che nulldovrebbe essere restituito, ciò solleva la questione di quali informazioni il valore restituito fornisce probabilmente al chiamante e perché il programmatore dovrebbe essere costretto a scrivere return nullquando non trasmette informazioni.

E se restituisci qualcos'altro totalmente assurdo (come la lunghezza di a), allora rendere l'utilizzo del valore restituito davvero confuso - pensa solo a quanto più confuso è dire int len = Arrays.sort(a)invece di int len = A.length!


1
In tutta onestà, il programmatore non sarebbe necessariamente tenuto a scrivere return null;- la JVM potrebbe anche imporre che se il controllo cade dalla fine di una funzione in modo diverso da un esplicito returno da una throwdichiarazione, nullviene implicitamente restituito al chiamante. IIRC C lo fa, ad eccezione del fatto che il valore di ritorno non è definito in quel caso (sarà qualsiasi valore si trovi nella posizione utilizzata per il valore di ritorno in quel momento).
un CVn il

2
La risposta corretta alla tua domanda in grassetto è "un array ordinato".
Bryan Boettcher,

2
@BryanBoettcher: non hai letto il resto?
Mehrdad,

1
@Michael Kjörling: è una proprietà fondamentale del linguaggio Java prevenire tali errori, vale a dire non avere un "comportamento indefinito" se si dimentica returnun'affermazione. Invece, ricevi un errore del compilatore che ti dice del tuo errore. Inserire un implicito return null;sarebbe meglio del "comportamento indefinito", ma non molto. Ciò sarebbe utile solo quando il tipo restituito non ha significato, ma da dove il compilatore trae la conclusione, che il valore restituito non ha significato, quando non lo è void?
Holger,

2
@ MichaelKjörling: "Se il returnvero non aggiunge alcun valore ...", beh, come detto, non c'è alcun indicatore per il compilatore che un ritorno non aggiungerebbe alcun valore, se non c'è un voidtipo. In realtà è l'opposto, il primo presupposto è che un metodo che dichiara un tipo restituito vuole returnqualcosa di utile di quel tipo. E non abbiamo ancora parlato di tipi primitivi, che non hanno un nullvalore, quindi qualsiasi valore predefinito iniettato dal compilatore sarebbe in conflitto con l'intervallo di valori di ritorno potenzialmente utili.
Holger,

7

Restituire un valore booleansempre uguale a quello trueche suggerisci non è solo inutile (il valore restituito non contiene informazioni), ma in realtà sarebbe fuorviante. Il più delle volte, è meglio tipi restituiti uso che effettuano solo quante più informazioni è in realtà disponibile - è per questo che in Java si ha booleanper true/ falseinvece di restituire un intcon 4 miliardi di valori possibili. Allo stesso modo, restituendo un booleancon due possibili valori in cui esiste un solo valore possibile ("successo generico"), sarebbe fuorviante.

Aggiungerebbe anche sovraccarico di prestazioni non necessarie.

Se un metodo viene utilizzato solo per il suo effetto collaterale, non c'è nulla da restituire e il tipo restituito voidriflette esattamente questo. Potenziali errori rari possono essere segnalati tramite eccezioni.

Una cosa che potrebbe essere migliorata sarebbe quella di creare voidun tipo reale, come quello di Scala Unit. Ciò risolverebbe alcuni problemi come la gestione dei farmaci generici.


Curiosità: List.addritorna sempre true, ma è per essere compatibile con il contratto più ampio di Collection.add, quindi Set.addpuò tornare trueo false.
Holger,

4

Java è un linguaggio relativamente vecchio. E nel 1995 (quando è stato creato) e poco dopo i programmatori erano molto preoccupati per la quantità di tempo in cui un processo ha preso il controllo del processore e il consumo di memoria. Il ritorno del vuoto eliminerebbe alcuni cicli di clock e un po 'di consumo di memoria da una chiamata di funzione perché non dovresti mettere il valore di ritorno nello stack e non dovresti successivamente rimuoverlo.

Un codice efficiente non ti restituisce qualcosa che non utilizzeresti mai, e quindi rendere nulla in qualcosa che ha un valore di ritorno insignificante è una pratica molto migliore in cui restituire un valore di successo.


14
i programmatori che erano molto preoccupati per la velocità non userebbero Java in questi giorni, perché le prime implementazioni di Java erano notoriamente lente. Così lento che molte persone che attualmente non ci lavorano hanno ancora l'impressione che Java sia sinonimo di grasso e lento.
Sarge Borsch,

8
@SargeBorsch mi ricorda una vecchia battuta di programmazione di 20 anni fa. "Bussa, bussa." "Chi è là?" ... ... ... ... pausa molto lunga ... ... ... "Java"
Dan Neely,

4
@DanNeely e qualche tempo dopo, un'auto guidata dall'intelligenza artificiale si schianta contro un muro a tutta velocità, senza nemmeno provare a usare i freni. È stato scritto in Java. Quindi, Java non frena ora! (era un gioco di parole in russo, non posso tradurlo molto bene in inglese)
Sarge Borsch

2
@SargeBorsch Quello che hai funziona come un gioco di parole inglese, anche se ha perso qualche sfumatura nella traduzione. E tradurre i giochi di parole è probabilmente più difficile della poesia.
Dan Neely,

3
I bravi programmatori si preoccupano ancora delle prestazioni e del consumo di memoria, il che rende inutili le richieste di restituzione della spazzatura anche oggi.
Sklivvz,

2

Ho letto argomenti che suggeriscono che la seconda opzione (ritorno this) dovrebbe essere l'approccio anziché void. Questa è una buona idea, ma l'approccio in stile builder non era popolare al momento della creazione di Java. Se fosse così popolare in quel momento come lo è ora, potrebbe essere stato l'approccio adottato. Tornare nullè una cattiva idea IMO. Vorrei che nullnon fosse nemmeno nella lingua.

Il problema ora è che se un metodo restituisce un'istanza del proprio tipo, non è chiaro se si tratti di un nuovo oggetto o dello stesso. Spesso non importa (o non dovrebbe) ma altre volte importa. Suppongo che la lingua potrebbe essere cambiata per tornare implicitamente thisa metodi nulli. L'unico problema che mi viene in mente al momento è che se il metodo fosse successivamente modificato per restituire un nuovo oggetto dello stesso tipo, non ci sarebbero stati avvisi di compilazione, ma forse non è un grosso problema. L'attuale sistema di tipi non si preoccupa di questi tipi di modifiche ora. Il modo in cui questo interagisce con l'ereditarietà / le interfacce richiederebbe qualche considerazione, ma consentirebbe alle vecchie API senza uno stile builder di essere chiamate facilmente come se lo fossero.


2
@Cody Buon punto, questo non si applica ai metodi statici.
JimmyJames,

14
@ marisbest2 Quale argomento?
8bittree

3
Bene, se nullnon facessero parte del linguaggio, le persone userebbero tutti i tipi di valori sentinella che significano "per favore ignora questo argomento / valore di ritorno, non contiene alcun valore sensibile". Il problema nullnon è la cosa in sé, ma solo che tende ad essere usato in modo errato. Scommetto che questo problema sarebbe esagerato solo se lo standardizzato nullvenisse sostituito con una miriade di soluzioni personalizzate, in cui ogni implementazione si comporterebbe diversamente come ignorare silenziosamente l'utilizzo o lanciare eccezioni speciali anziché un'eccezione puntatore nullo standard, ecc.
cmaster

2
@cmaster un ovvio sostituto nullè un tipo facoltativo adeguato (come, Maybein Haskell). Il problema con i null in Java è che non esiste un modo sano di decidere immediatamente se un valore è nullable in pratica o no. Questo porta a entrambi: programmazione inutilmente difensiva (controllo di null dove è inutile, aumentando così la percentuale di cacca nel codice) e NPE accidentali in produzione (se dimenticato di verificare dove fosse necessario). Sì, è parzialmente risolto dalle annotazioni ma sono opzionali, non sempre controllati, ecc. Quindi non ti salveranno ai confini con codice di terze parti.
Sarge Borsch,

2
@SargeBorsch Sono d'accordo con questo :-) La mia obiezione è contro una lingua senza un modo standard per esprimere un "per favore ignora questo valore". nullfunziona bene per questo (fine = semplice), ma i valori opzionali standardizzati con semantica solida sono sicuramente un'altra opzione eccellente (fine = sicura).
cmaster

2

Un altro aspetto:

Java è un linguaggio statico con funzionalità piuttosto rigide. Ciò significa che molte cose che sarebbero abbastanza aperte o dinamiche in altre lingue (c / f Ruby, Lisp ecc.) Sono rigorosamente determinate.

Questa è una decisione di progettazione generale. È difficile rispondere al "perché" (beh, perché i progettisti del linguaggio hanno pensato che sarebbe stato bello!). Il "what for" è abbastanza chiaro: consente al compilatore di rilevare molti errori, che è generalmente una buona funzionalità per qualsiasi lingua. In secondo luogo, rende relativamente facile ragionare sulla lingua. Ad esempio, è relativamente facile creare la correttezza formale che prova (sottoinsiemi) del linguaggio Java; come confronto, ciò sarebbe praticamente impossibile in un linguaggio dinamico come Ruby et al.

Questo pensiero permea la lingua, ad esempio, la dichiarazione forzata di possibili eccezioni che un metodo può generare, il tipo separato di interfacevs. classper evitare l'eredità multipla ambigua e così via. Per quello che è (un linguaggio del mondo reale OOP statico imperativo con forte attenzione alla gestione degli errori in fase di compilazione) queste cose sono in realtà piuttosto eleganti e potenti. Si avvicinano alle lingue teoriche (scientifiche) che sono state create apposta per esplorare alcuni di questi problemi rispetto a qualsiasi altra lingua del mondo reale prima (a quel tempo, attenzione).

Così. Avere un voidtipo rigoroso è un messaggio chiaro: questo metodo non restituisce nulla, punto. È quello che è. Sostituirlo con l'applicazione per restituire sempre qualcosa porterebbe a comportamenti molto più dinamici (come in Ruby dove ogni def ha un valore di ritorno esplicito o implicito, sempre), che sarebbe male per la provabilità e il ragionamento; o per gonfiare enormemente usando qualche altro meccanismo qui.

(E NB, Ruby (per esempio) gestisce in modo diverso, e la sua soluzione è ancora esattamente come accettabile come Java, perché ha una filosofia completamente diversa. Per esempio si getta dimostrabilità e ragionevolezza completamente fuori dalla finestra, mentre mettendo un grande fuoco su espressività estremamente elevata della lingua.)

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.