Dlaczego Java ma metody „void”?

51

Czy / dlaczego Java musi mieć voidmetody? Odniesienie :

Żadna metoda zadeklarowana jako nieważna nie zwraca wartości.

O ile myślę, każde użycie voidbyłoby lepiej obsługiwane przez zwrócenie flagi statusu, wywołanego obiektu lub null.

To sprawiłoby, że każde wywołanie byłoby stwierdzeniem, które można przypisać, i ułatwiłoby budowanie wzorców i tworzenie łańcuchów metod. Metody wywoływane tylko dla ich efektów zwykle zwracają wartość logiczną lub Successtyp ogólny lub zgłaszają wyjątek w przypadku niepowodzenia.

marisbest2
źródło
Abyśmy nie potrzebowali specjalnej składni do pisania podprogramów. Możemy użyć tej samej funkcji.
candied_orange
Odpowiedni
jpmc26

Odpowiedzi:

158

Ponieważ istnieje różnica między „Ta funkcja może odnieść sukces lub zawieść i jest na tyle świadoma, że ​​może odróżnić” i „Nie ma informacji zwrotnej na temat działania tej funkcji”. Bez voidtego bez końca sprawdzasz kody sukcesu i wierzysz, że piszesz solidne oprogramowanie, podczas gdy w rzeczywistości nie robisz nic takiego.

Kilian Foth
źródło
10
Ale to, czy się powiedzie, czy nie, jest czymś, co można wyrazić, rzucając wyjątek lub nie, nie zwracając flagi. A powrót voidnie mówi nic o tym, czy metoda lub funkcja jest wystarczająco samoświadoma, aby rzucić odpowiednio.
Volker Siegel
12
@VolkerSiegel: Kontekstem odpowiedzi jest pytanie. OP zasugerował, że we wszystkich przypadkach, w których używana jest wartość void, funkcja powinna zamiast tego zwrócić flagę stanu wskazującą powodzenie awarii. W tym kontekście powracająca pustka rzeczywiście mówi, że funkcja dosłownie nie ma nic do zwrócenia. Wymuszenie, aby taka funkcja zawsze zwracała 0 w celu wskazania sukcesu, da użytkownikom tej funkcji fałszywą pewność, że sprawdzają błędy, podczas gdy w rzeczywistości wartość zwracana wynosi zawsze 0, niezależnie od powodzenia lub niepowodzenia.
slebetman
19
@VolkerSiegel Istnieje wiele sytuacji, w których wyjątki nie są poprawnym sposobem działania. W rzeczywistości powiedziałbym, że rzadsze jest, aby wyjątek był dobry niż zły - wyjątek oznacza, że ​​wydarzyło się coś strasznego, co należy uwzględnić. Oczekiwany przypadek niepowodzenia nigdy nie powinien wykorzystywać wyjątku.
Gabe Sechan
35
@ GabeSechan Nie, należy zgłaszać wyjątki, jeśli metoda nie jest w stanie wykonać umowy. Jeśli metoda wymaga niepustej referencji, ale ją otrzyma, jest to oczekiwana awaria i powinna zostać wygenerowana.
Andy,
5
Istnieje wiele alternatywnych składni, aby to naprawić. Wybrali to (niezręczne) rozwiązanie, ponieważ C je stosuje.
Marco van de Voort,
95
  1. Ponieważ C ma voidtyp, a Java została zaprojektowana zgodnie z wieloma konwencjami rodziny języków C.
  2. Istnieje wiele funkcji, których nie chcesz, aby zwracały wartość. Co w ogóle zrobisz z „ Successtypem ogólnym ”? W rzeczywistości zwracane wartości wskazujące na sukces są nawet mniej ważne w Javie niż w C, ponieważ Java ma wyjątki wskazujące na niepowodzenie, a C nie.
