Dlaczego Java nie pozwala, aby definicje funkcji były obecne poza klasą?


22

W przeciwieństwie do C ++, w Javie nie możemy mieć po prostu deklaracji funkcji w klasie i definicji poza klasą. Dlaczego tak jest

Czy należy podkreślić, że pojedynczy plik w Javie powinien zawierać tylko jedną klasę i nic więcej?



1
Przez definicjach masz na myśli właściwości lub metody podpisów ala plików nagłówkowych?
Deco

Dobra satyryczna odpowiedź na twoje pytanie: steve-yegge.blogspot.com/2006/03/…
Brandon

Odpowiedzi:


33

Różnica między C ++ a Javą polega na tym, że języki uważają ich najmniejszą jednostkę powiązania.

Ponieważ C zaprojektowano do współistnienia z asemblerem, ta jednostka jest podprogramem wywoływanym przez adres. (Dotyczy to innych języków, które kompilują się do rodzimych plików obiektowych, takich jak FORTRAN.) Innymi słowy, plik obiektowy zawierający funkcję foo()będzie miał wywołany symbol, _fooktóry zostanie rozwiązany na adres taki jak adres0xdeadbeefpodczas łączenia. To wszystko. Jeśli funkcja ma pobierać argumenty, to do dzwoniącego należy upewnić się, że wszystko, czego oczekuje funkcja, jest w porządku przed wywołaniem jej adresu. Zwykle odbywa się to przez ułożenie rzeczy na stosie, a kompilator zajmuje się chrząknięciem i upewnia się, że prototypy są zgodne. Nie sprawdza się tego między plikami obiektowymi; jeśli przejdziesz w górę połączenia, połączenie nie zakończy się zgodnie z planem i nie otrzymasz ostrzeżenia o tym. Pomimo niebezpieczeństwa umożliwia to łączenie plików obiektowych skompilowanych z wielu języków (w tym asemblera) w działający program bez większego zamieszania.

C ++, pomimo całej swojej dodatkowej funkcjonalności, działa w ten sam sposób. Przestrzenie nazw kompilatora, klasy i metody / członkowie / etc. do tej konwencji, spłaszczając zawartość klas do pojedynczych nazw, które są zniekształcone w sposób, który czyni je wyjątkowymi. Na przykład metoda taka Foo::bar(int baz)może zostać zniekształcona _ZN4Foo4barEipo umieszczeniu w pliku obiektowym i adresie jak 0xBADCAFEw czasie wykonywania. Jest to całkowicie zależne od kompilatora, więc jeśli spróbujesz połączyć dwa obiekty o różnych schematach manipulacji, nie będziesz miał szczęścia. Choć jest to brzydkie, oznacza to, że możesz użyć extern "C"bloku, aby wyłączyć mangling, dzięki czemu kod C ++ może być łatwo dostępny dla innych języków. C ++ odziedziczył pojęcie swobodnie pływających funkcji po C, głównie dlatego, że pozwala na to macierzysty format obiektów.

Java to inna bestia, która żyje w izolowanym świecie z własnym formatem plików obiektowych - .classplikiem. Pliki klas zawierają bogactwo informacji o ich zawartości, które pozwalają środowisku robić rzeczy z klasami w czasie wykonywania, o których natywny mechanizm łączenia nie mógł nawet marzyć. Ta informacja musi gdzieś zacząć, a punktem wyjścia jestclass. Dostępne informacje pozwalają skompilowanemu kodowi opisywać się bez potrzeby oddzielnych plików zawierających opis w kodzie źródłowym, tak jak w C, C ++ lub innych językach. Daje to wszystkie rodzaje korzyści związanych z bezpieczeństwem przy użyciu natywnego braku powiązań, nawet w czasie wykonywania, i jest tym, co pozwala wyłowić dowolną klasę z pliku za pomocą odbicia i używać go z gwarantowaną awarią, jeśli coś się nie zgadza .

