Perché un metodo principale statico in Java e C #, piuttosto che un costruttore?


54

Sto cercando una risposta definitiva da una fonte primaria o secondaria sul perché (in particolare) Java e C # hanno deciso di avere un metodo statico come punto di ingresso, piuttosto che rappresentare un'istanza dell'applicazione da un'istanza di una Applicationclasse (con il punto di ingresso essere un costruttore appropriato).


Contesto e dettagli della mia precedente ricerca

Questo è stato chiesto prima. Sfortunatamente, le risposte esistenti stanno semplicemente supplicando la domanda . In particolare, le seguenti risposte non mi soddisfano, poiché le ritengo errate:

  • Ci sarebbe ambiguità se il costruttore fosse sovraccarico. - In effetti, C # (così come C e C ++) consente firme diverse per Maincui esiste la stessa potenziale ambiguità e viene gestita.
  • Un staticmetodo significa che nessun oggetto può essere istanziato prima che l'ordine di inizializzazione sia chiaro. - Questo è di fatto sbagliato, alcuni oggetti sono istanziati prima (ad es. In un costruttore statico).
  • Quindi possono essere richiamati dal runtime senza dover creare un'istanza di un oggetto padre. - Questa non è una risposta.

Giusto per giustificare ulteriormente perché ritengo che questa sia una domanda valida e interessante:

  • Molti quadri fanno utilizzare le classi per rappresentare applicazioni e costruttori come punti di ingresso. Ad esempio, il framework dell'applicazione VB.NET utilizza una finestra di dialogo principale dedicata (e il suo costruttore) come punto di ingresso 1 .

  • Né Java né C # hanno tecnicamente bisogno di un metodo principale. Bene, C # ha bisogno di essere compilato, ma Java nemmeno quello. E in nessun caso è necessario per l'esecuzione. Quindi questa non sembra essere una limitazione tecnica. E, come ho già detto nel primo paragrafo, per una semplice convenzione sembra stranamente incoerente con il principio generale del design di Java e C #.

Per essere chiari, non c'è uno svantaggio specifico nell'avere un mainmetodo statico , è semplicemente strano , il che mi ha fatto chiedermi se ci fosse qualche logica tecnica dietro di esso.

Sono interessato a una risposta definitiva da una fonte primaria o secondaria, non da semplici speculazioni.


1 Sebbene sia presente un callback ( Startup) che può intercettarlo.


4
@mjfgates Inoltre, avevo sperato di aver chiarito che questo non è semplicemente "perché la gente non lo ha fatto come voglio", e che sono sinceramente interessato alle ragioni.
Konrad Rudolph,

2
Per Java penso che il ragionamento sia semplice: durante lo sviluppo di Java, sapevano che la maggior parte delle persone che imparano la lingua conoscevano in anticipo C / C ++. Quindi Java non solo assomiglia molto a C / C ++ invece di dire smalltalk, ma ha anche assunto idiosincrasie da C / C ++ (basti pensare ai letterali interi ottali). Dato che c / c ++ usano entrambi un metodo principale, fare lo stesso per java aveva senso da quel punto di vista.
Voo,

5
@Jarrod Sei ingiusto. Pensavo di averlo trasformato chiaramente non in un'esplosione. "Non costruttivo"? Come mai? Chiedo esplicitamente riferimenti, non solo discussioni sfrenate. Naturalmente sei libero di dissentire sul fatto che questa sia una domanda interessante . Ma se questo tipo di domande è OT qui, non riesco davvero a vedere a cosa serve Programmers.SE.
Konrad Rudolph,

2
Discussione Meta pertinente .
yannis,

3
Domanda: se si tratta di un oggetto applicazione, non sono necessarie due cose. 1) Un costruttore. 2) Un metodo sull'oggetto per eseguire l'applicazione. Il costruttore deve completare affinché l'oggetto sia valido e quindi eseguibile.
Martin York,

Risposte:


38

TL; DR

In Java, la ragione public static void main(String[] args)è che

  1. Voleva papera
  2. il codice scritto da qualcuno con esperienza in C (non in Java)
  3. per essere eseguito da qualcuno abituato a eseguire PostScript su NeWS

http://i.stack.imgur.com/qcmzP.png

 
Per C #, il ragionamento è transitivamente simile per così dire. I progettisti linguistici hanno tenuto familiare la sintassi del punto di ingresso del programma per i programmatori provenienti da Java. Come afferma l' architetto C # Anders Hejlsberg ,