Mason Wheeler
źródło
8
> „Co zamierzasz zrobić z„ ogólnym typem sukcesu ”? - podczas pisania kodu ogólnego, jeden typ wartości (nazywa się on Typem jednostki btw) jest bardzo przydatny, ponieważ eliminuje specjalny przypadek funkcji, które „po prostu zwracają”, ale nie zwracają niczego konkretnego. Ale kiedy Java została stworzona po raz pierwszy, miała jeszcze mniej wyrazisty system typów (bez parametrów typu), więc nie było praktycznej różnicy. A teraz jest już za późno na zmiany. Bardziej współczesne języki faktycznie unikają pustego „typu” (w rzeczywistości nie jest to nawet typ, tylko specjalny znacznik do metod) i zamiast tego używają Typu jednostki.
Sarge Barszcz
9
@SargeBorsch VoidTyp Java działa jako typ Jednostki (np. Dla Generics), choć niestety różni się od void(uwaga: przytulanka umiera za każdym razem, gdy wymyślisz nowy typ, aby naprawić typ, który pomieszałeś w poprzedniej wersji). Jeśli ktoś nie jest zaznajomiony z typami jednostek, jest to przyzwoite wprowadzenie: en.wikipedia.org/wiki/Unit_type
Ogre Psalm33
1
@ OgrePsalm33 tak naprawdę nie działa jako typ Jednostki (przynajmniej w praktyczny sposób - trzeba jeszcze napisać „return null;”, aby powrócić z metody, która ma typ zwracany przez Void, a kompilator AFAIK przydziela miejsce na stosie dla referencji Void , nie wykorzystując faktu, że nigdy nie ma sensu czytać tych wartości), jest to tylko konwencja, aby używać go tam, gdzie „właściwy” typ jednostki byłby bardziej odpowiedni.
Sarge Barszcz
3
@immibis, ponieważ typ jednostki z definicji musi mieć dokładnie jedną wartość, a Void nie ma wartości. Tak, jest możliwe użycie null(w rzeczywistości jest to jedyny wybór), ale null jest wyjątkową rzeczą, każda wartość typu odwołania w Javie może być null (jest to kolejny błąd w projektowaniu języka). I, jak powiedziałem wcześniej, jeśli używasz Voidtypu return zamiast voidznaku, kompilator Java nie jest twoim przyjacielem.
Sarge Barszcz
5
@ SargeBorsch Void ma dokładnie jedną wartość, czyli null. To, że nulljest członkiem większości typów, nie ma znaczenia, czy Voidjest to typ jednostki.
user253751
65

Oryginalny powód, dla którego język ma voidtyp jest bo jak w C, twórcy języka nie chce niepotrzebnie komplikować składnię języka z procedury s oraz funkcja ów drodze Pascal zrobił.

To był pierwotny powód.

Twoje sugestie:

zwracanie flagi stanu

To nie-nie. Nie używamy flag stanu. Jeśli coś pójdzie nie tak, zgłaszamy to w drodze wyjątków.

obiekt jest wywoływany

Płynny styl wywoływania jest najnowszym osiągnięciem. Wielu programistów, którzy dzisiaj bardzo chętnie posługują się biegle i przewracają oczami, jeśli kiedykolwiek będą musieli użyć interfejsu, który go nie obsługuje, nie powstało nawet podczas tworzenia Java.

zwracanie wartości null

Zmusiłoby cię to do zadeklarowania metody jako zwracania czegoś, podczas gdy w rzeczywistości nic nie zwraca, więc byłoby to bardzo mylące dla kogoś, kto patrzy na interfejs próbujący dowiedzieć się, co on robi. Ludzie nieuchronnie wymyśliliby jakąś bezużyteczną klasę oznaczającą „brak wartości zwracanej”, aby wszystkie funkcje, które nie mają nic do zwrócenia, mogły zwrócić zerowe odwołanie do takiej klasy. To byłoby niezgrabne. Na szczęście istnieje na to rozwiązanie, nazywa się to void. I to jest odpowiedź na twoje pytanie.

