Warum eine statische Hauptmethode in Java und C # anstelle eines Konstruktors?


54

Ich suche nach einer endgültigen Antwort von einer primären oder sekundären Quelle, um herauszufinden, warum (insbesondere) Java und C # beschlossen haben, eine statische Methode als Einstiegspunkt zu verwenden, anstatt eine Anwendungsinstanz durch eine Instanz einer ApplicationKlasse (mit dem Einstiegspunkt) darzustellen ein geeigneter Konstruktor sein).


Hintergrund und Details meiner bisherigen Forschung

Dies wurde schon früher gefragt. Leider werfen die vorhandenen Antworten nur die Frage auf . Insbesondere die folgenden Antworten befriedigen mich nicht, da ich sie für falsch halte:

  • Es würde Mehrdeutigkeit geben, wenn der Konstruktor überladen wäre. - Tatsächlich lässt C # (sowie C und C ++) unterschiedliche Signaturen zu, Mainsodass dieselbe potenzielle Mehrdeutigkeit besteht und behandelt wird.
  • Eine staticMethode bedeutet, dass keine Objekte instanziiert werden können, bevor die Reihenfolge der Initialisierung klar ist. - Dies ist nur sachlich falsch, einige Objekte werden vorher instanziiert (zB in einem statischen Konstruktor).
  • Sie können also von der Laufzeit aufgerufen werden, ohne dass ein übergeordnetes Objekt instanziiert werden muss. - Das ist überhaupt keine Antwort.

Nur um weiter zu begründen, warum ich dies für eine berechtigte und interessante Frage halte:

  • Viele Frameworks tun Klassen verwenden , um Anwendungen zu repräsentieren, und Konstrukteuren als Einstiegspunkte. Beispielsweise verwendet das VB.NET-Anwendungsframework einen dedizierten Hauptdialog (und dessen Konstruktor) als Einstiegspunkt 1 .

  • Weder Java noch C # benötigen technisch eine Hauptmethode. Nun, C # braucht eines zum Kompilieren, aber Java nicht einmal das. Und in keinem Fall wird es für die Ausführung benötigt. Dies scheint also keine technische Einschränkung zu sein. Und, wie ich im ersten Absatz erwähnt habe, scheint es für eine bloße Konvention seltsamerweise nicht mit dem allgemeinen Entwurfsprinzip von Java und C # vereinbar zu sein.

Um es klar zu sagen, es ist nicht besonders nachteilig , eine statische mainMethode zu haben, es ist einfach merkwürdig , weshalb ich mich fragte, ob dahinter ein technisches Grundprinzip steckt.

Ich bin an einer endgültigen Antwort aus einer primären oder sekundären Quelle interessiert, nicht an bloßen Spekulationen.


1 Obwohl es einen Callback ( Startup) gibt, der dies abfangen kann.


4
@mjfgates Außerdem hatte ich gehofft, klargestellt zu haben, dass dies nicht einfach "warum haben die Leute es nicht so gemacht, wie ich es wollte", und dass mich die Gründe wirklich interessieren.
Konrad Rudolph

2
Für Java halte ich die Argumentation für einfach: Als sie Java entwickelten, wussten sie, dass die meisten Leute, die die Sprache lernen, C / C ++ vorher kennen würden. Daher sieht Java nicht nur wie C / C ++ aus, anstatt wie Smalltalk, sondern hat auch die Idiosynchrese von C / C ++ übernommen (man denke nur an oktale Integer-Literale). Da c / c ++ beide eine Hauptmethode verwenden, machte es unter diesem Gesichtspunkt Sinn, dasselbe für Java zu tun.
Voo

5
@ Jarrod Du bist ungerecht. Ich dachte, ich hätte es ganz klar nicht zu einer Schimpfe gemacht. "Nicht konstruktiv"? Wie? Ich bitte ausdrücklich um Referenzen, nicht nur um wilde Diskussionen. Sie können natürlich nicht zustimmen, dass dies eine interessante Frage ist. Aber wenn diese Art von Fragen OT ist, verstehe ich wirklich nicht, welchen Zweck Programmers.SE erfüllt.
Konrad Rudolph

2
Relevante Metadiskussion .
Yannis

