Warum erlaubt Java nicht, dass Funktionsdefinitionen außerhalb der Klasse vorhanden sind?


22

Anders als in C ++ können in Java nicht nur Funktionsdeklarationen in der Klasse und Definitionen außerhalb der Klasse vorhanden sein. Wieso ist es so?

Ist es zu betonen, dass eine einzelne Datei in Java nur eine Klasse und nichts anderes enthalten sollte?



1
Mit Definitionen meinen Sie Eigenschaften oder Methodensignaturen als Header-Dateien?
Deco

Eine gute satirische Antwort auf Ihre Frage: steve-yegge.blogspot.com/2006/03/…
Brandon

Antworten:


33

Der Unterschied zwischen C ++ und Java besteht darin, was die Sprachen als kleinste Verknüpfungseinheit betrachten.

Da C für die Koexistenz mit der Assembly konzipiert wurde, ist diese Unit die von einer Adresse aufgerufene Unterroutine. (Dies gilt auch für andere Sprachen, die in systemeigene Objektdateien kompiliert werden, z. B. FORTRAN.) Mit anderen Worten, eine Objektdatei, die eine Funktion enthält foo(), hat ein Symbol mit dem Namen _foo, das in nichts anderes als eine Adresse wie aufgelöst wird0xdeadbeefwährend der Verlinkung. Das ist alles was es gibt. Wenn die Funktion Argumente annehmen soll, muss der Aufrufer sicherstellen, dass alles, was die Funktion erwartet, in Ordnung ist, bevor er ihre Adresse aufruft. Normalerweise werden dazu Dinge auf den Stapel gestapelt, und der Compiler kümmert sich um die Grunzarbeit und stellt sicher, dass die Prototypen übereinstimmen. Dies wird zwischen den Objektdateien nicht überprüft. Wenn Sie die Anrufverknüpfung vermasseln, läuft der Anruf nicht wie geplant ab und Sie erhalten keine Warnung. Trotz der Gefahr können so aus mehreren Sprachen kompilierte Objektdateien (einschließlich Assembler) ohne großen Aufwand zu einem funktionsfähigen Programm zusammengefügt werden.

C ++ funktioniert trotz aller zusätzlichen Fantasie genauso. Der Compiler erkennt Namespaces, Klassen und Methoden / Member / etc. in diese Konvention einfließen, indem der Inhalt von Klassen in einzelne Namen reduziert wird, die auf eine Weise verkürzt werden, die sie einzigartig macht. Zum Beispiel Foo::bar(int baz)könnte eine Methode wie in entstellt werden, _ZN4Foo4barEiwenn sie in eine Objektdatei und eine Adresse wie 0xBADCAFEzur Laufzeit gestellt wird. Dies hängt vollständig vom Compiler ab. Wenn Sie also versuchen, zwei Objekte mit unterschiedlichen Verwirrungsschemata zu verknüpfen, haben Sie Pech. So hässlich dies ist, bedeutet dies, dass Sie einen extern "C"Block verwenden können, um das Mangeln zu deaktivieren, wodurch es möglich wird, C ++ - Code für andere Sprachen leicht zugänglich zu machen. C ++ hat die Idee der frei schwebenden Funktionen von C geerbt, hauptsächlich weil das native Objektformat dies zulässt.

Java ist ein anderes Biest, das in einer isolierten Welt mit seinem eigenen Objektdateiformat, der .classDatei , lebt . Klassendateien enthalten eine Fülle von Informationen zu ihren Inhalten , die es der Umgebung ermöglichen, zur Laufzeit Dinge mit Klassen zu tun, von denen der systemeigene Verknüpfungsmechanismus nicht einmal träumen konnte. Diese Information muss irgendwo beginnen, und dieser Ausgangspunkt ist derclass. Die verfügbaren Informationen ermöglichen es dem kompilierten Code, sich selbst zu beschreiben, ohne dass separate Dateien mit einer Beschreibung im Quellcode wie in C, C ++ oder anderen Sprachen erforderlich sind. Das gibt Ihnen alle Arten von Sicherheitsvorteilen, die Sprachen mit dem nativen Verknüpfungsmangel erzielen, selbst zur Laufzeit, und ermöglicht es Ihnen, eine beliebige Klasse mit Reflection aus einer Datei zu fischen und sie mit einem garantierten Fehler zu verwenden, wenn etwas nicht übereinstimmt .

