Perché Java non consente la presenza di definizioni di funzioni al di fuori della classe?

20

Diversamente dal C ++, in Java, non possiamo avere solo dichiarazioni di funzioni nella classe e definizioni al di fuori della classe. Perché è così?

È necessario sottolineare che un singolo file in Java dovrebbe contenere solo una classe e nient'altro?

    
posta gnat 29.01.2013 - 05:14
fonte

9 risposte

28

La differenza tra C ++ e Java è in ciò che le lingue considerano la loro più piccola unità di collegamento.

Poiché C è stato progettato per coesistere con l'assembly, quell'unità è la subroutine chiamata da un indirizzo. (Questo è vero per altri linguaggi che vengono compilati in file di oggetti nativi, come FORTRAN.) In altre parole, un file oggetto contenente una funzione foo() avrà un simbolo chiamato _foo che verrà risolto in nient'altro che un indirizzo tale come 0xdeadbeef durante il collegamento. Questo è tutto ciò che c'è. Se la funzione è di assumere argomenti, è compito del chiamante assicurarsi che tutto ciò che la funzione si aspetta sia in ordine prima di chiamare il suo indirizzo. Normalmente, questo viene fatto accumulando cose nello stack, e il compilatore si prende cura del lavoro del grugnito e assicurandosi che i prototipi combacino. Non c'è controllo di questo tra i file oggetto; se si risolve il collegamento di chiamata, la chiamata non sta per andare fuori come previsto e non si otterrà un avvertimento su di esso. Nonostante il pericolo, questo rende possibile che i file oggetto compilati da più linguaggi (incluso l'assemblaggio) siano collegati insieme in un programma funzionante senza troppe complicazioni.

C ++, nonostante tutto il suo fanciness aggiuntivo, funziona allo stesso modo. Il compilatore di scarpe namespace, classi e metodi / membri / ecc. in questa convenzione appiattendo i contenuti delle classi in singoli nomi che vengono storpiati in un modo che li rende unici. Ad esempio, un metodo come Foo::bar(int baz) potrebbe essere modificato in _ZN4Foo4barEi quando inserito 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 con schemi di mangling diversi, sarai sfortunato. Per quanto brutto, significa che puoi usare un blocco extern "C" per disabilitare il manegging, rendendo possibile rendere il codice C ++ facilmente accessibile ad altre lingue. C ++ ha ereditato la nozione di funzioni fluttuanti da C, soprattutto perché il formato nativo dell'oggetto lo consente.

Java è una bestia diversa che vive in un mondo isolato con il proprio formato di file oggetto, il file .class . I file di classe contengono una grande quantità di informazioni sui loro contenuti che consentono l'ambiente fare cose con le classi in fase di esecuzione che il meccanismo di collegamento nativo non potrebbe nemmeno sognare. Questa informazione deve iniziare da qualche parte e quel punto di partenza è class . Le informazioni disponibili consentono al codice compilato di descriversi senza la necessità di file separati contenenti una descrizione nel codice sorgente come avresti in C, C ++ o in altre lingue. Questo ti offre tutti i tipi di linguaggi di sicurezza che utilizzano la mancanza del link nativo, anche in fase di esecuzione, ed è ciò che ti permette di pescare una classe arbitraria da un file usando la reflection e usarla 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 (in 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 è stato grande e non particolarmente veloce sull'hardware disponibile quando è stato introdotto, proprio come Ada era stato nel decennio precedente. Solo Jim Gosling può dire con certezza quali fossero le sue motivazioni nel rendere la classe più piccola unità di collegamento di Java, ma dovrei indovinare che la complessità aggiuntiva che l'aggiunta di floaters avrebbe aggiunto al runtime avrebbe potuto essere un vero affare.

    
risposta data 29.01.2013 - 13:52
fonte
13

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. Farò il 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 della progettazione del linguaggio Java.

    
risposta data 29.01.2013 - 06:39
fonte
11