3
Frage: Wenn es sich um ein Anwendungsobjekt handelt, brauchen Sie keine zwei Dinge. 1) Ein Konstruktor. 2) Eine Methode für das Objekt, um Ihre Anwendung auszuführen. Der Konstruktor muss abgeschlossen sein, damit das Objekt gültig und damit ausführbar ist.
Martin York

Antworten:


38

TL; DR

In Java ist der Grund public static void main(String[] args)dafür

  1. Gänschen wollte
  2. der Code, der von jemandem geschrieben wurde, der Erfahrung mit C hat (nicht in Java)
  3. von jemandem ausgeführt werden, der es gewohnt ist, PostScript unter NeWS auszuführen

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

 
Für C # ist die Argumentation sozusagen transitiv ähnlich . Die Sprachentwickler haben die Syntax des Programmeinstiegspunkts für Programmierer aus Java beibehalten . Wie der C # -Architekt Anders Hejlsberg es ausdrückt ,

... unser Ansatz mit C # war einfach, eine Alternative zu Java-Programmierern anzubieten ...

 

Lange Version

oben erweitern und mit langweiligen Referenzen gesichert.

 

Java Terminator Hasta la Vista Baby!

VM Spec, 2.17.1 Start der virtuellen Maschine

... Die Art und Weise, in der die anfängliche Klasse für die Java Virtual Machine angegeben wird, liegt außerhalb des Bereichs dieser Spezifikation. In Hostumgebungen, die Befehlszeilen verwenden, ist es jedoch typisch, dass der vollständig qualifizierte Name der Klasse als angegeben wird Ein Befehlszeilenargument und nachfolgende Befehlszeilenargumente, die als Zeichenfolgen verwendet werden, müssen als Argument für die Methode main angegeben werden. Verwenden Sie beispielsweise das Java 2 SDK von Sun für Solaris, die Befehlszeile

java Terminator Hasta la vista Baby!

startet eine Java Virtual Machine, indem die Methode main of class Terminator(eine Klasse in einem nicht benannten Paket) aufgerufen und ein Array übergeben wird, das die vier Zeichenfolgen "Hasta", "la", "vista" und "Baby!" enthält.

... siehe auch: Anhang: Ich brauche deine Klamotten, deine Stiefel und dein Motorrad

  • Meine Interpretation:
    Zielgerichtete Ausführung für die Verwendung als typische Skripte in der Befehlszeilenschnittstelle.

 

wichtiger Nebenschritt

... das hilft, ein paar falsche Spuren in unserer Untersuchung zu vermeiden.

VM Spec, 1.2 Die Java Virtual Machine

Die Java Virtual Machine kennt die Programmiersprache Java nicht ...

Ich habe oben beim Studium des vorherigen Kapitels - 1.1 Geschichte bemerkt, was ich für hilfreich hielt (aber als nutzlos herausstellte).

  • Meine Interpretation: Die
    Ausführung wird ausschließlich durch die VM-Spezifikation geregelt, die
    ausdrücklich erklärt, dass sie nichts mit der Java-Sprache zu tun hat
    => OK, um JLS und jegliche Java-Sprache überhaupt zu ignorieren

 

Gosling: ein Kompromiss zwischen C und Skriptsprache ...

Basierend auf den obigen Angaben habe ich begonnen, das Web nach JVM-Verlauf zu durchsuchen . Hat nicht geholfen, zu viel Müll in den Ergebnissen.

Dann erinnerte ich mich an Legenden über Gosling und beschränkte meine Suche auf die Gosling JVM-Geschichte .

Eureka! Wie die JVM-Spezifikation entstanden ist

In dieser Keynote des JVM Languages ​​Summit 2008 geht James Gosling auf ... Javas Erschaffung, ... einen Kompromiss zwischen C und Skriptsprache ein ...

  • Meine Interpretation:
    explizite Erklärung, dass zum Zeitpunkt der Erstellung
    C und Scripting als wichtigste Einflüsse betrachtet wurden.
     
    Bereits Nicken zu Scripting in VM Spec 2.17.1, gesehen
    hinreichend erklären Befehlszeilenargumente String[] args
    aber staticund mainsind noch nicht da, müssen weiter graben ...