Mike Nakis
źródło
3
Podaj źródło „Pierwotnego powodu ...”. Według mojej wiedzy prawdziwym powodem jest to, że C został zaprojektowany jako przenośny asembler.
Thorbjørn Ravn Andersen
6
@ ThorbjørnRavnAndersen, czy naprawdę tak naprawdę prosisz mnie o podanie źródła twierdzenia, że ​​składnia java pochodzi ze składni języka C?
Mike Nakis,
3
@ ThorbjørnRavnAndersen och, rozumiem, źle zrozumiałem. Chcesz więc źródła mojego roszczenia dotyczącego C, a nie Javy. Ok, przepraszam, nie mam na to źródła. Ale myślę, że to raczej oczywiste. (Programowałem w Pascalu w 1987 roku, kiedy przestawiłem się na C. Mój Boże, to 30 lat temu.)
Mike Nakis,
6
To nie jest oczywiste. C został opracowany mniej więcej w tym samym czasie co pascal przez ludzi, którzy najprawdopodobniej nawet nie wiedzieli, że istnieje. Nie podawaj założeń jako faktów.
Thorbjørn Ravn Andersen
12
Rzeczywistym pierwotną przyczyną, że przez standardowe funkcje C powrócił inttak kluczowe dodano następnie do K i R „nie wskazuje typ zwrotny”.
user207421
20

Niektóre metody, takie jak System.out.println, nie zwracają niczego użytecznego, ale są nazywane wyłącznie dla tego efektu ubocznego. voidjest pomocnym wskaźnikiem dla kompilatora i czytnika kodu, że żadna użyteczna wartość nie jest zwracana.

Zwrot nullzamiast voidoznacza, że ​​po prostu NullPointerExceptionużyjesz tej wartości do czegokolwiek. Wymieniasz więc błąd czasu kompilacji na błąd czasu wykonywania, który jest znacznie gorszy. Ponadto należy zdefiniować typ zwrotu jako Object, co byłoby mylące i mylące. (A łańcuchowanie nadal nie działałoby.)

Kody stanu są zwykle używane do wskazania warunków błędu, ale Java ma wyjątki w tym celu.

Zwrot thisnie jest możliwy z metod statycznych.

Zwrócenie ogólnego Successobiektu nie miałoby pożytecznego celu.

JacquesB
źródło
1
Całkowicie się z tobą zgadzam, najprostsza i zwięzła odpowiedź.
webo80
11

Jednym z powodów jest to, że zwrócenie czegoś innego niż, powiedzmy, może być mylącenull . Przykład:

Co powinieneś Arrays.sort(a)zwrócić?

Jeśli argumentujesz, że anależy zwrócić, aby połączenie mogło zostać powiązane (co wydaje się być twoją odpowiedzią na podstawie twojego pytania), to nie jest już jasne, czy zwracana wartość jest kopią oryginalnego obiektu, czy też samego obiektu oryginalnego . Oba są możliwe. I tak, możesz umieścić to w dokumentacji, ale jest to na tyle dwuznaczne, że nie powinieneś tworzyć dwuznaczności.

Z drugiej strony, jeśli argumentujesz, że nullpowinien zostać zwrócony, nasuwa się pytanie o to, jakie informacje potencjalnie zwraca wartość zwracająca i dlaczego programista powinien być zmuszony do pisania, return nullgdy nie przekazuje żadnych informacji.

A jeśli coś innego całkowicie absurdalne (jak długość powrócić a) to właśnie sprawia, że za pomocą tej wartości zwracanej naprawdę skomplikowane - wystarczy pomyśleć o ileż bardziej skomplikowane to znaczy int len = Arrays.sort(a)zamiast int len = A.length!