Jeśli jeszcze tego nie rozgryzłeś, całe to bezpieczeństwo wiąże się z kompromisem: wszystko, co łączysz z programem Java, musi być Java. (Przez „link” mam na myśli, że zawsze, gdy coś w jednym pliku klasy odnosi się do czegoś w innym.) Możesz połączyć (w sensie natywnym) do kodu natywnego za pomocą JNI , ale istnieje niejawna umowa, która mówi, że jeśli złamiesz stronę natywną , posiadasz oba elementy.

Java po raz pierwszy była duża i niezbyt szybka na dostępnym sprzęcie, podobnie jak Ada w poprzedniej dekadzie. Tylko Jim Gosling może z całą pewnością powiedzieć, jakie były jego motywacje do stworzenia najmniejszej jednostki powiązania Javy w klasie, ale musiałbym zgadywać, że dodatkowa złożoność dodania darmowych zmiennoprzecinkowych elementów do środowiska wykonawczego mogłaby być zabójcą transakcji.


link uszkodzony, pobierz link do archiwum internetowego
noɥʇʎԀʎzɐɹƆ

14

Wierzę, że odpowiedź brzmi, według Wikipedii, że Java została zaprojektowana jako prosta i zorientowana obiektowo. Funkcje mają działać na klasach, w których są zdefiniowane. Przy takim sposobie myślenia posiadanie funkcji poza klasą nie ma sensu. Idę do wniosku, że Java nie pozwala na to, ponieważ nie pasuje do czystego OOP.

Szybkie wyszukiwanie w Google nie przyniosło wiele motywacji do projektowania języka Java.


6
Czy istnienie pierwotnych typów nie wyklucza, że ​​Java jest „czystym OOP”?
Radu Murzea

5
@SoboLAN: Tak, a dodanie funkcji sprawiłoby, że byłoby jeszcze mniej „czystego OOP”.
Giorgio

8
@SoboLAN, który zależy od twojej definicji „czystego OOP”. W pewnym momencie wszystko jest w końcu kombinacją bitów w pamięci komputera ...
jwenting

3
@jwenting: Cóż, celem abstrakcji w językach programowania jest jak największe ukrycie ukrytych bitów. Im więcej widzisz tych bitów w swoim programie, tym bardziej powinieneś zacząć myśleć, że twój język programowania oferuje nieszczelne abstrakty (chyba że celowo jest zbudowany blisko metalu). Tak, wszystko sprowadza się do manipulowania bitami, ale różne języki oferują różne poziomy abstrakcji, w przeciwnym razie nie byłoby w stanie odróżnić asemblera od języka wyższego poziomu.
Giorgio

2
Zależy od twojej definicji „czystego OOP”: każda wartość danych jest obiektem, a każda operacja danych jest wywołaniem metody wynikowej uzyskanym przez przekazanie komunikatu. O ile wiem, nie ma w tym nic więcej.
Giorgio

11

Prawdziwe pytanie brzmi: jaka byłaby zaleta kontynuowania robienia rzeczy w języku C ++ i jaki był pierwotny cel pliku nagłówkowego? Krótka odpowiedź jest taka, że ​​styl pliku nagłówkowego pozwala na szybsze czasy kompilacji w dużych projektach, w których wiele klas może potencjalnie odwoływać się do tego samego typu. Nie jest to konieczne w JAVA i .NET ze względu na naturę kompilatorów.

Zobacz odpowiedź tutaj: czy pliki nagłówkowe są naprawdę dobre?


1
+1, chociaż faktycznie słyszałem, jak ludzie mówią, że lubią mieć oddzielny interfejs publiczny od prywatnej implementacji zapewnianej przez pliki nagłówkowe. Ci ludzie oczywiście się mylą. ;)
vaughandroid

6
@Baqueta W Javie możesz to osiągnąć za pomocą interfacei class:) Nie potrzebujesz nagłówków!
Andres F.,

1
Nigdy nie zrozumiem potrzeby przeglądania plików 3+, aby zrozumieć, jak działa prosta instancja klasy.
Erik Reppen

