A differenza del C ++, in Java, non possiamo avere solo dichiarazioni di funzioni nella classe e definizioni al di fuori della classe. Perché è così?
È da sottolineare che un singolo file in Java dovrebbe contenere solo una classe e nient'altro?
A differenza del C ++, in Java, non possiamo avere solo dichiarazioni di funzioni nella classe e definizioni al di fuori della classe. Perché è così?
È da sottolineare che un singolo file in Java dovrebbe contenere solo una classe e nient'altro?
Risposte:
La differenza tra C ++ e Java sta in ciò che le lingue considerano la loro più piccola unità di collegamento.
Poiché C è stato progettato per coesistere con assembly, quell'unità è la subroutine chiamata da un indirizzo. (Questo vale per altri linguaggi che vengono compilati in file di oggetti nativi, come FORTRAN.) In altre parole, un file di oggetto contenente una funzione foo()
avrà un simbolo chiamato _foo
che verrà risolto in nient'altro che un indirizzo come0xdeadbeef
durante il collegamento. Questo è tutto quello che c'è. Se la funzione deve accettare argomenti, spetta al chiamante assicurarsi che tutto ciò che la funzione si aspetta sia in ordine prima di chiamare il suo indirizzo. Normalmente, questo viene fatto accatastando le cose sullo stack e il compilatore si occupa del lavoro grugnito e assicurandosi che i prototipi corrispondano. Non è possibile verificarlo tra i file oggetto; se si attiva il collegamento di chiamata, la chiamata non si interromperà come previsto e non si riceverà un avviso a riguardo. Nonostante il pericolo, ciò rende possibile che i file oggetto compilati da più lingue (incluso assembly) siano collegati insieme in un programma funzionante senza troppi problemi.
C ++, nonostante tutta la sua ulteriore fantasia, funziona allo stesso modo. Il compilatore calza gli spazi dei nomi, le classi e i metodi / membri / ecc. in questa convenzione, appiattendo il contenuto delle classi in nomi singoli che sono alterati in un modo che li rende unici. Ad esempio, un metodo come Foo::bar(int baz)
potrebbe essere alterato _ZN4Foo4barEi
quando messo in un file oggetto e un indirizzo come 0xBADCAFE
in fase di esecuzione. Questo dipende interamente dal compilatore, quindi se provi a collegare due oggetti che hanno schemi di manipolazione diversi, sarai sfortunato. Per quanto sia brutto, significa che è possibile utilizzare un extern "C"
blocco per disabilitare la manipolazione, rendendo possibile rendere il codice C ++ facilmente accessibile ad altre lingue. C ++ ha ereditato la nozione di funzioni fluttuanti da C, in gran parte perché il formato dell'oggetto nativo lo consente.
Java è una bestia diversa che vive in un mondo isolato con il suo formato di file oggetto, il .class
file. I file di classe contengono una grande quantità di informazioni sul loro contenuto che consente all'ambiente di fare cose con le classi in fase di esecuzione che il meccanismo di collegamento nativo non potrebbe nemmeno sognare. Tali informazioni devono iniziare da qualche parte e quel punto di partenza è ilclass
. Le informazioni disponibili consentono al codice compilato di descriversi senza la necessità di file separati contenenti una descrizione nel codice sorgente come in C, C ++ o altre lingue. Ciò offre tutti i tipi di linguaggi dei benefici per la sicurezza che utilizzano la mancanza di collegamenti nativi, anche in fase di esecuzione, ed è ciò che consente di estrarre una classe arbitraria da un file utilizzando la riflessione e di utilizzarlo con un errore garantito se qualcosa non corrisponde .
Se non l'hai già capito, tutta questa sicurezza ha un compromesso: tutto ciò che colleghi a un programma Java deve essere Java. (Per "collegamento" intendo ogni volta che qualcosa in un file di classe fa riferimento a qualcosa in un altro.) Puoi collegare (nel senso nativo) al codice nativo usando JNI , ma c'è un contratto implicito che dice che se rompi il lato nativo , possiedi entrambi i pezzi.
Java era grande e non particolarmente veloce sull'hardware disponibile quando fu introdotto per la prima volta, proprio come Ada era stato nel decennio precedente. Solo Jim Gosling può dire con certezza quali fossero le sue motivazioni nel creare la più piccola unità di collegamento della classe Java, ma dovrei indovinare che l'ulteriore complessità che l'aggiunta di floater liberi avrebbe aggiunto al runtime avrebbe potuto essere un affare-killer.
Credo che la risposta sia, per Wikipedia, che Java è stato progettato per essere semplice e orientato agli oggetti. Le funzioni sono pensate per operare sulle classi in cui sono definite. Con quella linea di pensiero, avere funzioni al di fuori di una classe non ha senso. Salto alla conclusione che Java non lo consente perché non si adattava alla pura OOP.
Una rapida ricerca su Google per me non ha prodotto molto sulle motivazioni del design del linguaggio Java.
La vera domanda è quale sarebbe il merito di continuare a fare le cose nel modo C ++ e qual era lo scopo originale del file header? La risposta breve è che lo stile del file di intestazione ha permesso tempi di compilazione più rapidi su grandi progetti in cui molte classi potrebbero potenzialmente fare riferimento allo stesso tipo. Ciò non è necessario in JAVA e .NET a causa della natura dei compilatori.
Vedi questa risposta qui: i file di intestazione sono effettivamente buoni?
interface
e class
:) Non c'è bisogno di intestazioni!
Un file Java rappresenta una classe. Se avessi una procedura fuori dalla classe, quale sarebbe l'ambito? Sarebbe globale? O appartiene alla classe rappresentata dal file Java?
Presumibilmente, lo metti in quel file Java invece di un altro file per un motivo - perché si abbina a quella classe più di qualsiasi altra classe. Se una procedura esterna a una classe è stata effettivamente associata a quella classe, allora perché non forzarla a entrare in quella classe a cui appartiene? Java gestisce questo come metodo statico all'interno della classe.
Se fosse consentita una procedura di classe esterna, presumibilmente non avrebbe alcun accesso speciale alla classe di cui è stato dichiarato il file, limitandolo a una funzione di utilità che non modifica alcun dato.
L'unico lato negativo possibile di questa limitazione Java è che se si hanno veramente procedure globali che non sono associate a nessuna classe, si finisce per creare una classe MyGlobals per conservarle e importare quella classe in tutti gli altri file che utilizzano tali procedure .
In effetti, il meccanismo di importazione Java ha bisogno di questa restrizione per funzionare. Con tutte le API disponibili, il compilatore java deve sapere esattamente cosa compilare e cosa compilare, quindi le dichiarazioni esplicite di importazione nella parte superiore del file. Senza dover raggruppare i tuoi globali in una classe artificiale, come diresti al compilatore Java di compilare i tuoi globali e non tutti i globali sul tuo percorso di classe? Che dire della collisione dello spazio dei nomi in cui hai un doStuff () e qualcun altro ha un doStuff ()? Non funzionerebbe. Costringendoti a specificare MyClass.doStuff () e YourClass.doStuff () risolve questi problemi. Obbligare le tue procedure a entrare in MyClass anziché all'esterno chiarisce solo questa restrizione e non impone ulteriori restrizioni al tuo codice.
Java ha sbagliato diverse cose: la serializzazione ha così tante piccole verruche che è quasi troppo difficile essere utile (pensa a SerialVersionUID). Può anche essere usato per rompere i singoli e altri modelli di design comuni. Il metodo clone () su Object dovrebbe essere diviso in deepClone () e shallowClone () ed essere sicuro per i tipi. Tutte le classi API potrebbero essere state rese immutabili per impostazione predefinita (come sono in Scala). Ma la restrizione che tutte le procedure devono appartenere a una classe è buona. Serve principalmente a semplificare e chiarire la lingua e il codice senza imporre restrizioni onerose.
Penso che la maggior parte delle persone che hanno risposto e i loro elettori abbiano frainteso la domanda. Riflette che non conoscono il C ++.
"Definizione" e "Dichiarazione" sono parole con un significato molto specifico in C ++.
L'OP non significa cambiare il modo in cui funziona Java. Questa è una domanda puramente sulla sintassi. Penso che sia una domanda valida.
In C ++ ci sono due modi per definire una funzione membro.
Il primo modo è il modo Java. Basta inserire tutto il codice tra parentesi graffe:
class Box {
public:
// definition of member function
void change(int newInt) {
this._m = newInt;
}
private:
int _m
}
Secondo modo:
class Box {
public:
// declaration of member function
void change(int newInt);
private:
int _m
}
// definition of member function
// this can be in the same file as the declaration
void Box::change(int newInt) {
this._m = newInt;
}
Entrambi i programmi sono uguali. La funzione change
è ancora una funzione membro: non esiste al di fuori della classe. Inoltre, la definizione della classe DEVE includere i nomi e i tipi di TUTTE le funzioni e variabili dei membri, proprio come in Java.
Jonathan Henson ha ragione sul fatto che questo è un artefatto del modo in cui funzionano le intestazioni in C ++: ti permette di mettere le dichiarazioni nel file di intestazione e le implementazioni in un file .cpp separato in modo che il tuo programma non violi l'ODR (One Definition Rule). Ma ha dei meriti al di fuori di quello: ti permette di vedere l'interfaccia di una grande classe a colpo d'occhio.
In Java puoi approssimare questo effetto con classi o interfacce astratte, ma quelle non possono avere lo stesso nome della classe di implementazione, il che lo rende piuttosto goffo.
Penso che sia un artefatto del meccanismo di caricamento della classe. Ogni file di classe è un contenitore per un oggetto caricabile. Non esiste spazio "esterno" ai file di classe.
C #, che è molto simile a Java, ha questo tipo di funzionalità attraverso l'uso di metodi parziali, tranne per il fatto che i metodi parziali sono esclusivamente privati.
Metodi parziali: http://msdn.microsoft.com/en-us/library/6b0scde8.aspx
Classi e metodi parziali: http://msdn.microsoft.com/en-us/library/wa80x488.aspx
Non vedo alcun motivo per cui Java non possa fare lo stesso, ma probabilmente dipende solo dalla necessità dell'utente di aggiungere questa funzionalità al linguaggio.
La maggior parte degli strumenti di generazione del codice per C # genera classi parziali in modo che lo sviluppatore possa aggiungere facilmente codice scritto manualmente alla classe in un file separato, se lo si desidera.
Il C ++ richiede che l'intero testo della classe sia compilato come parte di ogni unità di compilazione che ne utilizza i membri o produce istanze. L'unico modo per mantenere sani i tempi di compilazione è che il testo della classe stessa contenga - per quanto possibile - solo quelle cose che sono effettivamente necessarie ai suoi consumatori. Il fatto che i metodi C ++ siano spesso scritti al di fuori delle classi che li contengono è un vile hack davvero motivato dal fatto che richiedere al compilatore di elaborare il testo di ogni metodo di classe una volta per ogni unità di compilazione in cui viene utilizzata la classe porterebbe a tempi di costruzione folli.
In Java, i file di classe compilati contengono, tra le altre cose, informazioni che sono in gran parte equivalenti a un file .h C ++. I consumatori della classe possono estrarre tutte le informazioni di cui hanno bisogno da quel file, senza che il compilatore elabori il file .java. A differenza del C ++, in cui il file .h contiene informazioni rese disponibili sia alle implementazioni sia ai client delle classi in esso contenute, il flusso in Java è invertito: il file utilizzato dai client non è un file di origine utilizzato nella compilazione del codice di classe, ma viene invece prodotto dal compilatore utilizzando le informazioni del file di codice di classe. Poiché non è necessario dividere il codice di classe tra un file contenente le informazioni richieste dai client e un file contenente l'implementazione, Java non consente tale suddivisione.
Penso che parte di esso sia che Java è molto un linguaggio protezionistico che si occupa dell'utilizzo in grandi team. Le lezioni non possono essere sovrascritte o ridefinite. Hai 4 livelli di modificatori di accesso che definiscono in modo molto specifico come i metodi possono e non possono essere utilizzati. Tutto è forte / tipizzato staticamente per proteggere gli sviluppatori dal tipo hijinx non corrispondente causato da altri o loro stessi. Avere classi e funzioni come unità più piccole rende molto più semplice reinventare il paradigma di come progettare un'app.
Confronta con JavaScript dove funzioni di prima classe nome / verbo a doppia proposizione piovono e passano in giro come caramelle da una pinata aperta, il costruttore di funzioni equivalenti alla classe può avere il suo prototipo alterato aggiungendo nuove proprietà o cambiando quelle vecchie per istanze che sono già stati creati in qualsiasi momento, assolutamente nulla ti impedisce di sostituire qualsiasi costrutto con la tua versione di esso, ci sono 5 tipi e si convertono automaticamente e si valutano automaticamente in diverse circostanze e puoi associare nuove proprietà a dannatamente vicino a qualsiasi cosa:
function nounAndVerb(){
}
nounAndVerb.newProperty = 'egads!';
In definitiva si tratta di nicchie e mercati. JavaScript è quasi appropriato e disastroso nelle mani di 100 sviluppatori (molti dei quali saranno probabilmente mediocri) come una volta Java era nelle mani di piccoli gruppi di sviluppatori che cercavano di scrivere l'interfaccia utente Web con esso. Quando lavori con 100 sviluppatori l'ultima cosa che vuoi è un ragazzo che reinventa il dannato paradigma. Quando si lavora con l'interfaccia utente o lo sviluppo rapido è una preoccupazione più critica, l'ultima cosa che si desidera è essere bloccati nel fare rapidamente cose relativamente semplici semplicemente perché sarebbe facile farle molto stupidamente se non si sta attenti.
Alla fine della giornata, però, sono entrambe le lingue di uso generale più diffuse, quindi c'è un po 'di argomento filosofico lì. Il mio più grande manzo personale con Java e C # è che non ho mai visto o lavorato con una base di codice legacy in cui la maggior parte degli sviluppatori sembrava aver compreso il valore di OOP di base. Quando entri in gioco dovendo avvolgere tutto in una classe, forse è più semplice supporre che stai facendo OOP piuttosto che far funzionare gli spaghetti travestiti da enormi catene di classi di 3-5 righe.
Detto questo, non c'è niente di più terribile di JavaScript scritto da qualcuno che sa solo abbastanza per essere pericoloso e non ha paura di mostrarlo. E penso che sia l'idea generale. Quel ragazzo dovrebbe presumibilmente seguire le stesse regole di Java. Direi che il bastardo troverà comunque un modo.