Mehrdad
źródło
1
Mówiąc szczerze, programista niekoniecznie musiałby pisać return null;- JVM mógłby równie dobrze nakazać, aby w przypadku, gdy kontrola wypadnie z końca funkcji w sposób inny niż jawny returnlub throwinstrukcja, nullpośrednio zwracana była do programu wywołującego. IIRC C robi to, z tą różnicą, że wartość zwracana jest niezdefiniowana w tym przypadku (będzie to dowolna wartość, która stanie się w miejscu używanym w tym czasie dla wartości zwracanej).
CVn
2
Prawidłowa odpowiedź na twoje pogrubione pytanie brzmi: „posortowana tablica”.
Bryan Boettcher
2
@BryanBoettcher: Nie przeczytałeś reszty?
Mehrdad
1
@Michael Kjörling: podstawową właściwością języka Java jest zapobieganie takim błędom, tj. Brak „niezdefiniowanego zachowania” w przypadku zapomnienia returninstrukcji. Zamiast tego pojawia się błąd kompilatora informujący o pomyłce. Wprowadzenie niejawnego return null;byłoby lepsze niż „niezdefiniowane zachowanie”, ale niewiele. Przydałoby się to tylko wtedy, gdy typ zwracany nie ma znaczenia, ale skąd kompilator wyciąga wniosek, że wartość zwracana nie ma znaczenia, gdy nie jest void?
Holger
2
@ MichaelKjörling: „Jeśli returnnaprawdę nie dodaje żadnej wartości…”, cóż, jak już powiedziano, nie ma żadnego wskaźnika dla kompilatora, że ​​zwrot nie dodałby żadnej wartości, jeśli nie ma żadnego voidtypu. W rzeczywistości jest odwrotnie, pierwsze założenie jest takie, że metoda deklarująca typ zwracany chce returnczegoś użytecznego tego typu. I nie rozmawialiśmy jeszcze o typach pierwotnych, które nie mają nullwartości, dlatego każda wartość domyślna wprowadzona przez kompilator byłaby w konflikcie z zakresem potencjalnie użytecznych wartości zwracanych.
Holger
7

Zwracanie wartości, booleanktóra jest zawsze równa, truektórą sugerujesz, jest nie tylko bezcelowe (wartość zwracana nie zawiera żadnych informacji), ale w rzeczywistości byłaby myląca. Przez większość czasu, to najlepiej używać typów powrotne, które wykonują tylko tyle informacji, ile jest faktycznie dostępny - dlatego w Javie masz booleanna true/ falsezamiast Zwracanie intz 4 miliardów możliwych wartości. Podobnie zwrócenie a booleanz dwiema możliwymi wartościami, w przypadku których istnieje tylko jedna możliwa wartość („ogólny sukces”), byłoby mylące.

Zwiększyłoby to również niepotrzebny narzut wydajności.

Jeśli metoda jest używana tylko ze względu na efekt uboczny, nie ma nic do zwrócenia, a typ zwrotu voiddokładnie to odzwierciedla. Potencjalne rzadkie błędy mogą być sygnalizowane za pomocą wyjątków.

Jedną rzeczą, którą można ulepszyć, byłoby stworzenie voidrzeczywistego typu, takiego jak Scala Unit. Rozwiązałoby to niektóre problemy, takie jak obsługa leków generycznych.

Michał Kosmulski
źródło
Ciekawostka: List.addzawsze wraca true, ale to za zgodność z szerszą umową Collection.add, więc Set.addmoże wrócić truelub false.
Holger
4

Java jest stosunkowo starym językiem. A w 1995 r. (Kiedy został utworzony), a wkrótce potem programiści byli bardzo zaniepokojeni ilością czasu, jaką proces przejął kontrolę nad procesorem, a także zużyciem pamięci. Zwrócenie void wyeliminowałoby kilka cykli zegara i trochę zużycia pamięci z wywołania funkcji, ponieważ nie musiałbyś umieszczać wartości zwracanej na stosie, a następnie nie musiałbyś jej usuwać.