La vera domanda è quale sarebbe il merito di continuare a fare le cose in modo C ++ e qual era lo scopo originale del file di intestazione? La risposta breve è che lo stile del file di intestazione consentiva tempi di compilazione più rapidi su progetti di grandi dimensioni in cui molte classi potevano potenzialmente fare riferimento allo stesso tipo. Questo non è necessario in JAVA e .NET a causa della natura dei compilatori.

Vedi questa risposta qui: I file header sono effettivamente validi?

    
risposta data 29.01.2013 - 06:06
fonte
3

Un file Java rappresenta una classe. Se avessi una procedura al di fuori della classe, quale sarebbe lo scopo? Sarebbe globale? Oppure appartenere alla classe che rappresenta il file Java?

Presumibilmente, lo metti in quel file Java invece di un altro file per una ragione - perché va con quella classe più di ogni altra classe. Se una procedura al di fuori di una classe era effettivamente associata a quella classe, allora perché non forzarla a entrare in quella classe a cui appartiene? Java gestisce questo come un metodo statico all'interno della classe.

Se fosse consentita una procedura di classe esterna, presumibilmente non avrebbe accesso speciale alla classe di cui è stato dichiarato il file, limitandolo quindi a una funzione di utilità che non modifica alcun dato.

L'unico lato negativo di questa limitazione Java è che se hai veramente procedure globali che non sono associate a nessuna classe, finisci per creare una classe MyGlobals per tenerle e importare quella classe in tutti gli altri file che usa queste procedure.

In effetti, il meccanismo di importazione Java ha bisogno di questa restrizione per poter funzionare. Con tutte le API disponibili, il compilatore java deve sapere esattamente cosa compilare e cosa compilare, quindi le istruzioni di importazione esplicite nella parte superiore del file. Senza dover raggruppare i globals in una classe artificiale, come diresti al compilatore Java di compilare le tue globali e non nessuna e tutte globali sul tuo classpath? Che ne pensi della collisione nello spazio dei nomi in cui hai un doStuff () e qualcun altro ha un doStuff ()? Non funzionerebbe. Costringendoti a specificare MyClass.doStuff () e YourClass.doStuff () corregge questi problemi. Forzare le tue procedure per andare all'interno di MyClass anziché all'esterno chiarisce questa restrizione e non impone ulteriori restrizioni al tuo codice.

Java ha sbagliato alcune cose - la serializzazione ha così tante piccole vistose che è quasi troppo difficile essere utile (si pensi SerialVersionUID). Può anche essere usato per rompere i singleton e altri schemi di progettazione comuni. Il metodo clone () su Object deve essere suddiviso in deepClone () e shallowClone () ed essere sicuro per il tipo. Tutte le classi API potevano essere 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.

    
risposta data 29.01.2013 - 14:50
fonte
2

Penso che sia un artefatto del meccanismo di caricamento della classe. Ogni file di classe è un contenitore per un oggetto caricabile. Non c'è posto "al di fuori" dei file di classe.

    
risposta data 29.01.2013 - 07:38
fonte
2

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 di 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 all'interno delle parentesi:

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 di classe DEVE includere i nomi e i tipi di TUTTE le funzioni e le variabili dei membri, proprio come in Java.

Jonathan Henson ha ragione nel dire che questo è un artefatto del modo in cui le intestazioni funzionano in C ++: ti permette di inserire dichiarazioni nel file di intestazione e nelle implementazioni in un file .cpp separato in modo che il tuo programma non violi l'ODR (One Definition Regola). Ma ha dei pregi al di fuori di questo: 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.

    
risposta data 04.02.2014 - 17:49
fonte
0

C #, che è molto simile a Java, ha questo tipo di funzionalità attraverso l'uso di metodi parziali, tranne che i metodi parziali sono esclusivamente privati.

Metodi parziali: link

Classi e metodi parziali: link

Non vedo alcun motivo per cui Java non potrebbe fare lo stesso, ma probabilmente si tratta solo di stabilire se esiste una necessità percepita dalla base di utenti per aggiungere questa funzionalità alla lingua.

