Dlaczego statyczna główna metoda w Javie i C #, a nie konstruktor?

54

Szukam ostatecznej odpowiedzi z pierwotnego lub wtórnego źródła, dlaczego (zwłaszcza) Java i C # zdecydowały się na metodę statyczną jako punkt wejścia, zamiast reprezentować instancję aplikacji przez instancję Applicationklasy (z punktem wejścia bycie odpowiednim konstruktorem).


Tło i szczegóły moich wcześniejszych badań

Zostało to już wcześniej zadane. Niestety istniejące odpowiedzi są jedynie pytaniem . W szczególności poniższe odpowiedzi mnie nie satysfakcjonują, ponieważ uważam je za nieprawidłowe:

  • Byłoby niejednoznaczne, gdyby konstruktor został przeciążony. - W rzeczywistości C # (jak również C i C ++) pozwala na różne podpisy, Maindlatego istnieje taka sama potencjalna niejednoznaczność i jest ona rozpatrywana.
  • staticMetoda oznacza, że nie obiekty mogą być tworzone wystąpienia przed tak kolejność inicjalizacji jest jasne. - Jest to po prostu nieprawdziwe, niektóre obiekty wcześniej tworzone (np. W konstruktorze statycznym).
  • Dzięki temu mogą być wywoływane przez środowisko wykonawcze bez konieczności tworzenia instancji obiektu nadrzędnego. - To w ogóle nie jest odpowiedź.

Aby jeszcze bardziej uzasadnić, dlaczego uważam, że jest to ważne i interesujące pytanie:

  • Wiele ram zrobić użyć klas do reprezentowania aplikacji i konstruktorów jako punkty wejścia. Na przykład środowisko aplikacji VB.NET używa dedykowanego głównego okna dialogowego (i jego konstruktora) jako punktu wejścia 1 .

  • Ani Java, ani C # technicznie nie potrzebują głównej metody. C # wymaga kompilacji jednego, ale Java nawet tego. I w żadnym przypadku nie jest to konieczne do wykonania. Nie wydaje się to ograniczeniem technicznym. I, jak wspomniałem w pierwszym akapicie, dla samej konwencji wydaje się to dziwnie niezgodne z ogólną zasadą projektowania Java i C #.

Żeby było jasne, nie ma konkretnej wady posiadania mainmetody statycznej , jest to po prostu wyraźnie dziwne , co sprawiło, że zastanawiałem się, czy kryje się za tym jakieś techniczne uzasadnienie.

Interesuje mnie ostateczna odpowiedź z pierwotnego lub wtórnego źródła, a nie zwykłe spekulacje.


1 Chociaż istnieje funkcja zwrotna ( Startup), która może to przechwycić.

Konrad Rudolph
źródło
4
@ mjfgates Miałem również nadzieję, że wyjaśniłem, że nie jest to po prostu „dlaczego ludzie nie zrobili tego tak, jak chcę” i że jestem naprawdę zainteresowany przyczynami.
Konrad Rudolph,
2
Jeśli chodzi o Javę, myślę, że rozumowanie jest proste: podczas programowania w Javie wiedzieli, że większość osób uczących się języka zna wcześniej C / C ++. Dlatego Java nie tylko przypomina C / C ++ zamiast powiedzieć smalltalk, ale także przejęła cechy charakterystyczne od C / C ++ (wystarczy pomyśleć o liczbach całkowitych ósemkowych). Ponieważ oba c / c ++ używają głównej metody, robienie tego samego dla java ma sens z tego punktu widzenia.
Voo,
5
@Jarrod Jesteś niesprawiedliwy. Wydawało mi się, że całkiem wyraźnie nie uczyniłem z tego grzeczności. „Nie konstruktywny”? Jak to? Pytam wprost o referencje, a nie tylko dzikie dyskusje. Oczywiście możesz się nie zgodzić, że jest to interesujące pytanie. Ale jeśli tego rodzaju pytania są tutaj OT, to naprawdę nie widzę, do czego służy Programmers.SE.
Konrad Rudolph,
2
Odpowiednia dyskusja Meta .
yannis,
3
Pytanie: Jeśli jest to obiekt aplikacji, nie potrzebujesz dwóch rzeczy. 1) Konstruktor. 2) Metoda obiektu do uruchomienia aplikacji. Konstruktor musi się zakończyć, aby obiekt był poprawny, a tym samym działał.
Martin York,