Efektywny kod nie daje ci czegoś, czego nigdy nie używałbyś, a zatem unieważnienie czegoś, co ma bezwartościową wartość zwrotną, jest znacznie lepszą praktyką niż zwracanie wartości sukcesu.

Lazy Coder
źródło
14
programiści, którzy bardzo martwili się szybkością, i tak nie używaliby Javy, ponieważ pierwsze implementacje Javy były notorycznie wolne. Tak wolno, że wiele osób, które obecnie z nim nie pracują, nadal ma wrażenie, że Java jest synonimem grubego i wolnego.
Sarge Barszcz
8
@SargeBorsch przypomina mi stary żart programistyczny sprzed 20 lat. „Puk, puk”. "Kto tam?" ... ... ... ... bardzo długa pauza ... ... ... "Java"
Dan Neely
4
@ Dan Niezwykle i jakiś czas później samochód napędzany sztuczną inteligencją uderza z pełną prędkością w ścianę, nawet nie próbując użyć hamulców. Został napisany w Javie. Zatem Java nie hamuje teraz! (to była gra słów w języku rosyjskim, nie można jej bardzo dobrze przetłumaczyć na angielski)
Sarge Borsch
2
@SargeBorsch To, co masz, działa jako angielski kalambur, nawet jeśli stracił trochę niuansów w tłumaczeniu. Tłumaczenie kalamburów jest prawdopodobnie trudniejsze niż poezja.
Dan Neely
3
Dobrzy programiści wciąż martwią się wydajnością i zużyciem pamięci, co sprawia, że ​​niepotrzebnie prosi się ludzi o zwrot śmieci nawet dzisiaj.
Sklivvz
2

Przeczytałem argumenty sugerujące, że drugą opcją (return this) powinno być podejście zamiast void. Jest to całkiem dobry pomysł, ale podejście w stylu konstruktora nie było popularne w czasie tworzenia Java. Gdyby w tym czasie był tak popularny, jak teraz, mogłoby to być przyjęte podejście. Powrót nullto naprawdę zły pomysł IMO. Chciałbym, żeby nullw ogóle nie było tego języka.

Problem polega na tym, że jeśli metoda zwraca instancję własnego typu, nie jest jasne, czy jest to nowy obiekt, czy ten sam. Często nie ma (lub nie powinien) mieć znaczenia, ale innym razem ma to znaczenie. Podejrzewam, że język mógłby zostać zmieniony, aby domyślnie powrócić thisdo nieważnych metod. Jedyną kwestią, o której mogę teraz pomyśleć, jest to, że jeśli później metoda zostanie zmieniona w celu zwrócenia nowego obiektu tego samego typu, nie pojawią się żadne ostrzeżenia dotyczące kompilacji, ale może to nie jest wielka sprawa. Obecny system typów nie przejmuje się teraz tego rodzaju zmianami. Sposób interakcji z dziedziczeniem / interfejsami wymagałby pewnych rozważań, ale pozwoliłby na łatwe wywoływanie starych interfejsów API bez stylu konstruktora, tak jakby to zrobiły.