... il nostro approccio con C # è stato semplicemente quello di offrire un'alternativa ... ai programmatori Java ...

 

Versione lunga

espandendosi sopra e backup con riferimenti noiosi.

 

java Terminator Hasta la vista Baby!

Specifiche VM, 2.17.1 Avvio macchina virtuale

... Il modo in cui la classe iniziale viene specificata nella macchina virtuale Java va oltre lo scopo di questa specifica, ma è tipico, negli ambienti host che utilizzano le righe di comando, che il nome completo della classe sia specificato come un argomento della riga di comando e per i successivi argomenti della riga di comando da utilizzare come stringhe da fornire come argomento al metodo principale. Ad esempio, usando l'SDK Java 2 di Sun per Solaris, la riga di comando

java Terminator Hasta la vista Baby!

avvierà una macchina virtuale Java invocando il metodo main of class Terminator(una classe in un pacchetto senza nome) e passandogli un array contenente le quattro stringhe "Hasta", "la", "vista" e "Baby!" ...

... vedi anche: Appendice: ho bisogno dei tuoi vestiti, dei tuoi stivali e della tua moto

  • La mia interpretazione:
    esecuzione mirata per l'uso come script tipici nell'interfaccia della riga di comando.

 

evitamento importante

... questo aiuta a evitare un paio di false tracce nella nostra indagine.

VM Spec, 1.2 Java Virtual Machine

La macchina virtuale Java non sa nulla del linguaggio di programmazione Java ...

Ho notato sopra quando studiavo il capitolo precedente - 1.1 Storia che pensavo potesse essere utile (ma risultò inutile).

  • La mia interpretazione: l'
    esecuzione è governata dalle specifiche della VM, che
    dichiara esplicitamente che non ha nulla a che fare con il linguaggio Java
    => OK per ignorare JLS e qualsiasi cosa relativa al linguaggio Java

 

Gosling: un compromesso tra C e linguaggio di scripting ...

Sulla base di quanto sopra, ho iniziato a cercare nel web la storia di JVM . Non ha aiutato, troppa spazzatura nei risultati.

Poi, ho ricordato leggende su Gosling e ho ristretto la mia ricerca alla storia di Gosling JVM .

Eureka! Come sono venute le specifiche JVM

In questo keynote del JVM Languages ​​Summit 2008, James Gosling discute ... la creazione di Java, ... un compromesso tra C e il linguaggio di scripting ...

  • La mia interpretazione:
    dichiarazione esplicita che al momento della creazione,
    C e scripting sono stati considerati le influenze più importanti.
     
    Già visto accennare allo scripting nella specifica VM 2.17.1, gli
    argomenti della riga di comando spiegano sufficientemente String[] args
    ma statice mainnon sono ancora arrivati, è necessario scavare ulteriormente ...

Nota durante la digitazione - che collega C, scripting e VM Spec 1.2 con il suo nulla di Java - Sento che qualcosa di familiare, qualcosa ... orientato agli oggetti sta lentamente scomparendo. Prendi la mia mano e continua a muoverti Non rallentare, siamo quasi arrivati ​​adesso

Le diapositive Keynote sono disponibili online: 20_Gosling_keynote.pdf , abbastanza conveniente per copiare punti chiave.

    pagina 3

        La preistoria di Java
        * Che cosa ha plasmato il mio pensiero

    pagina 9

        Notizia
        * Sistema finestra estensibile collegato in rete
        * Un sistema di finestre basato su scripting ....
          PostScript (!!)

    pagina 16

        Un grande (ma silenzioso) obiettivo:
          Quanto potrei avvicinarmi a
          "scripting" sentire ...

    pagina 19

        Il concetto originale
        * Tutto riguardava la costruzione
          reti di cose,
          orchestrato da uno scripting
          linguaggio
        * (Shell Unix, AppleScript, ...)

    pagina 20

        Un lupo travestito da pecora
        * Sintassi C per creare sviluppatori
          confortevole

A-ha! Diamo un'occhiata più da vicino la sintassi C .

L'esempio "ciao, mondo" ...

main()
{
    printf("hello, world\n");
}

... è stata definita una funzione chiamata main. La funzione principale ha uno scopo speciale nei programmi C; l'ambiente di runtime chiama la funzione principale per iniziare l'esecuzione del programma.