Beachten Sie, während Sie dies eingeben - indem Sie C, Scripting und VM Spec 1.2 mit dem Nichts von Java verbinden -, dass ich das Gefühl habe, dass etwas Vertrautes, etwas ... Objektorientiertes langsam vergeht. Nimm meine Hand und beweg dich weiter. Mach nicht langsamer, wir sind fast da

Die Keynote-Folien sind online verfügbar: 20_Gosling_keynote.pdf. Dies ist sehr praktisch, um wichtige Punkte zu kopieren.

    Seite 3

        Die Vorgeschichte von Java
        * Was hat mein Denken geprägt?

    Seite 9

        Nachrichten
        * Networked Extensible Window System
        * Ein auf Scripting basierendes Fenstersystem ....
          PostScript (!!)

    Seite 16

        Ein großes (aber ruhiges) Ziel:
          Wie nah könnte ich an eine ... herankommen?
          "scripting" fühlen ...

    Seite 19

        Das ursprüngliche Konzept
        * War alles über das Bauen
          Netzwerke von Dingen,
          orchestriert durch ein Skript
          Sprache
        * (Unix-Shells, AppleScript, ...)

    Seite 20

        Ein Wolf im Schafspelz
        * C-Syntax, um Entwickler zu machen
          gemütlich

Aha! Schauen wir uns die C-Syntax genauer an .

Das "Hallo, Welt" Beispiel ...

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

... wird eine Funktion namens main definiert. Die Hauptfunktion erfüllt in C-Programmen einen besonderen Zweck; Die Laufzeitumgebung ruft die Hauptfunktion auf, um die Programmausführung zu starten.

... Die Hauptfunktion hat tatsächlich zwei Argumente, int argcund char *argv[], jeweils, der verwendet werden kann , um Befehlszeilenargumente Griff ...

Kommen wir näher? Sie wetten. Es lohnt sich auch, dem "Haupt" -Link aus dem obigen Zitat zu folgen:

Die Hauptfunktion ist, wo ein Programm die Ausführung startet. Es ist für die übergeordnete Organisation der Programmfunktionalität verantwortlich und hat normalerweise Zugriff auf die Befehlsargumente, die dem Programm bei seiner Ausführung gegeben wurden.

  • Meine Interpretation:
    Um für C-Entwickler komfortabel zu sein, muss der Programmeinstiegspunkt sein main.
    Da Java erfordert, dass eine Methode in der Klasse enthalten ist, Class.mainist es
    so nah wie möglich: statischer Aufruf, nur Klassenname und Punkt,
    bitte keine Konstruktoren - C weiß nichts dergleichen.
     
    Dies gilt auch transitiv für C #, wobei
    die Idee einer einfachen Migration von Java zu C # berücksichtigt wird .

Leser, die der Meinung sind, dass der Einstiegspunkt in ein vertrautes Programm keine Rolle spielt, werden gebeten, nach Stack Overflow-Fragen zu suchen und zu suchen, bei denen Jungs aus Java SE versuchen, Hello World für Java ME MIDP zu schreiben . Hinweis Der MIDP-Einstiegspunkt hat kein mainNor static.

 

Fazit

Basierend auf oben ich sagen würde static, mainund String[] argswar in den Momenten von Java und C # Schaffung vernünftigste Wahl zu definieren Programmeinstiegspunkt .

 

Anhang: Ich brauche deine Kleidung, deine Stiefel und dein Motorrad

Zugegeben , das Lesen von VM Spec 2.17.1 hat riesigen Spaß gemacht.

... die Kommandozeile

java Terminator Hasta la vista Baby!

startet eine Java Virtual Machine, indem die Methode main of class Terminator(eine Klasse in einem nicht benannten Paket) aufgerufen und ein Array übergeben wird, das die vier Zeichenfolgen "Hasta", "la", "vista" und "Baby!" enthält.

Wir skizzieren nun die Schritte, die die virtuelle Maschine ausführen kann Terminator, als Beispiel für die Lade-, Verknüpfungs- und Initialisierungsprozesse, die in späteren Abschnitten weiter beschrieben werden.

Der erste Versuch ... stellt fest, dass die Klasse Terminatornicht geladen ist ...