@ErikReppen zwykle interfejs (lub plik nagłówkowy w C) to rzecz, którą klienci otrzymują w formie czytelnej dla użytkownika, aby napisać swoje rozwiązania, a reszta jest dostarczana tylko w formie binarnej (oczywiście w Javie, nie ma potrzeby podawania źródeł interfejsów, plików klas i javadoc zrobią).
jwenting

@jwenting Nie myślałem o tej sprawie. Przyzwyczaiłem się bardziej do myślenia o kolejnym biednym draniu, który musi utrzymywać tę głupią bazę kodu, ponieważ zbyt często to ja w kolejnej pracy dźgam oczy po spędzaniu godzin na oglądaniu jakiegoś dziwacznego interfejsu i wesołej podklasy / superklasy architektura okrągła.
Erik Reppen

3

Plik Java reprezentuje klasę. Gdybyś miał procedurę poza klasą, jaki byłby zakres? Czy byłoby to globalne? A może należałoby do klasy reprezentowanej przez plik Java?

Prawdopodobnie umieścisz go w tym pliku Java zamiast w innym pliku z jakiegoś powodu - ponieważ pasuje on do tej klasy bardziej niż do jakiejkolwiek innej klasy. Jeśli procedura poza klasą była faktycznie powiązana z tą klasą, to dlaczego nie zmusić jej do wejścia do tej klasy, do której należy? Java obsługuje tę metodę jako metodę statyczną w klasie.

Gdyby dozwolona była procedura spoza klasy, prawdopodobnie nie miałaby specjalnego dostępu do klasy, w której pliku została zadeklarowana, ograniczając ją do funkcji narzędzia, która nie zmienia żadnych danych.

Jedynym możliwym minusem tego ograniczenia Java jest to, że jeśli naprawdę masz globalne procedury, które nie są powiązane z żadną klasą, ostatecznie tworzysz klasę MyGlobals, która je przechowuje, i importujesz tę klasę do wszystkich innych plików, które używają tych procedur .

W rzeczywistości mechanizm importujący Java wymaga tego ograniczenia, aby mógł funkcjonować. Przy wszystkich dostępnych interfejsach API kompilator Java musi dokładnie wiedzieć, co skompilować i z czym skompilować, a zatem jawne instrukcje importowania u góry pliku. Bez konieczności grupowania globalnych w klasie sztucznego, w jaki sposób poinformować kompilator Javy do kompilacji swoich globalnych, a nie wszelkich globalne na ścieżce klas? Co z kolizją przestrzeni nazw, w której masz doStuff (), a ktoś inny ma doStuff ()? To by nie działało. Zmuszenie do określenia MyClass.doStuff () i YourClass.doStuff () rozwiązuje te problemy. Zmuszenie procedur do wejścia do MyClass zamiast na zewnątrz wyjaśnia tylko to ograniczenie i nie nakłada dodatkowych ograniczeń na kod.

Java ma wiele błędów - serializacja ma tak wiele małych brodawek, że jest prawie zbyt trudna do bycia użytecznym (pomyśl SerialVersionUID). Może być również używany do przełamywania singletonów i innych typowych wzorców projektowych. Metoda clone () na Object powinna być podzielona na deepClone () i shallowClone () i być bezpieczna dla typu. Wszystkie klasy API mogły zostać domyślnie unieruchomione (tak jak są w Scali). Ale ograniczenie, że wszystkie procedury muszą należeć do klasy, jest dobre. Służy przede wszystkim do uproszczenia i wyjaśnienia języka i kodu bez nakładania uciążliwych ograniczeń.


3

Myślę, że większość osób, które odpowiedziały i ich wyborcy, źle zrozumieli pytanie. Odzwierciedla to, że nie znają C ++.

„Definicja” i „Deklaracja” to słowa o bardzo specyficznym znaczeniu w C ++.

OP nie oznacza zmiany sposobu działania Java. To pytanie dotyczy wyłącznie składni. Myślę, że to ważne pytanie.