Wenn Sie es noch nicht herausgefunden haben, bringt all diese Sicherheit einen Kompromiss mit sich: Alles, was Sie mit einem Java-Programm verknüpfen, muss Java sein. (Mit "verknüpfen" meine ich, wann immer sich etwas in einer Klassendatei auf etwas in einer anderen Datei bezieht.) Sie können mit JNI (im systemeigenen Sinne) auf systemeigenen Code verlinken , aber es gibt einen impliziten Vertrag, der besagt, dass Sie die systemeigene Seite unterbrechen Sie besitzen beide Stücke.

Java war groß und nicht besonders schnell auf der verfügbaren Hardware, als es zum ersten Mal eingeführt wurde, ähnlich wie Ada es im vorigen Jahrzehnt getan hatte. Nur Jim Gosling kann mit Sicherheit sagen, was seine Motivation war, um die kleinste Verbindungseinheit der Klasse Java herzustellen, aber ich muss vermuten, dass die zusätzliche Komplexität, die das Hinzufügen von Streubesitzern zur Laufzeit beigetragen hätte, ein Deal-Killer gewesen sein könnte.


link kaputt, web archive link bekommen
noɥʇʎԀʎzɐɹƆ 16.10.16

14

Ich glaube, die Antwort laut Wikipedia ist, dass Java so konzipiert wurde, dass es einfach und objektorientiert ist. Funktionen sollen auf die Klassen angewendet werden, in denen sie definiert sind. Bei dieser Denkweise macht es keinen Sinn, Funktionen außerhalb einer Klasse zu haben. Ich werde zu dem Schluss kommen, dass Java dies nicht zulässt, weil es nicht zu reinem OOP passt.

Eine schnelle Google-Suche ergab für mich nicht viel zu den Motivationen des Java-Sprachdesigns.


6
Schliesst die Existenz primitiver Typen nicht aus, dass Java "pure OOP" ist?
Radu Murzea

5
@SoboLAN: Ja, und das Hinzufügen von Funktionen würde es noch weniger "pure OOP" machen.
Giorgio

8
@SoboLAN, das von Ihrer Definition von "pure OOP" abhängt. Irgendwann ist doch alles eine Kombination von Bits im Computerspeicher ...
am

3
@jwenting: Nun, der Zweck der Abstraktion in Programmiersprachen ist es, die zugrunde liegenden Bits so weit wie möglich zu verbergen. Je mehr Sie diese Teile in Ihrem Programm sehen können, desto mehr sollten Sie glauben, dass Ihre Programmiersprache undichte Abstraktionen bietet (es sei denn, sie wurde absichtlich in der Nähe des Metalls erstellt). Also, ja, alles läuft darauf hinaus, Bits zu manipulieren, aber verschiedene Sprachen bieten unterschiedliche Abstraktionsebenen, sonst könnten Sie Assembler nicht von einer höheren Sprache unterscheiden.
Giorgio

2
Hängt von Ihrer Definition von "pure OOP" ab: Jeder Datenwert ist ein Objekt, und jede Datenoperation ist der Ergebnismethodenaufruf, der durch Nachrichtenübergabe erhalten wird. Soweit ich weiß, steckt nichts mehr dahinter.
Giorgio

11

Die eigentliche Frage ist, was das Verdienst wäre, weiterhin C ++ zu tun und was der ursprüngliche Zweck der Header-Datei war. Die kurze Antwort lautet, dass der Header-Dateiformat schnellere Kompilierzeiten für große Projekte ermöglichte, in denen viele Klassen möglicherweise auf denselben Typ verweisen könnten. Dies ist in JAVA und .NET aufgrund der Art der Compiler nicht erforderlich.

Sehen Sie diese Antwort hier: Sind Header-Dateien wirklich gut?


1
+1, obwohl ich tatsächlich gehört habe, dass Leute sagen, dass sie die Trennung zwischen öffentlicher Schnittstelle und privater Implementierung mögen, die Header-Dateien bieten. Diese Leute sind natürlich falsch. ;)
Vaughandroid

6
@Baqueta In Java können Sie dies mit interfaceund erreichen class:) Keine Header erforderlich!
Andres F.

1
Ich werde nie verstehen, wie wünschenswert es ist, 3 oder mehr Dateien zu betrachten, um zu verstehen, wie eine einfache Klasseninstanz funktioniert.
Erik Reppen