Nachdem Terminatorgeladen wurde, muss es initialisiert werden, bevor main aufgerufen werden kann, und ein Typ (Klasse oder Schnittstelle) muss immer verknüpft werden, bevor es initialisiert wird. Das Verknüpfen (§2.17.3) beinhaltet die Überprüfung, Vorbereitung und (optional) Auflösung ...

Die Überprüfung (§2.17.3) prüft, ob die geladene Darstellung von Terminatorwohlgeformt ist ...

Die Auflösung (§2.17.3) ist der Prozess des Überprüfens von symbolischen Referenzen aus der Klasse Terminator...

 
Symbolische Verweise von Terminatoroh yeah.


2
Aus irgendeinem Grund fiel es mir schwer zu glauben, dass "Modernität" ein tatsächliches Wort war.
Irgendwann

@Songo Geschichte der Antwort ist auch wie ein Film. Es wurde zum ersten Mal bei meta veröffentlicht , in einer Diskussion über das Schließen von Fragen: "Wenn die Frage wieder geöffnet würde, würde ich wahrscheinlich eine Antwort wie unten schreiben ..." Dann wurde es verwendet, um den Aufruf zur Wiedereröffnung zu unterstützen und schließlich hierher zu ziehen
gnat

16

Das fühlt sich für mich nur vage beleidigend an. Ein Konstruktor wird zur Initialisierung eines Objekts verwendet: Er richtet ein Objekt ein, das dann von dem Code verwendet wird, der es erstellt hat.

Wenn Sie grundlegende Verwendungsfunktionen in den Konstruktor integrieren und dann niemals das Objekt verwenden, das der Konstruktor in externem Code erstellt, verstoßen Sie gegen die Prinzipien von OOP. Grundsätzlich macht man etwas wirklich seltsames ohne ersichtlichen Grund.

Warum würdest du das sowieso wollen?


5
Aber ist die „Anwendungsinstanz“ nicht logisch ein Objekt? Warum sollte das missbräuchlich sein? Die Verwendung des Objekts hat einen Zweck: die laufende Anwendung darzustellen. Klingt sehr SoC -y mir. "Warum sollten Sie das tun?" - Ich bin nur an der Begründung für die Entscheidung interessiert, da ich finde, dass sie im Widerspruch zur restlichen Mentalität steht.
Konrad Rudolph

7
@KonradRudolph: Es wird allgemein erwartet, dass ein Konstruktor wie ein Eigenschafts-Getter in einer begrenzten Zeit abgeschlossen wird, ohne auf das Auftreten eines asynchronen Ereignisses (wie Benutzereingaben) zu warten. Es wäre möglich, einen Konstruktor zu haben, der einen Hauptanwendungsthread startet, der jedoch eine Komplexität hinzufügt, die möglicherweise nicht für alle Anwendungen erforderlich ist. Das Erfordernis, dass eine Konsolenanwendung, die einfach "Hallo Welt" auf die Standardausgabe druckt, einen zusätzlichen Thread erzeugt, wäre doof. Die Verwendung einer MainMethode eignet sich für den einfachen Fall und ist in schwierigeren Fällen kein wirkliches Problem. Warum also nicht?
Superkatze

9

Für Java halte ich die Argumentation für einfach: Bei der Entwicklung von Java wussten die Entwickler, dass die meisten Leute, die die Sprache lernen, C / C ++ bereits im Voraus kennen würden.

Daher sieht Java nicht nur wie C / C ++ aus, anstatt wie Smalltalk, sondern hat auch die Idiosynchrese von C / C ++ übernommen (man denke nur an oktale Integer-Literale). Da c / c ++ beide eine Hauptmethode verwenden, machte es unter diesem Gesichtspunkt Sinn, dasselbe für Java zu tun.

Ich bin mir ziemlich sicher, dass ich mich erinnere, dass Bloch oder jemand in diesem Sinne etwas darüber gesagt hat, warum er oktale Integer-Literale hinzugefügt hat. Ich werde sehen, ob ich einige Quellen finden kann :)


2
Wenn auf der Suche war die gleiche wie C ++ , so viel wichtiger für Java, warum sie zum Beispiel veränderte sich :zu extends? Und public static void main(String [ ] args)innerhalb einer Klasse ist ganz anders als int main(int argc, char **argv)außerhalb einer Klasse.
Svick