... La funzione principale in realtà ha due argomenti int argce char *argv[], rispettivamente, che possono essere utilizzati per gestire gli argomenti della riga di comando ...

Ci stiamo avvicinando? Scommetti. Vale anche la pena seguire il link "principale" dalla citazione sopra:

la funzione principale è dove un programma inizia l'esecuzione. È responsabile dell'organizzazione di alto livello della funzionalità del programma e in genere ha accesso agli argomenti dei comandi forniti al programma quando è stato eseguito.

  • La mia interpretazione:
    per essere a mio agio con lo sviluppatore C, il punto di accesso al programma deve essere main.
    Inoltre, poiché Java richiede che qualsiasi metodo sia in classe, Class.mainè il
    più vicino possibile: invocazione statica, solo nome e punto della classe,
    nessun costruttore per favore - C non sa nulla del genere.
     
    Questo vale anche in modo transitorio per C #, tenendo conto
    dell'idea di una facile migrazione ad esso da Java.

I lettori che pensano che il punto di ingresso del programma familiare non abbia importanza sono gentilmente invitati a cercare e controllare le domande Stack Overflow in cui i ragazzi provenienti da Java SE stanno cercando di scrivere Hello World per Java ME MIDP. Nota Il punto di ingresso MIDP non ha mainstatic.

 

Conclusione

Basandomi su, direi che static, maine String[] argsnei momenti della creazione di Java e C #, le scelte più ragionevoli per definire il punto di ingresso del programma .

 

Appendice: ho bisogno dei tuoi vestiti, dei tuoi stivali e della tua moto

Devo ammettere che leggere la specifica VM 2.17.1 è stato molto divertente.

... la riga di comando

java Terminator Hasta la vista Baby!

avvierà una macchina virtuale Java invocando il metodo main of class Terminator(una classe in un pacchetto senza nome) e passandogli un array contenente le quattro stringhe "Hasta", "la", "vista" e "Baby!".

Descriviamo ora i passaggi che la macchina virtuale può eseguire per eseguire Terminator, come esempio dei processi di caricamento, collegamento e inizializzazione descritti più avanti nelle sezioni successive.

Il tentativo iniziale ... scopre che la classe Terminatornon è caricata ...

Dopo che Terminatorè stato caricato, deve essere inizializzato prima di poter richiamare main e un tipo (classe o interfaccia) deve essere sempre collegato prima di essere inizializzato. Il collegamento (§2.17.3) comporta la verifica, la preparazione e (facoltativamente) la risoluzione ...

La verifica (§2.17.3) verifica che la rappresentazione caricata di Terminatorsia ben formata ...

La risoluzione (§2.17.3) è il processo di verifica dei riferimenti simbolici dalla classe Terminator...

 
Riferimenti simbolici da Terminatoroh sì.


2
Per qualche ragione ho avuto difficoltà a credere che "modernità" fosse una parola vera.
someguy,

La storia di @Songo della risposta è anche come un film. È stato pubblicato per la prima volta su meta , in una discussione sulla chiusura della domanda: "Se la domanda dovesse essere riaperta, probabilmente scriverei una risposta come sotto ..." Quindi è stata utilizzata per sostenere l'appello per riaprire e infine spostata qui
moscerino del

16

Mi sembra vagamente offensivo. Un costruttore viene utilizzato per l'inizializzazione di un oggetto: imposta un oggetto, che viene quindi utilizzato dal codice che lo ha creato.

Se si inserisce la funzionalità di utilizzo di base all'interno del costruttore e quindi non si utilizza effettivamente l'oggetto che il costruttore crea nel codice esterno, si stanno violando i principi di OOP. Fondamentalmente, fare qualcosa di veramente strano senza una ragione apparente.

Perché dovresti farlo comunque?


5
Ma l '"istanza dell'applicazione" non è logicamente un oggetto? Perché sarebbe abusivo? Per quanto riguarda l'utilizzo dell'oggetto - ha uno scopo: rappresentare l'applicazione in esecuzione. Sembra molto SoC -y per me. "Perché dovresti volerlo fare?" - Sono solo interessato alla logica della decisione poiché la trovo in contrasto con il resto della mentalità.
Konrad Rudolph,