@ErikReppen Typischerweise erhalten Kunden die Schnittstelle (oder die Header-Datei in C) in benutzerlesbarer Form, in die sie ihre Lösungen schreiben können. Der Rest wird nur in binärer Form bereitgestellt (in Java ist es natürlich nicht erforderlich, die Quellen anzugeben von den Schnittstellen reichen classfiles und javadoc aus).
Jwenting

@jwenting Ich hatte nicht an diesen Fall gedacht. Ich bin eher daran gewöhnt, an den nächsten armen Bastard zu denken, der diese alberne Codebasis aufrechterhalten muss, denn allzu oft bin ich es, der mir beim nächsten Job die Augen erstochen hat, nachdem ich stundenlang auf eine bizarre Schnittstelle und Sub- / Superklasse-Fröhlichkeit geschaut habe Architektur.
Erik Reppen

3

Eine Java-Datei repräsentiert eine Klasse. Wenn Sie eine Prozedur außerhalb der Klasse hätten, welchen Umfang hätte diese? Wäre es global? Oder würde es zu der Klasse gehören, die die Java-Datei repräsentiert?

Vermutlich haben Sie es aus einem bestimmten Grund in diese Java-Datei anstatt in eine andere Datei eingefügt - weil es zu dieser Klasse mehr gehört als zu jeder anderen Klasse. Wenn eine Prozedur außerhalb einer Klasse tatsächlich mit dieser Klasse verknüpft war, können Sie sie dann zwingen, in die Klasse zu wechseln, zu der sie gehört. Java behandelt dies als statische Methode innerhalb der Klasse.

Wenn eine Prozedur außerhalb der Klasse zulässig wäre, hätte sie vermutlich keinen speziellen Zugriff auf die Klasse, in deren Datei sie deklariert wurde, und beschränkt sich somit auf eine Dienstprogrammfunktion, die keine Daten ändert.

Der einzige Nachteil dieser Java-Einschränkung ist, dass Sie, wenn Sie wirklich globale Prozeduren haben, die keiner Klasse zugeordnet sind, eine MyGlobals-Klasse erstellen, um sie zu speichern, und diese Klasse in alle Ihre anderen Dateien importieren, die diese Prozeduren verwenden .

Tatsächlich benötigt der Java-Importmechanismus diese Einschränkung, um zu funktionieren. Bei allen verfügbaren APIs muss der Java-Compiler genau wissen, was zu kompilieren und womit zu kompilieren ist, also die expliziten Importanweisungen oben in der Datei. Wie würden Sie den Java-Compiler anweisen, ohne Ihre Globals in einer künstlichen Klasse zu gruppieren , Ihre Globals und nicht alle Globals auf Ihrem Klassenpfad zu kompilieren ? Was ist mit einer Namespace-Kollision, bei der Sie ein doStuff () haben und eine andere Person ein doStuff ()? Es würde nicht funktionieren. Das Erzwingen, dass Sie MyClass.doStuff () und YourClass.doStuff () angeben, behebt diese Probleme. Durch das Erzwingen, dass Ihre Prozeduren innerhalb von MyClass anstatt außerhalb von MyClass ausgeführt werden, wird diese Einschränkung nur klargestellt, und der Code unterliegt keinen zusätzlichen Einschränkungen.

Java hat eine Reihe von Fehlern begangen - die Serialisierung hat so viele kleine Warzen, dass es fast zu schwierig ist, nützlich zu sein (denken Sie an SerialVersionUID). Es kann auch verwendet werden, um Singletons und andere übliche Entwurfsmuster aufzubrechen. Die clone () -Methode in Object sollte in deepClone () und shallowClone () aufgeteilt und typsicher sein. Alle API-Klassen könnten standardmäßig unveränderlich gemacht worden sein (so wie sie in Scala sind). Die Einschränkung, dass alle Prozeduren zu einer Klasse gehören müssen, ist jedoch gut. Es dient in erster Linie dazu, die Sprache und Ihren Code zu vereinfachen und zu verdeutlichen, ohne lästige Einschränkungen aufzuerlegen.


3

Ich denke, die meisten Menschen, die geantwortet haben, und ihre Wähler haben die Frage falsch verstanden. Es zeigt, dass sie C ++ nicht kennen.