Odpowiedzi:

38

TL; DR

Przyczyną tego public static void main(String[] args)jest Java

  1. gosling chciał
  2. kod napisany przez osobę mającą doświadczenie w C (nie w Javie)
  3. do wykonania przez osobę przyzwyczajoną do uruchamiania PostScript na NeWS

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

 
W przypadku C # rozumowanie jest tranzytowo podobne, że tak powiem. Projektanci języków znali składnię punktów wejścia programu znaną programistom pochodzącym z Javy. Jak ujął to architekt C #, Anders Hejlsberg ,

... naszym podejściem do C # było po prostu zaoferowanie alternatywy ... dla programistów Java ...

 

Długa wersja

rozwijając się powyżej i tworząc nudne referencje.

 

java Terminator Hasta la vista Baby!

Specyfikacja VM, 2.17.1 Uruchomienie maszyny wirtualnej

... Sposób, w jaki klasa początkowa jest określana na maszynie wirtualnej Java, wykracza poza zakres tej specyfikacji, ale jest typowy w środowiskach hostów korzystających z wiersza poleceń, aby w pełni kwalifikowana nazwa klasy została określona jako argument wiersza poleceń i kolejne argumenty wiersza polecenia, które mają być użyte jako ciągi znaków jako argument metody main. Na przykład przy użyciu zestawu SDK Java 2 firmy Sun dla systemu Solaris wiersz poleceń

java Terminator Hasta la vista Baby!

uruchomi maszynę wirtualną Java, wywołując metodę main klasy Terminator(klasa w nienazwanym pakiecie) i przekazując jej tablicę zawierającą cztery ciągi znaków „Hasta”, „la”, „vista” i „Baby!” ...

... patrz także: Załącznik: Potrzebuję twoich ubrań, butów i motocykla

  • Moja interpretacja:
    wykonanie ukierunkowane do użycia jak typowe skrypty w interfejsie wiersza poleceń.

 

ważny boczny krok

... pomaga to uniknąć kilku fałszywych śladów w naszym dochodzeniu.

VM Spec, 1.2 The Java Virtual Machine

Wirtualna maszyna Java nic nie zna języka programowania Java ...

Powyżej zauważyłem, studiując poprzedni rozdział - 1.1 Historia, która moim zdaniem może być pomocna (ale okazała się bezużyteczna).

  • Moja interpretacja:
    wykonanie jest regulowane przez samą specyfikację VM, która
    wyraźnie oświadcza, że ​​nie ma to nic wspólnego z językiem Java
    => OK, aby zignorować JLS i dowolny inny język Java

 

Gąsiątko: kompromis między C a językiem skryptowym ...

W oparciu o powyższe zacząłem przeszukiwać sieć w poszukiwaniu historii JVM . Nie pomogło, zbyt wiele śmieci w wynikach.

Potem przypomniałem sobie legendy o Goslingu i zawęziłem poszukiwania do historii JVM Goslinga .

Eureka! Jak powstała specyfikacja JVM

W tym przemówieniu z JVM Languages ​​Summit 2008 James Gosling omawia ... tworzenie Javy, ... kompromis między C i językiem skryptowym ...

  • Moja interpretacja:
    wyraźne oświadczenie, że w momencie tworzenia
    C i skrypt zostały uznane za najważniejsze czynniki.
     
    Już widać ukłon skryptów w VM Spec 2.17.1,
    argumenty linii poleceń dostatecznie wyjaśnić String[] args
    , ale statici mainnie są tam jeszcze, trzeba kopać dalej ...