Molti strumenti di generazione del codice per C # generano classi parziali in modo che lo sviluppatore possa facilmente aggiungere codice scritto manualmente alla classe in un file separato, se lo si desidera.

    
risposta data 04.02.2014 - 20:11
fonte
0

C ++ richiede che l'intero testo della classe sia compilato come parte di ogni unità di compilazione che ne usi membri o produca istanze. L'unico modo per mantenere sani i tempi di compilazione è di avere il testo della classe stessa, per quanto possibile, solo le 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 brutto vile trucco 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 la classe viene utilizzata tempi di costruzione insani.

In Java, i file di classe compilati contengono, tra le altre cose, informazioni che sono in gran parte equivalenti a un file C ++ .h. I consumatori della classe possono estrarre tutte le informazioni di cui hanno bisogno da quel file, senza dover fare in modo che il compilatore elabori il file .java. A differenza del C ++, dove il file .h contiene informazioni rese disponibili sia alle implementazioni che ai client delle classi in esse contenute, il flusso in Java è invertito: il file che i client usano non è un file sorgente usato nella compilazione del codice di classe, ma è invece prodotto dal compilatore usando le informazioni sul file codice di classe. Poiché non è necessario dividere il codice di classe tra un file contenente le informazioni richieste dai client e un file che contiene l'implementazione, Java non consente tale suddivisione.

    
risposta data 04.05.2015 - 20:11
fonte
-1

Penso che parte di esso sia che Java è un linguaggio molto protezionistico che si occupa dell'uso su grandi team. Le classi 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 è strong / tipizzato in modo statico per proteggere gli sviluppatori dal tipo mischiamento hijinx causato da altri o da soli. Avere classi e funzioni come le tue unità più piccole rende molto più facile reinventare il paradigma di come fare per progettare un'app.

Confrontati con JavaScript in cui le funzioni di prima classe a nome doppio / verbo a doppio tono piovono e passano in giro come caramelle da un pinata aperto da blocchi, il costruttore di funzioni equivalenti può modificare il prototipo aggiungendo nuove proprietà o cambiando quelle vecchie per istanze che sono già state create in qualsiasi momento, assolutamente nulla ti impedisce di sostituire qualsiasi costrutto con la tua versione, ci sono 5 tipi e si auto-convertono e auto-valutano in circostanze diverse e puoi allegare nuove proprietà a dannazione vicino a qualsiasi cosa:

function nounAndVerb(){
}
nounAndVerb.newProperty = 'egads!';

In fin dei conti si tratta di nicchie e mercati. JavaScript è approssimativamente appropriato e disastroso nelle mani di 100 (molti dei quali probabilmente saranno mediocri) come Java era una volta nelle mani di piccoli gruppi di sviluppatori che cercavano di scrivere un'interfaccia utente web con esso. Quando lavori con 100 sviluppatori l'ultima cosa che vuoi è un ragazzo che reinventa il dannato paradigma. Quando lavori con l'interfaccia utente o il rapido sviluppo è un problema più critico, l'ultima cosa che vuoi è essere bloccata a fare cose relativamente semplici velocemente semplicemente perché sarebbe facile farle molto stupidamente se non stai attento.

Alla fine della giornata, però, sono entrambi popolari lingue di uso generale, quindi c'è un po 'di argomento filosofico lì. La mia più grande mania 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 capito il valore dell'OOP di base. Quando entri in gioco dovendo avvolgere tutto in una classe, forse è solo più semplice supporre che stai facendo OOP piuttosto che gli spaghetti di funzioni travestiti da enormi catene di classi da 3 a 5 righe.

Detto questo, non c'è niente di più terribile di JavaScript scritto da qualcuno che sa quanto basta per essere pericoloso e non ha paura di mostrarlo. E penso che sia l'idea generale. Si suppone che quel tizio debba interpretare all'incirca le stesse regole in Java. Direi che il bastardo troverà sempre un modo però.

    
risposta data 11.07.2013 - 08:47
fonte

Leggi altre domande sui tag