2
@svick Eine Möglichkeit: Java hat Schnittstellen eingeführt, und sie wollten die beiden Konzepte (Vererben von Schnittstellen / Klassen) eindeutig trennen - mit nur einem "Schlüsselwort", das nicht funktioniert. Und "ganz anders"? Es ist die nächstmögliche Zuordnung und ich habe bisher noch nie gesehen, dass ein C ++ - Programmierer ein Problem damit hat, zu verstehen, dass die statische Hauptmethode der Einstiegspunkt ist. Im Gegensatz zu einer Klasse namens Application oder etwas, dessen Konstruktor verwendet wird, sieht dies für die meisten C ++ - Programmierer seltsam aus.
Voo

@svick int in c to void in java musste mit angeben, wie ein Rückgabecode aus einer Anwendung generiert wurde - in Java die 0, sofern nicht System.exit (int) aufgerufen wird. Die Änderung der Parameter hat damit zu tun, wie Arrays von Strings in jeder Sprache übergeben werden. Alles in Java ist in einer Klasse - es gibt keine Möglichkeit, es woanders zu haben. Der Wechsel :zu extendsist eine Frage der Syntax und im Wesentlichen identisch. Alles andere wird von der Sprache vorgegeben.

@MichaelT Aber all das sind Designentscheidungen, die Java von C ++ unterscheiden. Warum sollte es also wichtig sein, Java so wie C ++ zu lassen main(), wenn es in anderen Fällen anscheinend nicht wichtig genug war?
Svick

@svick Abgesehen davon, dass es völlig in Ordnung ist, auch in C nichts von main zurückzugeben, und solche Kleinigkeiten niemanden irgendwie verwirren würden. Es ging nicht darum, c ++ und all seine Fehler neu zu erstellen, sondern den Programmierer zu Hause zu machen. Was kann ein C ++ - Programmierer Ihrer Meinung nach leichter lesen: Java oder Objective-C-Code? Was wird Ihrer Meinung nach für einen C ++ - Programmierer offensichtlicher sein, wenn er eine Hauptmethode oder einen Konstruktor einer Klasse als Einstiegspunkt verwendet?
Voo

6

Nun, es gibt viele Hauptfunktionen, die nur eine Endlosschleife durchlaufen. Ein Konstruktor, der auf diese Weise arbeitet (mit einem Objekt, das niemals konstruiert wird), erscheint mir seltsam.

Es gibt so viele lustige Dinge an diesem Konzept. Ihre Logik läuft auf einem ungeborenen Objekt, Objekten, die geboren wurden, um zu sterben (da sie alle ihre Arbeit im Konstruktor erledigen), ...

Würden nicht all diese Nebenwirkungen den OO-Wagen viel mehr beschädigen als eine einfache öffentliche (weil sie von einem unbekannten Benutzer aufgerufen werden muss) statische (weil wir keine Instanz benötigen, um loszulegen) Leere Haupt (weil es der Einstiegspunkt ist) )?

Damit ein einfacher, einfacher Funktionseinstiegspunkt in Java vorhanden ist, werden automatisch öffentliche und statische Funktionen benötigt. Obwohl es sich um eine statische Methode handelt , läuft sie darauf hinaus, was wir einer einfachen Funktion näher bringen können, um das zu erreichen, was gewünscht wird: einen einfachen Einstiegspunkt.

Wenn Sie keinen einfachen Funktionseinstiegspunkt als Einstiegspunkt verwenden möchten. Was kommt als nächstes, das als Konstrukteur, der nicht konstruiert werden soll, nicht seltsam erscheint?


1
Ich würde sagen, das Problem war, keine erstklassigen Funktionen zu haben. Das Einfügen von main () in ein Objekt (das nicht instanziiert wird, bevor main aufgerufen wird) ist ein gewisses Anti-Pattern. Vielleicht sollten sie ein "application" -Objekt haben, das konstruiert wird und dessen nicht-statische main () -Methode ausführt, dann können Sie die Startinitialisierung in den Konstruktor einfügen, und es würde sich viel besser anfühlen als statische Methoden, obwohl es eine einfache top-Methode ist. = level main () fn wäre auch gut. Static Main ist insgesamt ein bisschen kludge.
Gbjbaanb

3