Zauważ, że pisząc to - łącząc C, skryptowanie i VM Spec 1.2 z nic-of-Java - czuję się jak coś znajomego, coś ... obiektowego powoli odchodzi. Weź mnie za rękę i ruszaj się. Nie zwalniaj, jesteśmy już prawie na miejscu

Slajdy keynote są dostępne online: 20_Gosling_keynote.pdf , całkiem wygodne do kopiowania kluczowych punktów.

    strona 3

        Prehistoria Java
        * Co ukształtowało moje myślenie

    strona 9

        Aktualności
        * Sieciowy system rozszerzalnych okien
        * System okien oparty na skryptach ....
          PostScript (!!)

    strona 16

        Duży (ale cichy) cel:
          Jak blisko mogę się dostać do
          „skryptowanie” wydaje się…

    strona 19

        Oryginalna koncepcja
        * Chodziło o budowę
          sieci rzeczy,
          zaaranżowane przez skrypt
          język
        * (Powłoki Unix, AppleScript, ...)

    strona 20

        Wilk w owczej skórze
        * C składnia do tworzenia programistów
          wygodny

Aha! Spójrzmy bliżej na składni C .

Przykład „witaj, świecie” ...

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

... definiowana jest funkcja main. Główną funkcja służy do celów specjalnych w programach C; środowisko wykonawcze wywołuje główną funkcję, aby rozpocząć wykonywanie programu.

... Główna funkcja faktycznie ma dwa argumenty int argci char *argv[], odpowiednio, można ich użyć do obsługi argumentów wiersza poleceń ...

Czy zbliżamy się? obstawiasz. Warto również kliknąć „główny” link z powyższego cytatu:

główną funkcją jest miejsce, w którym program rozpoczyna wykonywanie. Odpowiada za organizację wysokiego poziomu funkcjonalności programu i zazwyczaj ma dostęp do argumentów poleceń podanych programowi podczas jego wykonywania.

  • Moja interpretacja:
    Aby być wygodnym dla programisty C, punkt wejścia programu musi być main.
    Ponadto, ponieważ Java wymaga, aby każda metoda była w klasie, Class.mainjest
    tak blisko, jak to możliwe: wywołanie statyczne, tylko nazwa klasy i kropka,
    proszę żadnych konstruktorów - C nie wie nic takiego.
     
    Dotyczy to także tranzytowo C #, biorąc pod uwagę
    pomysł łatwej migracji do niego z Java.

Czytelnicy myślący, że znajomy punkt wejścia programu nie ma znaczenia, są proszeni o wyszukiwanie i sprawdzanie pytań dotyczących przepełnienia stosu, w których faceci z Java SE próbują napisać Hello World dla Java ME MIDP. Uwaga Punkt wejścia MIDP nie ma mainani static.

 

Wniosek

W oparciu o powyższe chciałbym powiedzieć, że static, maini String[] argsbyły w chwilach Java i C # tworzenie najbardziej rozsądnych wyborów do definiują programu punktu wejścia .

 

Dodatek: Potrzebuję twoich ubrań, butów i motocykla

Muszę przyznać, że czytanie VM Spec 2.17.1 było niesamowitą zabawą.

... wiersz poleceń

java Terminator Hasta la vista Baby!

uruchomi maszynę wirtualną Java, wywołując metodę main klasy Terminator(klasa w pakiecie bez nazwy) i przekazując jej tablicę zawierającą cztery ciągi znaków „Hasta”, „la”, „vista” i „Baby!”.

Teraz nakreślamy kroki, które może wykonać maszyna wirtualna Terminator, jako przykład procesów ładowania, łączenia i inicjowania, które są opisane w dalszej części.

Pierwsza próba ... odkrywa, że ​​klasa Terminatornie jest załadowana ...

Po Terminatorzaładowaniu należy go zainicjować, aby można było wywołać main, a typ (klasa lub interfejs) musi być zawsze połączony przed jego zainicjowaniem. Łączenie (§2.17.3) obejmuje weryfikację, przygotowanie i (opcjonalnie) rozwiązanie ...