W C ++ istnieją dwa sposoby definiowania funkcji członka.

Pierwszy sposób to Java. Wystarczy umieścić cały kod w nawiasach klamrowych:

class Box {
public:
    // definition of member function
    void change(int newInt) { 
        this._m = newInt;
    }
private:
    int _m
}

Drugi sposób:

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;
}

Oba programy są takie same. Ta funkcja changejest nadal funkcją składową: nie istnieje poza klasą. Ponadto definicja klasy MUSI zawierać nazwy i typy WSZYSTKICH funkcji członkowskich i zmiennych, tak jak w Javie.

Jonathan Henson ma rację, że jest to artefakt sposobu, w jaki nagłówki działają w C ++: pozwala na umieszczanie deklaracji w pliku nagłówkowym i implementacjach w osobnym pliku .cpp, aby twój program nie naruszał ODR (reguły jednej definicji). Ale poza tym ma swoje zalety: pozwala na pierwszy rzut oka zobaczyć interfejs dużej klasy.

W Javie można przybliżyć ten efekt za pomocą klas abstrakcyjnych lub interfejsów, ale nie mogą one mieć takiej samej nazwy jak klasa implementacji, co czyni go raczej niezdarnym.


i to pokazuje, że oboje nie rozumiecie ludzi ani Javy. Możesz użyć tej samej nazwy dla interfejsu i implementacji, o ile nie są one w tym samym pakiecie.
jwenting

Uważam pakiet (lub przestrzeń nazw lub klasa zewnętrzna) za część nazwy.
Erik van Velzen,

2

Myślę, że to artefakt mechanizmu ładowania klasy. Każdy plik klasy jest kontenerem dla obiektu, który można załadować. Nie ma miejsca „poza” plikami klas.


1
Nie rozumiem, dlaczego zorganizowanie kodu źródłowego w osobne jednostki uniemożliwiłoby zachowanie formatu pliku klasy w jego obecnej postaci.
Mat.

istnieje zgodność 1: 1 między plikami klas i plikami źródłowymi, co jest jedną z lepszych decyzji projektowych w całym systemie.
ddyer

@ddyer Nigdy nie widziałeś Foo 2 $ 1. klasa? (patrz nazwy plików klas wewnętrznych Java )

Czy to sprawi, że będzie to mapowanie „na”? w każdym razie każda klasa jest generowana na podstawie kompilacji dokładnie jednego pliku źródłowego, co jest dobrą decyzją projektową.
ddyer

0

C #, który jest bardzo podobny do Javy, ma tego rodzaju funkcje dzięki zastosowaniu metod częściowych, z tym wyjątkiem, że metody częściowe są wyłącznie prywatne.

Metody częściowe: http://msdn.microsoft.com/en-us/library/6b0scde8.aspx

Częściowe klasy i metody: http://msdn.microsoft.com/en-us/library/wa80x488.aspx

Nie widzę żadnego powodu, dla którego Java nie mogłaby zrobić tego samego, ale prawdopodobnie sprowadza się to tylko do tego, czy istnieje zapotrzebowanie ze strony użytkowników, aby dodać tę funkcję do języka.

Większość narzędzi do generowania kodu dla C # generuje klasy częściowe, dzięki czemu deweloper może w razie potrzeby łatwo dodać ręcznie napisany kod do klasy w osobnym pliku.


0

C ++ wymaga, aby cały tekst klasy był kompilowany jako część każdej jednostki kompilacji, która korzysta z dowolnego jego elementu lub tworzy dowolne instancje. Jedynym sposobem na zachowanie rozsądnego czasu kompilacji jest, aby tekst samej klasy zawierał - w możliwym zakresie - tylko te rzeczy, które są rzeczywiście niezbędne dla jego konsumentów. Fakt, że metody C ++ są często pisane poza klasami, które je zawierają, jest naprawdę paskudnym podłym hackiem motywowanym faktem, że wymaganie od kompilatora przetworzenia tekstu każdej metody klasy raz dla każdej jednostki kompilacji, w której klasa jest używana, doprowadziłoby do całkowitego szalone czasy budowania.