Sie können während der Entwicklung schnell einige eigenständige Tests für eine Klasse ausführen, indem Sie ein main()in die Klasse einfügen, die Sie testen möchten.


1
Dies ist für mich wahrscheinlich der überzeugendste Grund, da dadurch auch mehrere Einstiegspunkte während der Entwicklung zum Testen verschiedener Konfigurationen usw. möglich sind.
cgull

0

Du musst irgendwo anfangen. Ein statisches main ist die einfachste Ausführungsumgebung, die Sie haben können - es muss keine Instanz von irgendetwas (außerhalb der JVM und der einfachen Zeichenfolgenparameter) erstellt werden -, damit es mit einem Minimum an Aufwand (und geringer Wahrscheinlichkeit) "erstellt" werden kann eines Codierungsfehlers, der das Starten verhindert, usw.) und kann einfache Dinge ohne viele andere Einstellungen ausführen.

Grundsätzlich eine Anwendung von KISS.

[Und natürlich ist der Hauptgrund: Warum nicht?]


3
Davon bin ich, wie gesagt, überhaupt nicht überzeugt. Objekte haben instanziiert , bevor erhalten, und Code wird vor ausgeführt. Ich brauche ein Zitat von einem der ursprünglichen Entwickler, um mich davon zu überzeugen, dass dies der Grund ist.
Konrad Rudolph

2
Der Arbeitsaufwand für die Instanziierung einer Klasse aus C-Code ist nahezu identisch mit dem Aufrufen einer statischen Methode. Sie müssen sogar dieselben Überprüfungen durchführen (existiert die Klasse? Gut, hat sie einen öffentlichen Konstruktor mit der richtigen Signatur? gut, dann mach weiter).
Voo

Es muss kein Benutzerobjekt erstellt werden. Der Objektkonstruktor wird nicht ausgeführt. Die API ist unglaublich einfach. Und es ist am einfachsten zu verstehen.
Daniel R Hicks

0

Nach meinem Verständnis ist der Hauptgrund einfach. Sun war ein Unix-Unternehmen, das Unix-Maschinen verkaufte, und für Unix wurde die C- Konvention "main (args)" zum Aufrufen einer Binärdatei entwickelt.

Darüber hinaus wurde Java explizit so konzipiert, dass es für C- und C ++ - Programmierer einfach zu erlernen ist. Es gab also keinen guten Grund, nicht einfach die C-Konvention aufzugreifen.

Der gewählte Ansatz, bei dem jede Klasse eine Aufrufmethode haben kann, ist sehr flexibel, insbesondere in Kombination mit der Main-ClassZeile in der Datei MANIFEST.MF in einem ausführbaren Jar.


Natürlich wurde die JAR-Datei erst viel später erfunden.
Daniel R Hicks

-1

Es entspricht nicht der OOP-Philosophie, dass ein Programm aus Sicht des Betriebssystemprozesses ein Objekt ist, da es per Definition keine Möglichkeit gibt, mehr als eines zu haben.

Darüber hinaus ist ein Konstruktor keinesfalls ein Einstiegspunkt.

Es scheint mir die vernünftigste Wahl zu sein, main als statische Funktion zu haben, die es eigentlich am Ende des Tages ist. Angesichts der Architektur von VMs wie JVM und CLR würde jede andere Wahl diese unnötig vorantreiben.


1
Ich denke du liegst da falsch. Es ist möglich, mehr als einen Prozess zu haben, also mehr als ein Objekt. Dies entspricht im Übrigen der Instanziierung von RunnableObjekten mit mehreren Threads.
Konrad Rudolph

Ein Prozess ist ein laufendes Programm, und Sie können einen Prozess nur einmal über einen Einstiegspunkt starten. Threads haben ihre eigenen Einstiegspunkte, befinden sich jedoch immer noch im selben Prozess.
Yam Marcovic

1
Wie ich unten sagte, ist die Antwort von jemandem nicht relevant. Entscheidend ist die logische Konsistenz. Logischerweise werden Prozesse vom Launcher (OS, JVM, was auch immer) als Objekte dargestellt und initialisiert.
Konrad Rudolph

@KonradRudolph Stimmt, aber die Initialisierung eines Programms ist nur ein Teil der Initialisierung eines Prozesses und legitimiert keinen Programmkonstruktor.
Yam Marcovic
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.