JimmyJames
źródło
2
@Cody Dobrze, że nie dotyczy to metod statycznych.
JimmyJames
14
@ marisbest2 Który argument?
8bittree 24.04.17
3
Cóż, gdyby nullnie były częścią języka, ludzie używaliby wszelkiego rodzaju wartości wartowników, co oznacza „proszę zignoruj ​​tę argument / wartość zwracaną, nie zawiera ona żadnej sensownej wartości”. Problemem nullnie jest sama rzecz, tylko to, że często jest używane nieprawidłowo. Założę się, że ten problem byłby przesadzony tylko wtedy, gdyby znormalizowany nullzostałby zastąpiony niezliczonymi niestandardowymi rozwiązaniami, w których każda implementacja zachowywałaby się inaczej jak cicho ignorując użycie lub rzucając specjalne wyjątki zamiast standardowego wyjątku wskaźnika zerowego itp.
cmaster
2
@cmaster oczywistym zamiennikiem nulljest właściwy typ opcjonalny (jak Maybew Haskell). Problem z zerami w Javie polega na tym, że nie ma rozsądnego sposobu, aby od razu zdecydować, czy wartość może być zerowana w praktyce, czy nie. Prowadzi to zarówno do: niepotrzebnie defensywnego programowania (sprawdzanie wartości zerowych tam, gdzie jest to bezcelowe, zwiększając procent kupy w kodzie), jak i przypadkowych NPE w produkcji (jeśli zapomniałem sprawdzić, gdzie było potrzebne). Tak, jest częściowo rozwiązany za pomocą adnotacji, ale są one opcjonalne, nie zawsze sprawdzane itp. Więc nie będą cię oszczędzać na granicach z kodem strony trzeciej.
Sarge Barszcz
2
@SargeBorsch Zgadzam się z tym :-) Mój sprzeciw dotyczy języka bez standardowego sposobu wyrażenia „proszę zignorować tę wartość”. nulldziała dobrze w tym przypadku (dobrze = prosto), ale znormalizowane wartości opcjonalne z solidną semantyką są zdecydowanie kolejną dobrą opcją (dobrze = bezpiecznie).
cmaster
2

Kolejny aspekt:

Java to język statyczny z dość ścisłymi funkcjami. Oznacza to, że wiele rzeczy, które byłyby dość otwarte lub dynamiczne w innych językach (c / f Ruby, Lisp itp.) Są ściśle określone.

Jest to ogólna decyzja projektowa. Na „dlaczego” trudno jest odpowiedzieć (cóż, ponieważ projektanci języka sądzili, że będzie dobrze!). „Po co” jest dość jasne: pozwala kompilatorowi wykryć wiele błędów, co ogólnie jest całkiem dobrą cechą dla każdego języka. Po drugie, sprawia, że ​​stosunkowo łatwo jest zrozumieć język. Na przykład stosunkowo łatwo jest stworzyć formalne dowody poprawności w (podzestawach) języka Java; dla porównania byłoby to praktycznie niemożliwe w dynamicznym języku, takim jak Ruby i in.

Takie myślenie przenika język, na przykład wymuszoną deklarację możliwych wyjątków, które może rzucić metoda, osobny typ interfacekontra, classaby uniknąć niejednoznacznego wielokrotnego dziedziczenia i tak dalej. W rzeczywistości (statyczny imperialny język OOP w świecie rzeczywistym z silnym naciskiem na obsługę błędów w czasie kompilacji) te rzeczy są naprawdę dość eleganckie i potężne. Zbliżają się do języków teoretycznych (naukowych), które zostały celowo stworzone wyłącznie po to, aby zbadać niektóre z tych zagadnień niż jakikolwiek inny język rzeczywisty wcześniej (w tym czasie, pamiętajcie).

Więc. Posiadanie ścisłego voidtypu jest wyraźnym komunikatem: ta metoda niczego nie zwraca, kropka. Jest jak jest. Zastąpienie go wymuszeniem, aby zawsze zwracało coś, prowadziłoby do znacznie bardziej dynamicznego zachowania (tak jak w Ruby, gdzie każdy def ma zawsze wyraźną lub dorozumianą wartość zwrotu), co byłoby niekorzystne z punktu widzenia sprawdzalności i uzasadnienia; lub do masywnego wzdęcia za pomocą innego mechanizmu tutaj.

(I na przykład, Ruby (na przykład) radzi sobie z tym inaczej, a jego rozwiązanie jest nadal tak samo akceptowalne jak Java, ponieważ ma zupełnie inną filozofię. Na przykład wyrzuca możliwości sprawdzania się i rozsądności całkowicie poza okno, kładąc duży nacisk na wyjątkowo wysoka ekspresja języka).

AnoE
źródło