Weryfikacja (§2.17.3) sprawdza, czy załadowana reprezentacja Terminatorjest poprawnie uformowana ...

Rozdzielczość (§2.17.3) to proces sprawdzania symbolicznych odniesień z klasy Terminator...

 
Symboliczne odniesienia od Terminatoro tak.

komar
źródło
2
Z jakiegoś powodu trudno mi było uwierzyć, że „nowoczesność” to prawdziwe słowo.
czasami
@Songo historia odpowiedzi jest również jak film. Został on po raz pierwszy opublikowany na stronie meta w dyskusji na temat zamknięcia pytania: „Gdyby pytanie zostało ponownie otwarte, prawdopodobnie napisałbym odpowiedź taką jak poniżej ...” Następnie wykorzystano go do poparcia odwołania, aby ponownie otworzyć, i ostatecznie przeniósł się tutaj
komar
16

To po prostu wydaje mi się obelżywe. Do inicjalizacji obiektu służy konstruktor: ustawia obiekt, który jest następnie używany przez kod, który go utworzył.

Jeśli umieścisz podstawowe funkcje użytkowania wewnątrz konstruktora, a następnie nigdy nie użyjesz obiektu, który konstruktor tworzy w kodzie zewnętrznym, naruszasz zasady OOP. Zasadniczo robienie czegoś naprawdę dziwnego bez wyraźnego powodu.

Dlaczego i tak chcesz to zrobić?

Mason Wheeler
źródło
5
Ale czy „instancja aplikacji” nie jest logicznie obiektem? Dlaczego miałoby to być obelżywe? Jeśli chodzi o korzystanie z obiektu - ma on jeden cel: reprezentowanie działającej aplikacji. Brzmi dla mnie bardzo SoC . „Dlaczego miałbyś to zrobić?” - interesuje mnie jedynie uzasadnienie tej decyzji, ponieważ nie zgadzam się z resztą mentalności.
Konrad Rudolph,
7
@KonradRudolph: Oczekuje się, że konstruktor, podobnie jak moduł pobierania właściwości, zakończy się w ograniczonym czasie, nie czekając na wystąpienie jakiegoś zdarzenia asynchronicznego (takiego jak dane wejściowe użytkownika). Byłby możliwy konstruktor, który uruchomił główny wątek aplikacji, ale to dodaje poziom złożoności, który może nie być konieczny dla wszystkich aplikacji. Wymaganie, aby aplikacja konsolowa, która po prostu wypisuje „Hello world” na standardowym wyjściu, powinna odrodzić dodatkowy wątek, byłaby głupia. Użycie Mainmetody działa dobrze w prostym przypadku i nie jest tak naprawdę problemem w trudniejszych przypadkach, więc dlaczego nie?
supercat
9

Jeśli chodzi o Javę, to myślę, że rozumowanie jest proste: deweloperzy wiedzieli, że większość osób uczących się języka zna wcześniej C / C ++.

Dlatego Java nie tylko przypomina C / C ++ zamiast powiedzieć smalltalk, ale także przejęła cechy charakterystyczne od C / C ++ (wystarczy pomyśleć o liczbach całkowitych ósemkowych). Ponieważ oba c / c ++ używają głównej metody, robienie tego samego dla java ma sens z tego punktu widzenia.

Jestem pewien, że pamiętam blocha lub kogoś, kto mówi coś w tym stylu o tym, dlaczego dodali ósemkowe liczby całkowite, zobaczę, czy uda mi się znaleźć jakieś źródła :)