W Javie skompilowane pliki klas zawierają między innymi informacje, które są w dużej mierze równoważne plikowi C ++ .h. Konsumenci klasy mogą wyodrębnić wszystkie potrzebne informacje z tego pliku, bez konieczności kompilacji przez plik kompilatora .java. W przeciwieństwie do C ++, gdzie plik .h zawiera informacje, które są udostępniane zarówno implementacjom, jak i klientom klas w nim zawartych, przepływ w Javie jest odwrócony: plik używany przez klientów nie jest plikiem źródłowym używanym do kompilacji kod klasy, ale jest generowany przez kompilator przy użyciu informacji z pliku kodu klasy. Ponieważ nie ma potrzeby dzielenia kodu klasy między plik zawierający informacje potrzebne klientom i plik zawierający implementację, Java nie pozwala na taki podział.


-1

Myślę, że częścią tego jest to, że Java jest bardzo protekcjonistycznym językiem, który dotyczy samego siebie w dużych zespołach. Klasy nie można zastąpić ani przedefiniować. Masz 4 poziomy modyfikatorów dostępu, które określają bardzo szczegółowo, w jaki sposób metody mogą i nie mogą być używane. Wszystko jest silnie / statycznie wpisane, aby chronić deweloperów przed hijinxem niedopasowania typów spowodowanym przez innych lub przez nich samych. Posiadanie klas i funkcji jako najmniejszych jednostek znacznie ułatwia odkrywanie paradygmatu, jak zacząć budować aplikację.

Porównaj z JavaScriptem, gdzie pierwszorzędne funkcje rzeczownika / czasownika podwójnie opadają i są przekazywane jak cukierek z rozpiętej pinaty, konstruktor funkcji równoważny klasie może mieć zmieniony prototyp dodając nowe właściwości lub zmieniając stare dla instancji, które zostały już utworzone w dowolnym momencie, absolutnie nic nie stoi na przeszkodzie, aby zastąpić dowolny konstrukt własną wersją, istnieje około 5 typów i są one automatycznie konwertowane i automatycznie oceniane w różnych okolicznościach i możesz dołączyć nowe właściwości do cholernie blisko:

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

Ostatecznie chodzi o nisze i rynki. JavaScript jest tak samo odpowiedni i katastrofalny w rękach 100 deweloperów (z których wiele będzie prawdopodobnie miernych) deweloperów, ponieważ Java była kiedyś w rękach małych grup deweloperów próbujących pisać w nim interfejs WWW. Kiedy pracujesz ze 100 programistami, ostatnią rzeczą, jakiej chcesz, jest odkrycie tego przeklętego paradygmatu. Kiedy pracujesz z interfejsem użytkownika lub szybszy rozwój jest bardziej krytyczny, ostatnią rzeczą, której chcesz, jest zablokowanie możliwości robienia stosunkowo prostych rzeczy po prostu dlatego, że byłoby to bardzo głupie, gdybyś nie był ostrożny.

Pod koniec dnia oba są popularnymi językami ogólnego użytku, więc jest tu trochę argumentu filozoficznego. Moją największą osobistą wołowiną z Javą i C # jest to, że nigdy nie widziałem ani nie pracowałem ze starszą bazą kodu, w której większość programistów zdawała się rozumieć wartość podstawowego OOP. Kiedy wchodzisz do gry i musisz owijać wszystko w klasę, być może łatwiej jest założyć, że robisz OOP, niż funkcjonować spaghetti w przebraniu masywnych łańcuchów 3-5 linii.

To powiedziawszy, nie ma nic bardziej okropnego niż JavaScript napisany przez kogoś, kto wie tylko, że jest niebezpieczny i nie boi się go pochwalić. I myślę, że to jest ogólny pomysł. Ten facet podobno musi grać w Javie według tych samych zasad. Twierdziłbym, że ten drań zawsze znajdzie sposób.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.