7
@KonradRudolph: generalmente si prevede che un costruttore, come un getter di proprietà, si completi in un tempo limitato senza attendere che si verifichi un evento asincrono (come l'input dell'utente). Sarebbe possibile avere un costruttore che ha avviato un thread principale dell'applicazione, ma che aggiunge un livello di complessità che potrebbe non essere necessario per tutte le applicazioni. Richiedere che un'applicazione console che stampa semplicemente "Hello world" sull'output standard debba generare un thread aggiuntivo sarebbe sciocco. L'uso di un Mainmetodo funziona bene per il caso semplice e non è davvero un problema nei casi più difficili, quindi perché no?
supercat

9

Per Java penso che il ragionamento sia semplice: durante lo sviluppo di Java, gli sviluppatori sapevano che la maggior parte delle persone che imparano la lingua conoscevano in anticipo C / C ++.

Quindi Java non solo assomiglia molto a C / C ++ invece di dire smalltalk, ma ha anche assunto idiosincrasie da C / C ++ (basti pensare ai letterali interi ottali). Dato che c / c ++ usano entrambi un metodo principale, fare lo stesso per java aveva senso da quel punto di vista.

Sono abbastanza sicuro di ricordare Bloch o qualcuno che ha detto qualcosa del genere sul perché hanno aggiunto valori letterali interi ottali, vedrò se riesco a trovare alcune fonti :)


2
Se l'aspetto dello stesso C ++ era così importante per Java, perché ad esempio sono cambiati :in extends? E public static void main(String [ ] args)all'interno di una classe è abbastanza diverso rispetto a int main(int argc, char **argv)fuori di una classe.
svick,

2
@svick Una possibilità: Java ha introdotto le interfacce e chiaramente volevano separare i due concetti (ereditando interfacce / classi) - con una sola "parola chiave" che non funzionava. E "abbastanza diverso"? È il mapping più vicino possibile di esso e finora non ho mai visto un programmatore c ++ avere un problema a capire che il metodo principale statico è il punto di ingresso. Contrariamente a quello che ha una classe chiamata Application o qualcosa il cui costruttore è usato, è qualcosa che sembrerebbe strano alla maggior parte dei programmatori c ++.
Voo,

@svick int in c a void in java ha dovuto a come è stato generato un codice di ritorno da un'applicazione - in java, è 0 se non viene richiamato System.exit (int). Il cambio di parametri ha a che fare con il modo in cui le matrici di stringhe vengono passate in ciascuna lingua. Tutto in Java è in una classe - non c'è possibilità di averlo altrove. Il passaggio :a extendsè una questione di sintassi e sono essenzialmente gli stessi. Tutto il resto è dettato dalla lingua.

@MichaelT Ma tutte queste sono decisioni di progettazione che rendono Java diverso dal C ++. Quindi perché mantenere Java uguale al C ++ sarebbe importante nel caso di main(), quando apparentemente non era abbastanza importante in altri casi.
svick,

@svick Ad eccezione del fatto che è perfettamente ok non restituire nulla dalla rete principale in C e tali banalità difficilmente confonderebbero nessuno comunque. Il punto non era ricreare c ++ e tutti i suoi errori, ma solo rendere il programmatore più a suo agio. Cosa pensi che un programmatore C ++ avrà più tempo a leggere: Java o codice object-c? Cosa pensi che apparirà più ovvio a un programmatore C ++ come metodo principale o costruttore di una classe come punto di ingresso?
Voo,

6

Bene, ci sono molte funzioni principali là fuori che eseguono un ciclo infinito. Un costruttore che lavora in questo modo (con un oggetto che non viene mai costruito) è ciò che mi sembra strano.

Ci sono così tante cose divertenti su questo concetto. La tua logica in esecuzione su un oggetto non ancora nato, oggetti che sono nati per morire (dal momento che fanno tutto il loro lavoro nel costruttore), ...

Tutti questi effetti collaterali non corromperebbero molto di più il carro OO di un semplice pubblico (perché deve essere accessibile da uno sconosciuto) statico (perché non è necessaria alcuna istanza per iniziare) void main (perché è il punto di ingresso )?

Affinché un punto di accesso alla funzione semplice, chiaro, esista in Java, sia pubblico che statico sarebbe automaticamente richiesto. Pur essendo un metodo statico , si riduce a ciò che possiamo avvicinarci di più a una semplice funzione per realizzare ciò che si desidera: un semplice punto di ingresso.