Voo
źródło
2
Jeśli wyglądanie tak samo jak C ++ było tak ważne dla Javy, dlaczego na przykład zmienili się :na extends? A public static void main(String [ ] args)wewnątrz klasy jest zupełnie inaczej niż int main(int argc, char **argv)poza klasą.
svick,
2
@svick Jedna możliwość: Java wprowadziła interfejsy i najwyraźniej chcieli oddzielić dwie koncepcje (dziedziczenie interfejsów / klas) - tylko jednym „słowem kluczowym”, które nie zadziała. I „całkiem inny”? Jest to najbliższe możliwe odwzorowanie tego i jak dotąd nigdy nie widziałem, aby programista c ++ miał problem ze zrozumieniem, że statyczna metoda główna jest punktem wejścia. W przeciwieństwie do tego, że posiadanie klasy o nazwie Aplikacja lub czegoś, którego konstruktor jest używany, jest czymś, co wyglądałoby dziwnie dla większości programistów c ++.
Voo,
@svick int in c, aby unieważnić w Javie, musiał ustalić sposób generowania kodu powrotu z aplikacji - w Javie jego wartość 0, chyba że zostanie wywołany System.exit (int). Zmiana parametrów ma związek z tym, jak tablice ciągów są przekazywane w każdym języku. Wszystko w Javie należy do klasy - nie ma opcji, aby mieć to gdzie indziej. Zmiana :na extendsto kwestia składni i zasadniczo są takie same. Cała reszta jest podyktowana językiem.
@MichaelT Ale wszystkie te decyzje projektowe odróżniają Javę od C ++. Dlaczego więc utrzymanie Javy tak samo jak C ++ byłoby ważne w przypadku main(), gdy najwyraźniej nie było wystarczająco ważne w innych przypadkach.
svick,
@svick Tyle, że nie jest w porządku nic nie zwracać również z głównego w C, a takie błahostki i tak nikogo nie dezorientują. Nie chodziło o to, aby odtworzyć c ++ i wszystkie jego błędy, ale tylko po to, aby programista był bardziej w domu. Jak myślisz, co programiści C ++ będą mieli łatwiej czytać: Java lub kod celu-c? Jak myślisz, co będzie bardziej oczywiste dla programisty C ++ jako głównej metody lub konstruktora jakiejś klasy jako punktu wejścia?
Voo,
6

Istnieje wiele głównych funkcji, które po prostu uruchamiają nieskończoną pętlę. Konstruktor działający w ten sposób (z obiektem, który nigdy nie jest konstruowany) wydaje mi się dziwny.

W tej koncepcji jest tyle zabawnych rzeczy. Twoja logika działająca na nienarodzonym obiekcie, obiektach, które urodziły się, aby umrzeć (ponieważ wykonują całą pracę w konstruktorze), ...

Czy wszystkie te skutki uboczne nie spowodowałyby o wiele większego uszkodzenia wozu OO niż zwykły publiczny (ponieważ musi być dostępny przez nieznany) statyczny (ponieważ nie jest potrzebna żadna instancja, aby rozpocząć) void main (ponieważ jest to punkt wejścia )?

Aby prosty, prosty punkt wejścia funkcji istniał w Javie, publiczny i statyczny byłby automatycznie wymagany. Chociaż jest to metoda statyczna , sprowadza się do tego, co możemy zbliżyć do zwykłej funkcji, aby osiągnąć to, czego chcemy: prosty punkt wejścia.

Jeśli nie zamierzasz przyjąć prostego, prostego punktu wejścia funkcji jako punktu wejścia. Co dalej nie wydaje się dziwne jako konstruktor, którego nie zamierza się budować?

pepper_chico
źródło
1
Powiedziałbym, że problemem nie były funkcje pierwszej klasy. Wstawienie main () do obiektu (które nie jest tworzone przed wywołaniem main) jest trochę anty-wzorem. Być może powinny mieć obiekt „aplikacji”, który zostanie skonstruowany i uruchomi swoją niestatyczną metodę main (), a następnie możesz wprowadzić inicjalizację uruchamiania do konstruktora, i byłoby to o wiele lepsze niż posiadanie metod statycznych, chociaż proste = poziom main () fn też byłby dobry. Statyczny main jest trochę kludge.
gbjbaanb
3

Możesz szybko uruchomić niektóre niezależne testy na klasie podczas programowania, umieszczając main()w klasie, którą próbujesz przetestować.