"Definition" und "Deklaration" sind Wörter mit einer ganz bestimmten Bedeutung in C ++.

Das OP soll nicht die Funktionsweise von Java verändern. Dies ist eine reine Syntaxfrage. Ich denke, es ist eine berechtigte Frage.

In C ++ gibt es zwei Möglichkeiten , eine Mitgliedsfunktion zu definieren .

Der erste Weg ist der Java-Weg. Setzen Sie einfach den gesamten Code in die geschweiften Klammern:

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

Zweiter Weg:

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

Beide Programme sind gleich. Die Funktion changeist immer noch eine Mitgliedsfunktion: Sie existiert nicht außerhalb der Klasse. Darüber hinaus MUSS die Klassendefinition die Namen und Typen ALLER Elementfunktionen und -variablen enthalten, genau wie in Java.

Jonathan Henson hat Recht, dass dies ein Artefakt der Funktionsweise von Headern in C ++ ist: Sie können Deklarationen in der Headerdatei und Implementierungen in einer separaten CPP-Datei ablegen, damit Ihr Programm nicht gegen die ODR (One Definition Rule) verstößt. Aber es hat auch andere Vorteile: Sie können die Benutzeroberfläche einer großen Klasse auf einen Blick sehen.

In Java können Sie diesen Effekt mit abstrakten Klassen oder Interfaces approximieren, die jedoch nicht denselben Namen wie die Implementierungsklasse haben können, was sie ziemlich umständlich macht.


und das zeigt, dass Sie beide weder Menschen noch Java verstehen. Sie können denselben Namen für die Schnittstelle und die Implementierung verwenden, sofern diese nicht im selben Paket enthalten sind.
5.

Ich betrachte das Paket (oder den Namespace oder die äußere Klasse) als Teil des Namens.
Erik van Velzen

2

Ich denke, es ist ein Artefakt des Klassenlademechanismus. Jede Klassendatei ist ein Container für ein ladbares Objekt. Es gibt keinen Platz "außerhalb" von Klassendateien.


1
Ich verstehe nicht, warum das Organisieren des Quellcodes in separaten Einheiten verhindern würde, dass das Klassendateiformat unverändert bleibt.
Mat

Es gibt eine 1: 1-Entsprechung zwischen Klassendateien und Quellendateien, was eine der besseren Entwurfsentscheidungen im Gesamtsystem darstellt.
Ddyer

@ddyer Du hast Foo $ 2 $ 1.class noch nie gesehen? (Siehe Java-Klassendateinamen der inneren Klasse )

Wäre das ein "auf" Mapping? In jedem Fall wird jede Klasse aus genau einer Quelldatei generiert. Dies ist eine gute Entwurfsentscheidung.
Ddyer

0

C #, das Java sehr ähnlich ist, verfügt über diese Art von Funktion durch die Verwendung von Teilmethoden, mit der Ausnahme, dass Teilmethoden ausschließlich privat sind.

Teilweise Methoden: http://msdn.microsoft.com/en-us/library/6b0scde8.aspx

Teilklassen und Methoden: http://msdn.microsoft.com/en-us/library/wa80x488.aspx

Ich sehe keinen Grund, warum Java nicht das Gleiche tun könnte, aber es kommt wahrscheinlich nur darauf an, ob von der Benutzerbasis ein Bedarf gesehen wird, diese Funktion der Sprache hinzuzufügen.

Die meisten Codegenerierungstools für C # generieren Teilklassen, sodass der Entwickler der Klasse bei Bedarf problemlos manuell geschriebenen Code in einer separaten Datei hinzufügen kann.


0

C ++ erfordert, dass der gesamte Text der Klasse als Teil jeder Kompilierungseinheit kompiliert wird, die Mitglieder davon verwendet oder Instanzen erzeugt. Die einzige Möglichkeit, die Kompilierungszeiten vernünftig zu halten, besteht darin, dass der Text der Klasse selbst - soweit möglich - nur die Dinge enthält, die für ihre Verbraucher tatsächlich notwendig sind. Die Tatsache, dass C ++ - Methoden häufig außerhalb der Klassen geschrieben werden, die sie enthalten, ist ein wirklich übler Hack, der durch die Tatsache motiviert ist, dass der Compiler den Text jeder Klassenmethode einmal für jede Kompilierungseinheit verarbeiten muss, in der die Klasse verwendet wird, würde insgesamt dazu führen verrückte Bauzeiten.