Se non si adotterà un punto di ingresso funzione semplice, chiaro come punto di ingresso. Qual è il prossimo che non sembra strano come un contruttore che non è destinato a costruire?


1
Direi che il problema non era avere funzioni di prima classe. Attaccare main () all'interno di un oggetto (che non è istanziato prima che venga chiamato main) è un po 'un anti-pattern. Forse dovrebbero avere un oggetto "application" che viene costruito ed esegue il suo metodo main () non statico, quindi puoi mettere l'inizializzazione di avvio nel costruttore e sarebbe molto meglio che avere metodi statici, anche se un semplice top- = level main () fn sarebbe anche buono. Il main statico è un po 'un kludge tutto sommato.
gbjbaanb,

3

Puoi eseguire rapidamente alcuni test autonomi su una classe, durante lo sviluppo, attaccando a main()nella classe che stai provando a testare.


1
Questo per me è probabilmente il motivo più convincente, in quanto consente anche più punti di ingresso durante lo sviluppo per testare diverse configurazioni, ecc.
cgull

0

Devi iniziare da qualche parte. Un main statico è l'ambiente di esecuzione più semplice che puoi avere - nessuna istanza di nulla (al di fuori della JVM e dei semplici parametri di stringa) deve essere creata - quindi può "venire fuori" con un minimo di confusione (e bassa probabilità di un errore di codifica che impedisce l'avvio, ecc.) e può fare cose semplici senza molte altre impostazioni.

Fondamentalmente un'applicazione di KISS.

[E, naturalmente, il motivo principale è: perché no?]


3
Come ho detto, non ne sono affatto convinto. Gli oggetti non vengono istanziati prima, e il codice Viene eseguito prima. Avrebbe bisogno di un preventivo di uno degli sviluppatori originali per convincermi che questo era il motivo.
Konrad Rudolph,

2
La quantità di lavoro necessaria per creare un'istanza di una classe dal codice C è praticamente identica alla chiamata di un metodo statico. Devi anche fare gli stessi controlli (esiste la classe? Va bene, ha un costruttore pubblico con la firma giusta? bene poi vai avanti).
Voo,

Non è necessario creare alcun oggetto utente . Il costruttore dell'oggetto non viene eseguito. L'API è straordinariamente semplice. Ed è il più facile da capire.
Daniel R Hicks,

0

Per la mia comprensione il motivo principale è semplice. Sun era una società Unix che vendeva macchine Unix e Unix è ciò per cui è stata progettata la convenzione C "main (args)" per invocare un binario .

Inoltre, Java è stato esplicitamente progettato per essere facile da imparare per i programmatori C e C ++, quindi non c'erano buoni motivi per non semplicemente prendere la convenzione C.

L'approccio scelto in cui ogni classe può avere un metodo di invocazione è abbastanza flessibile, specialmente in combinazione con la Main-Classlinea nel file MANIFEST.MF in un vaso eseguibile.


Naturalmente, il file jar non è stato inventato fino a molto tempo dopo.
Daniel R Hicks,

-1

Non è coerente con la filosofia OOP che un programma sarebbe un oggetto dal punto di vista del processo del sistema operativo, poiché non c'è modo di avere più di uno per definizione.

Inoltre, un costruttore non è assolutamente un punto di accesso.

Mi sembra la scelta più ragionevole avere main come funzione statica, che in realtà è alla fine della giornata. Data l'architettura di macchine virtuali come JVM e CLR, qualsiasi altra scelta la spingerebbe inutilmente.


1
Penso che ti sbagli lì. Si è possibile avere più di un processo, quindi più di un oggetto. Per inciso, questo è del tutto equivalente all'istanza di Runnableoggetti per avere più thread.
Konrad Rudolph,

Un processo è un programma in esecuzione e puoi avviare un processo solo una volta tramite un punto di ingresso. Le discussioni hanno i loro punti di ingresso ma sono ancora all'interno dello stesso processo.
Yam Marcovic,

1
Come ho detto sotto la risposta di Someguy, questo non è rilevante. Ciò che è rilevante è la coerenza logica . Logicamente, i processi sono rappresentati come oggetti dal programma di avvio (OS, JVM, qualunque cosa) e vengono inizializzati.
Konrad Rudolph,

@KonradRudolph Vero, ma l'inizializzazione di un programma è solo una parte dell'inizializzazione di un processo e non legittima un costruttore di programmi.
Yam Marcovic,
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.