Graham Borland
źródło
1
Jest to dla mnie chyba najbardziej przekonujący powód, ponieważ umożliwia także wiele punktów wejścia podczas programowania do testowania różnych konfiguracji itp.
cgull
0

Musisz gdzieś zacząć. Statyczny element główny jest najprostszym środowiskiem wykonawczym, jakie możesz mieć - nie trzeba tworzyć żadnej instancji (poza JVM i parametry prostego łańcucha) - więc może „wymyślić” minimum zamieszania (i niskie prawdopodobieństwo błędu kodowania uniemożliwiającego uruchomienie itp.) i może robić proste rzeczy bez wielu innych ustawień.

Zasadniczo aplikacja KISS.

[I oczywiście głównym powodem jest: dlaczego nie?]

Daniel R. Hicks
źródło
3
Jak powiedziałem, wcale mnie to nie przekonuje. Obiekty wcześniej tworzone, a kod jest wykonywany wcześniej. Potrzebowałby cytatu od jednego z oryginalnych programistów, aby przekonać mnie, że to był powód.
Konrad Rudolph,
2
Ilość pracy potrzebnej do utworzenia instancji klasy z kodu C jest prawie identyczna jak wywołanie metody statycznej. Musisz nawet wykonać te same kontrole (czy klasa istnieje? Dobrze, czy ma publicznego konstruktora z odpowiednią sygnaturą? dobrze, a następnie śmiało).
Voo,
Nie trzeba tworzyć obiektu użytkownika . Konstruktor obiektów nie jest wykonywany. Interfejs API jest niezwykle prosty. I to jest najłatwiejsze do zrozumienia.
Daniel R Hicks,
0

Według mnie główny powód jest prosty. Sun był firmą uniksową sprzedającą maszyny uniksowe, a Unix jest tym, do czego została zaprojektowana konwencja C „główna (args)” do wywoływania pliku binarnego .

Ponadto Java została specjalnie zaprojektowana, aby była łatwa do pobrania dla programistów C i C ++, więc nie było żadnego dobrego powodu, aby po prostu nie wybierać konwencji C.

Wybrane podejście, w którym każda klasa może mieć metodę wywoływania, jest dość elastyczne, szczególnie w połączeniu z Main-Classlinią w pliku MANIFEST.MF w uruchamialnym słoju.

komar
źródło
Oczywiście plik jar został wymyślony dopiero dużo później.
Daniel R Hicks,
-1

Nie jest zgodne z filozofią OOP, że program byłby obiektem z punktu widzenia procesu systemu operacyjnego, ponieważ nie ma sposobu, aby mieć więcej niż jeden z definicji.

Co więcej, konstruktor w żadnym wypadku nie jest punktem wejścia.

Wydaje mi się, że najbardziej rozsądnym wyborem jest posiadanie funkcji głównej jako funkcji statycznej, którą tak naprawdę jest pod koniec dnia. Biorąc pod uwagę architekturę maszyn wirtualnych, takich jak JVM i CLR, każdy inny wybór byłby niepotrzebnie popychany.

Yam Marcovic
źródło
1
Myślę, że się tam mylisz. Możliwe jest posiadanie więcej niż jednego procesu, a więc więcej niż jednego obiektu. Nawiasem mówiąc, jest to całkowicie równoważne z tworzeniem Runnableobiektów, które mają wiele wątków.
Konrad Rudolph,
Proces jest działającym programem i można go rozpocząć tylko raz za pośrednictwem jednego punktu wejścia. Wątki mają własne punkty wejścia, ale nadal są w tym samym procesie.
Yam Marcovic,
1
Jak powiedziałem poniżej odpowiedź, nie ma to znaczenia. Istotna jest logiczna spójność. Logicznie procesy są reprezentowane przez program uruchamiający jako obiekty (OS, JVM, cokolwiek) i są inicjowane.
Konrad Rudolph,
@KonradRudolph Prawda, ale inicjalizacja programu jest tylko jedną częścią inicjalizacji procesu i nie legitymuje konstruktora programu.
Yam Marcovic