In Java enthalten kompilierte Klassendateien unter anderem Informationen, die weitgehend einer C ++ .h-Datei entsprechen. Konsumenten der Klasse können alle benötigten Informationen aus dieser Datei extrahieren, ohne dass der Compiler die .java-Datei verarbeiten muss. Anders als in C ++, wo die .h-Datei Informationen enthält, die sowohl den Implementierungen als auch den Clients der darin enthaltenen Klassen zur Verfügung gestellt werden, ist der Ablauf in Java umgekehrt: Die von den Clients verwendete Datei ist keine Quelldatei, die bei der Kompilierung von verwendet wird Klassencode, sondern wird vom Compiler anhand der Informationen der Klassencodedatei erzeugt. Da der Klassencode nicht zwischen einer Datei mit den von Clients benötigten Informationen und einer Datei mit der Implementierung aufgeteilt werden muss, lässt Java eine solche Aufteilung nicht zu.


-1

Ich denke, ein Teil davon ist, dass Java eine sehr protektionistische Sprache ist, die sich mit der Verwendung in großen Teams befasst. Klassen können nicht überschrieben oder neu definiert werden. Sie haben 4 Ebenen von Zugriffsmodifikatoren, die genau definieren, wie Methoden verwendet werden können und nicht. Alles ist stark / statisch geschrieben, um Entwickler vor Typenkonflikten zu schützen, die von anderen oder von ihnen selbst verursacht wurden. Klassen und Funktionen als kleinste Einheiten zu haben, erleichtert die Neuerfindung des Paradigmas für die Entwicklung einer App erheblich.

Im Vergleich zu JavaScript, bei dem erstklassige Funktionen mit zwei Verwendungszwecken (Nomen / Verb) herunterregnen und wie Süßigkeiten aus einer aufgebrochenen Pinata herumgereicht werden, kann der Prototyp des klassenäquivalenten Funktionskonstruktors geändert werden, indem neue Eigenschaften hinzugefügt oder alte geändert werden, wenn dies der Fall ist wurden bereits erstellt, nichts hindert Sie daran, ein Konstrukt durch eine eigene Version davon zu ersetzen. Es gibt 5 Typen, die unter verschiedenen Umständen automatisch konvertiert und ausgewertet werden und Sie können neue Eigenschaften an nahezu alles anhängen:

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

Letztendlich geht es um Nischen und Märkte. JavaScript ist in den Händen von 100 Entwicklern (von denen viele wahrscheinlich mittelmäßig sein werden) ungefähr so ​​angemessen und katastrophal wie Java früher in den Händen kleiner Gruppen von Entwicklern war, die versuchten, damit eine Web-Benutzeroberfläche zu schreiben. Wenn Sie mit 100 Entwicklern arbeiten, ist das Letzte, was Sie wollen, ein Typ, der das verdammte Paradigma neu erfindet. Wenn Sie mit der Benutzeroberfläche arbeiten oder wenn die schnelle Entwicklung ein kritischeres Anliegen ist, ist es das Letzte, was Sie wollen, sich daran zu hindern, relativ einfache Dinge schnell auszuführen, einfach weil es einfach wäre, sie sehr dumm zu machen, wenn Sie nicht aufpassen.

Letztendlich sind beide populäre allgemeine Gebrauchssprachen, daher gibt es dort ein wenig philosophisches Argument. Mein größtes persönliches Problem mit Java und C # ist, dass ich noch nie eine ältere Codebasis gesehen oder mit ihr gearbeitet habe, in der die Mehrheit der Entwickler den Wert von OOP verstanden zu haben schien. Wenn Sie ins Spiel kommen und alles in eine Klasse packen müssen, ist es vielleicht einfacher anzunehmen, dass Sie OOP machen, als Spaghetti zu funktionieren, die als massive Ketten von 3-5 Linien-Klassen getarnt sind.

Trotzdem gibt es nichts Schlimmeres als JavaScript, das von jemandem geschrieben wurde, der nur genug weiß, um gefährlich zu sein, und keine Angst hat, es vorzuführen. Und ich denke, das ist die allgemeine Idee. Dieser Typ muss angeblich nach ungefähr den gleichen Regeln in Java spielen. Ich würde behaupten, dass der Bastard immer einen Weg finden wird.

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.