
Der Artikel spiegelt die persönlichen Erfahrungen des Autors wider - eines begeisterten Mikrocontroller-Programmierers, der nach langjähriger Erfahrung in der Entwicklung von Mikrocontrollern in C (und ein wenig in C ++) die Gelegenheit hatte, an einem großen Java-Projekt zur Entwicklung von Software für TV-Set-Top-Boxen mit Android teilzunehmen. Während dieses Projekts konnte ich Notizen über interessante Unterschiede zwischen den Sprachen Java und C / C ++ sammeln und verschiedene Ansätze zum Schreiben von Programmen bewerten. Der Artikel gibt nicht vor, eine Referenz zu sein, und untersucht nicht die Effizienz und Produktivität von Java-Programmen. Es ist eher eine Sammlung persönlicher Beobachtungen. Sofern nicht anders angegeben, handelt es sich um eine Java SE 7-Version.
Syntaxunterschiede und Kontrollkonstrukte
Kurz gesagt - die Unterschiede sind minimal, die Syntax ist sehr ähnlich. Codeblöcke werden auch durch ein Paar geschweifter Klammern {} gebildet. Die Regeln zum Kompilieren von Bezeichnern sind dieselben wie für C / C ++. Die Liste der Schlüsselwörter ist fast dieselbe wie in C / C ++. Integrierte Datentypen - ähnlich wie in C / C ++. Arrays - Alle werden auch in eckigen Klammern deklariert.
Das Steuerelement if-else, while, do-while, for, switch ist ebenfalls fast vollständig identisch. Es ist bemerkenswert, dass es in Java Bezeichnungen gab, die C-Programmierern vertraut waren (solche, die mit dem Schlüsselwort goto verwendet werden und von deren Verwendung dringend abgeraten wird). Java schloss jedoch die Möglichkeit aus, mit goto zu einem Label zu wechseln. Beschriftungen sollten nur zum Verlassen verschachtelter Schleifen verwendet werden:
outer: for (int i = 0; i < 5; i++) { inner: for (int j = 0; j < 5; j++) { if (i == 2) break inner; if (i == 3) continue outer; } }
Um die Lesbarkeit von Programmen in Java zu verbessern, wurde eine interessante Möglichkeit hinzugefügt, die Bits langer Zahlen mit einem Unterstrich zu trennen:
int value1 = 1_500_000; long value2 = 0xAA_BB_CC_DD;
Extern unterscheidet sich ein Java-Programm nicht wesentlich von einem bekannten C-Programm. Der visuelle Hauptunterschied besteht darin, dass Java keine Funktionen, Variablen, Definitionen neuer Typen (Strukturen), Konstanten usw. zulässt, die sich "frei" in der Quelldatei befinden. Java ist eine objektorientierte Sprache, daher müssen alle Programmentitäten zu einer Klasse gehören. Ein weiterer wesentlicher Unterschied ist das Fehlen eines Präprozessors. Diese beiden Unterschiede werden nachstehend ausführlicher beschrieben.
Objektansatz in C-Sprache
Wenn wir große Programme in C schreiben, müssen wir grundsätzlich mit Objekten arbeiten. Die Rolle des Objekts wird von einer Struktur übernommen, die eine bestimmte Essenz der „realen Welt“ beschreibt:
Auch in C gibt es Methoden zur Verarbeitung von "Objekten" -Strukturen - Funktionen. Funktionen werden jedoch nicht im Wesentlichen mit Daten zusammengeführt. Ja, sie werden normalerweise in einer Datei abgelegt, aber jedes Mal muss ein Zeiger auf das zu verarbeitende Objekt in die "typische" Funktion übergeben werden:
int process(struct Data *ptr, int arg1, const char *arg2) { return result_code; }
Sie können das "Objekt" erst verwenden, nachdem Sie Speicher zum Speichern zugewiesen haben:
Data *data = malloc(sizeof(Data));
In einem C-Programm wird normalerweise eine Funktion definiert, die für die Initialisierung des „Objekts“ vor seiner ersten Verwendung verantwortlich ist:
void init(struct Data *data) { data->field = 1541; data->str = NULL; }
Dann ist der Lebenszyklus eines „Objekts“ in C normalerweise so:
struct Data *data = malloc(sizeof(Data)); init(data); process(data, 0, "string"); free(data);
Nun listen wir die möglichen Laufzeitfehler auf, die der Programmierer im Lebenszyklus des „Objekts“ machen kann:
- Vergessen Sie, Speicher für das "Objekt" zuzuweisen
- Geben Sie die falsche Menge des zugewiesenen Speichers an
- Vergessen Sie das "Objekt" zu initialisieren
- Vergessen Sie, nach der Verwendung des Objekts Speicher freizugeben
Es kann äußerst schwierig sein, solche Fehler zu erkennen, da sie vom Compiler nicht erkannt werden und während des Programmbetriebs auftreten. Darüber hinaus kann ihre Wirkung sehr unterschiedlich sein und andere Variablen und „Objekte“ des Programms beeinflussen.
Java-Objektansatz
Angesichts der objektorientierten Programmierung von OOP haben Sie wahrscheinlich von einem der OOP-Wale gehört - der Kapselung. In Java werden im Gegensatz zu C Daten und Methoden zu ihrer Verarbeitung miteinander kombiniert und sind „echte“ Objekte. In Bezug auf OOP wird dies als Kapselung bezeichnet. Eine Klasse ist eine Beschreibung eines Objekts. Das nächste Analogon einer Klasse in C besteht darin, einen neuen Typ mit typedef struct zu definieren. In Java werden die Funktionen, die zu einer Klasse gehören, als Methoden bezeichnet.
Die Ideologie der Java-Sprache basiert auf der Aussage "Alles ist ein Objekt". Daher ist es nicht verwunderlich, dass Java die Erstellung von Methoden (Funktionen) und Datenfeldern (Variablen) getrennt von der Klasse verbietet. Auch die bekannte main () -Methode, von der aus das Programm startet, muss zu einer der Klassen gehören.
Eine Klassendefinition in Java ist analog zu einer Strukturdeklaration in C. Durch die Beschreibung einer Klasse erstellen Sie nichts im Speicher. Ein Objekt dieser Klasse wird zum Zeitpunkt seiner Erstellung durch den neuen Operator angezeigt. Das Erstellen eines Objekts in Java ist ein Analogon zum Zuweisen von Speicher in der Sprache C, aber im Gegensatz zu letzterem wird bei der Objekterstellung automatisch eine spezielle Methode aufgerufen - der Konstruktor des Objekts. Der Konstruktor übernimmt die Rolle der anfänglichen Initialisierung des Objekts - ein Analogon der zuvor diskutierten Funktion init (). Der Name des Konstruktors muss mit dem Namen der Klasse übereinstimmen. Der Konstruktor kann keinen Wert zurückgeben.
Der Lebenszyklus eines Objekts in einem Java-Programm ist wie folgt:
Beachten Sie, dass die Anzahl der möglichen Fehler im Java-Programm viel geringer ist als im C-Programm. Ja, Sie können immer noch vergessen, das Objekt vor der ersten Verwendung zu erstellen (was jedoch zu einer leicht zu debuggenden NullPointerException führt), aber was die anderen inhärenten Fehler betrifft C-Programme ändert sich die Situation grundlegend:
- In Java gibt es keinen Operator sizeof (). Der Java-Compiler selbst berechnet die Speichermenge zum Speichern des Objekts. Daher ist es nicht möglich, die falsche Größe der Auswahl anzugeben.
- Die Initialisierung des Objekts erfolgt zum Zeitpunkt der Erstellung. Es ist unmöglich, die Initialisierung zu vergessen.
- Der vom Objekt belegte Speicher muss nicht freigegeben werden, der Garbage Collector erledigt diese Arbeit. Es ist nicht zu vergessen, ein Objekt nach der Verwendung zu löschen - es besteht eine geringere Wahrscheinlichkeit eines „Speicherverlust“ -Effekts.
Alles in Java ist also ein Objekt der einen oder anderen Klasse. Ausnahmen sind Grundelemente, die der Sprache hinzugefügt wurden, um die Leistung und den Speicherverbrauch zu verbessern. Mehr über Primitive finden Sie weiter unten.
Speicher- und Speicherbereinigung
Java behält die bekannten Konzepte von Heap und Stack für C / C ++, einen Programmierer, bei. Beim Erstellen eines Objekts mit dem neuen Operator wird der Speicher zum Speichern des Objekts vom Heap ausgeliehen. Eine Verknüpfung zu einem Objekt (eine Verknüpfung ist ein Analogon eines Zeigers) wird jedoch auf dem Stapel platziert, wenn das erstellte Objekt nicht Teil eines anderen Objekts ist. Auf dem Heap werden die "Körper" von Objekten gespeichert, und auf dem Stapel befinden sich lokale Variablen: Verweise auf Objekte und primitive Typen. Wenn der Heap während der Ausführung des Programms vorhanden ist und für alle Threads des Programms verfügbar ist, gehört der Stapel zur Methode und existiert nur während seiner Ausführung und ist auch für andere Threads des Programms nicht zugänglich.
Java ist unnötig und vor allem - Sie können den von einem Objekt belegten Speicher nicht manuell freigeben. Diese Arbeit erledigt der Garbage Collector im automatischen Modus. Die Laufzeit überwacht, ob es möglich ist, jedes Objekt im Heap vom aktuellen Speicherort des Programms aus zu erreichen, indem sie den Links von Objekt zu Objekt folgt. Wenn nicht, wird ein solches Objekt als "Müll" erkannt und wird zum Löschkandidaten.
Es ist wichtig zu beachten, dass das Löschen selbst nicht in dem Moment erfolgt, in dem das Objekt „nicht mehr benötigt wird“ - der Garbage Collector entscheidet über das Löschen, und das Löschen kann beliebig verzögert werden, bis das Programm endet.
Natürlich erfordert die Arbeit des Garbage Collector Prozessor-Overhead. Im Gegenzug entlastet er den Programmierer von großen Kopfschmerzen, die mit der Notwendigkeit verbunden sind, nach dem Ende der Verwendung von "Objekten" Speicher freizugeben. Tatsächlich „nehmen“ wir das Gedächtnis, wenn wir es brauchen, und nutzen es, ohne zu denken, dass wir es nach uns selbst befreien müssen.
Wenn wir über lokale Variablen sprechen, sollten wir uns an den Ansatz von Java zu ihrer Initialisierung erinnern. Wenn in C / C ++ eine nicht initialisierte lokale Variable einen zufälligen Wert enthält, lässt der Java-Compiler einfach nicht zu, dass sie nicht initialisiert wird:
int i;
Links - Ersatzzeiger
Java hat keine Zeiger, dementsprechend kann ein Java-Programmierer keinen der vielen Fehler machen, die beim Arbeiten mit Zeigern auftreten. Wenn Sie ein Objekt erstellen, erhalten Sie einen Link zu diesem Objekt:
In C hatte der Programmierer die Wahl: Wie soll beispielsweise eine Struktur an eine Funktion übergeben werden? Sie könnten nach Wert übergeben:
Durch die Übergabe des Werts wurde garantiert, dass die Funktion die Daten in der Struktur nicht ändert, jedoch hinsichtlich der Leistung ineffektiv ist. Zum Zeitpunkt des Aufrufs der Funktion wurde eine Kopie der Struktur erstellt. Das Durchlaufen eines Zeigers ist viel effizienter: Tatsächlich wurde die Adresse im Speicher, an der sich die Struktur befindet, an die Funktion übergeben.
In Java gab es nur eine Möglichkeit, ein Objekt an eine Methode zu übergeben - als Referenz. Das Übergeben als Referenz in Java entspricht dem Übergeben eines Zeigers in C:
- Das Kopieren (Klonen) von Speicher erfolgt nicht.
- Tatsächlich wird die Adresse des Ortes dieses Objekts übertragen.
Im Gegensatz zum C-Sprachzeiger kann eine Java-Verbindung jedoch nicht inkrementiert / dekrementiert werden. Das "Durchlaufen" der Elemente eines Arrays mithilfe eines Links dazu in Java funktioniert nicht. Mit einem Link können Sie ihm lediglich einen anderen Wert geben.
Natürlich reduziert das Fehlen von Zeigern als solche die Anzahl möglicher Fehler, jedoch bleibt das Analogon des Nullzeigers in der Sprache - eine Nullreferenz, die durch das Schlüsselwort null gekennzeichnet ist.
Eine Nullreferenz bereitet einem Java-Programmierer Kopfschmerzen Erzwingt, dass die Objektreferenz vor der Verwendung entweder auf null überprüft wird oder dass NullPointerException-Ausnahmen behandelt werden. Ist dies nicht der Fall, stürzt das Programm ab.
Alle Objekte in Java werden also über Links übergeben. Primitive Datentypen (int, long, char ...) werden als Wert übergeben (weitere Informationen zu Primitiven finden Sie weiter unten).
Java Link-Funktionen
Der Zugriff auf ein Objekt im Programm erfolgt über einen Link - dies wirkt sich eindeutig positiv auf die Leistung aus, kann jedoch einen Neuling überraschen:
Methodenargumente und Rückgabewerte - alles wird über den Link übergeben. Zusätzlich zu den Vorteilen gibt es einen Nachteil im Vergleich zu C / C ++ - Sprachen, bei denen wir Funktionen ausdrücklich daran hindern können, den durch einen Zeiger übergebenen Wert mithilfe eines const-Qualifikators zu ändern:
void func(const struct Data* data) {
Das heißt, mit der Sprache C können Sie diesen Fehler in der Kompilierungsphase verfolgen. Java hat auch das Schlüsselwort const, ist jedoch für zukünftige Versionen reserviert und wird derzeit überhaupt nicht verwendet. Bis zu einem gewissen Grad muss das endgültige Schlüsselwort seine Rolle erfüllen. Das an die Methode übergebene Objekt wird jedoch nicht vor Änderungen geschützt:
public class Main { void func(final Entity data) {
Die Sache ist, dass das letzte Schlüsselwort in diesem Fall auf den Link angewendet wird und nicht auf das Objekt, auf das der Link verweist. Wenn final auf das Grundelement angewendet wird, verhält sich der Compiler wie erwartet:
void func(final int value) {
Java-Links sind C ++ - Sprachlinks sehr ähnlich.
Java-Grundelemente
Jedes Java-Objekt enthält neben Datenfeldern unterstützende Informationen. Wenn wir beispielsweise in separaten Bytes arbeiten möchten und jedes Byte durch ein Objekt dargestellt wird, kann der Speicher-Overhead bei einem Array von Bytes die verwendbare Größe um ein Vielfaches überschreiten.
Damit Java in den oben beschriebenen Fällen effizient genug bleibt, wurde der Sprache die Unterstützung für primitive Typen - Primitive - hinzugefügt.
Primitiv | Anzeigen | Bittiefe | Mögliches Analogon in C. |
---|
Byte | Ganzzahl | 8 | char |
kurz | 16 | kurz |
char | 16 | wchar_t |
int | 32 | int (lang) |
lang | 64 | lang |
float | Gleitkommazahlen | 32 | float |
doppelt | | 64 | doppelt |
Boolescher Wert | Logisch | - - | int (C89) / bool (C99) |
Alle Grundelemente haben ihre Analoga in der C-Sprache. Der C-Standard bestimmt jedoch nicht die genaue Größe von Ganzzahltypen, sondern der Wertebereich, den dieser Typ speichern kann. Oft möchte der Programmierer die gleiche Bittiefe für verschiedene Maschinen sicherstellen, was dazu führt, dass Typen wie uint32_t im Programm angezeigt werden, obwohl für alle Bibliotheksfunktionen nur Argumente vom Typ int erforderlich sind.
Diese Tatsache kann nicht auf die Vorteile der Sprache zurückgeführt werden.
Java Integer Primitive haben im Gegensatz zu C feste Bittiefen. Sie müssen sich also keine Gedanken über die tatsächliche Bittiefe des Computers machen, auf dem das Java-Programm ausgeführt wird, sowie über die Bytereihenfolge ("Netzwerk" oder "Intel"). Diese Tatsache hilft, das Prinzip „es ist einmal geschrieben - es wird überall erfüllt“ zu verwirklichen.
Außerdem sind in Java alle ganzzahligen Grundelemente signiert (der Sprache fehlt das vorzeichenlose Schlüsselwort). Dies beseitigt die Schwierigkeit, vorzeichenbehaftete und vorzeichenlose Variablen in einem einzelnen Ausdruck zu verwenden, der C innewohnt.
Zusammenfassend ist die Bytereihenfolge in Mehrbyte-Grundelementen in Java festgelegt (niedriges Byte bei niedriger Adresse, Little-Endian, umgekehrte Reihenfolge).
Zu den Nachteilen der Implementierung von Operationen mit Grundelementen in Java gehört die Tatsache, dass hier wie im C / C ++ - Programm der Überlauf des Bitgitters auftreten kann, ohne dass Ausnahmen ausgelöst werden:
int i1 = 2_147_483_640; int i2 = 2_147_483_640; int r = (i1 + i2);
Daten in Java werden also durch zwei Arten von Entitäten dargestellt: Objekte und Grundelemente. Primitive verletzen das Konzept „Alles ist ein Objekt“, aber in einigen Situationen sind sie zu effektiv, um sie nicht zu verwenden.
Vererbung
Vererbung ist ein weiterer OOP-Wal, von dem Sie wahrscheinlich gehört haben. Wenn Sie kurz die Frage beantworten, warum Vererbung überhaupt erforderlich ist, lautet die Antwort „Wiederverwendung von Code“.
Angenommen, Sie programmieren in C und haben eine gut geschriebene und debuggte „Klasse“ - eine Struktur und Funktionen für deren Verarbeitung. Als nächstes besteht die Notwendigkeit, eine ähnliche "Klasse" zu erstellen, jedoch mit erweiterter Funktionalität, und die grundlegende "Klasse" wird weiterhin benötigt. Bei der C-Sprache haben Sie nur einen Weg, um dieses Problem zu lösen - die Komposition. Es geht darum, eine neue erweiterte Struktur zu erstellen - "Klasse", die einen Zeiger auf die Basisstruktur "Klasse" enthalten sollte:
struct Base { int field1; char *field2; }; void baseMethod(struct Base *obj, int arg); struct Extended { struct Base *base; int auxField; }; void extendedMethod(struct Extended *obj, int arg) { baseMethod(obj->base, 123); }
Mit Java als objektorientierte Sprache können Sie die Funktionalität vorhandener Klassen mithilfe des Vererbungsmechanismus erweitern:
Es ist zu beachten, dass Java die Verwendung von Kompositionen zur Erweiterung der Funktionalität bereits geschriebener Klassen in keiner Weise verbietet. Darüber hinaus ist in vielen Situationen die Zusammensetzung der Vererbung vorzuziehen.
Dank der Vererbung sind Klassen in Java in einer hierarchischen Struktur angeordnet. Jede Klasse hat notwendigerweise nur ein "Elternteil" und kann eine beliebige Anzahl von "Kindern" haben. Im Gegensatz zu C ++ kann eine Klasse in Java nicht von mehr als einem übergeordneten Element erben (dies löst das Problem der "Diamantvererbung").
Während der Vererbung erhält die abgeleitete Klasse alle öffentlichen und geschützten Felder und Methoden ihrer Basisklasse sowie die Basisklasse ihrer Basisklasse usw. an ihren Speicherort usw. in der Vererbungshierarchie.
An der Spitze der Vererbungshierarchie steht der gemeinsame Vorläufer aller Java-Klassen - die Object-Klasse, die einzige, die kein übergeordnetes Element hat.
Dynamische Typidentifikation
Einer der wichtigsten Punkte der Java-Sprache ist die Unterstützung der dynamischen Typidentifikation (RTTI). Mit einfachen Worten, mit RTTI können Sie ein Objekt einer abgeleiteten Klasse ersetzen, für das ein Verweis auf die Basis erforderlich ist:
Mit einem Link zur Laufzeit können Sie den wahren Typ des Objekts bestimmen, auf das sich der Link bezieht - mithilfe des Operators instanceof:
if (link instanceof Base) {
Methodenüberschreibungen
Eine Methode oder Funktion neu zu definieren bedeutet, ihren Körper in der Programmausführungsphase zu ersetzen. C-Programmierer sind sich der Fähigkeit einer Sprache bewusst, das Verhalten einer Funktion während der Programmausführung zu ändern. Es geht darum, Funktionszeiger zu verwenden. Beispielsweise können Sie einen Zeiger auf eine Funktion in die Struktur der Struktur aufnehmen und dem Zeiger verschiedene Funktionen zuweisen, um den Datenverarbeitungsalgorithmus dieser Struktur zu ändern:
struct Object {
In Java wie auch in anderen OOP-Sprachen sind überschreibende Methoden untrennbar mit der Vererbung verbunden. Eine abgeleitete Klasse erhält Zugriff auf die öffentlichen und geschützten Methoden der Basisklasse. Neben der Tatsache, dass er sie aufrufen kann, können Sie das Verhalten einer der Methoden der Basisklasse ändern, ohne deren Signatur zu ändern. Dazu reicht es aus, eine Methode mit genau derselben Signatur in der abgeleiteten Klasse zu definieren:
Es ist sehr wichtig, dass die Signatur (Methodenname, Rückgabewert, Argumente) genau übereinstimmt. Wenn der Methodenname übereinstimmt und die Argumente unterschiedlich sind, wird die Methode überladen, mehr dazu weiter unten.
Polymorphismus
Wie die Einkapselung und Vererbung hat auch der dritte OOP-Wal - der Polymorphismus - eine Art Analogon in der prozedural orientierten C-Sprache.
Angenommen, wir haben mehrere "Klassen" von Strukturen, mit denen Sie dieselbe Art von Aktion ausführen möchten, und die Funktion, die diese Aktion ausführt, muss universell sein - muss "in der Lage" sein, mit jeder "Klasse" als Argument zu arbeiten.
Eine mögliche Lösung ist wie folgt: enum Ids { ID_A, ID_B }; struct ClassA { int id; } void aInit(ClassA obj) { obj->id = ID_A; } struct ClassB { int id; } void bInit(ClassB obj) { obj->id = ID_B; } void commonFunc(void *klass) { int id = (int *)klass; switch (id) { case ID_A: ClassA *obj = (ClassA *) klass; break; case ID_B: ClassB *obj = (ClassB *) klass; break; } }
Die Lösung sieht umständlich aus, aber das Ziel ist erreicht - die universelle Funktion commonFunc () akzeptiert das "Objekt" einer beliebigen "Klasse" als Argument. Voraussetzung ist, dass eine „Klassen“ -Struktur im ersten Feld einen Bezeichner enthält, anhand dessen die tatsächliche „Klasse“ des Objekts bestimmt wird. Eine solche Lösung ist aufgrund der Verwendung des Arguments mit dem Typ "void *" möglich. Ein Zeiger eines beliebigen Typs kann jedoch an eine solche Funktion übergeben werden, z. B. "int *". Dies führt nicht zu Kompilierungsfehlern, aber zur Laufzeit verhält sich das Programm unvorhersehbar.Nun wollen wir sehen, wie Polymorphismus in Java aussieht (jedoch wie in jeder anderen OOP-Sprache). Angenommen, wir haben viele Klassen, die von einer Methode auf dieselbe Weise verarbeitet werden sollten. Im Gegensatz zu der oben vorgestellten Lösung für Sprache C MUSS diese polymorphe Methode in allen Klassen des angegebenen Satzes enthalten sein, und alle ihre Versionen MÜSSEN dieselbe Signatur haben. class A { public void method() {} } class B { public void method() {} } class C { public void method() {} }
Als Nächstes müssen Sie den Compiler zwingen, genau die Version der Methode aufzurufen, die zur entsprechenden Klasse gehört. void executor(_set_of_class_ klass) { klass.method(); }
Das heißt, die executor () -Methode, die sich an einer beliebigen Stelle im Programm befinden kann, muss mit jeder Klasse aus der Menge (A, B oder C) arbeiten können. Wir müssen dem Compiler irgendwie "sagen", dass _set_of_class_ unsere vielen Klassen bezeichnet. Hier ist die Vererbung nützlich - es ist notwendig, alle Klassen aus den Mengenableitungen einer Basisklasse zu erstellen, die eine polymorphe Methode enthält: abstract class Base { abstract public void method(); } class A extends Base { public void method() {} } class B extends Base { public void method() {} } class C extends Base { public void method() {} } executor() : void executor(Base klass) { klass.method(); }
Und jetzt kann jede Klasse, die ein Erbe von Base ist (dank dynamischer Typidentifikation), als Argument an sie übergeben werden: executor(new A()); executor(new B()); executor(new C());
Abhängig davon, welches Klassenobjekt als Argument übergeben wird, wird eine zu dieser Klasse gehörende Methode aufgerufen.Mit dem Schlüsselwort abstract können Sie den Hauptteil der Methode ausschließen (in Bezug auf OOP abstrakt machen). Tatsächlich teilen wir dem Compiler mit, dass diese Methode in den daraus abgeleiteten Klassen überschrieben werden muss. Ist dies nicht der Fall, tritt ein Kompilierungsfehler auf. Eine Klasse, die mindestens eine abstrakte Methode enthält, wird auch als abstrakt bezeichnet. Der Compiler muss solche Klassen auch mit dem Schlüsselwort abstract markieren.Java-Projektstruktur
In Java haben alle Quelldateien die Erweiterung * .java. Sowohl die * .h-Headerdateien als auch die Prototypen von Funktionen oder Klassen fehlen. Jede Java-Quelldatei muss mindestens eine Klasse enthalten. Der Name der Klasse ist üblich und beginnt mit einem Großbuchstaben.Mehrere Dateien mit Quellcode können zu einem Paket kombiniert werden. Dazu müssen folgende Bedingungen erfüllt sein:- Dateien mit Quellcode müssen sich im selben Verzeichnis im Dateisystem befinden.
- Der Name dieses Verzeichnisses muss mit dem Namen des Pakets übereinstimmen.
- Am Anfang jeder Quelldatei sollte das Paket angegeben werden, zu dem diese Datei gehört, zum Beispiel:
package com.company.pkg;
Um die Eindeutigkeit von Paketnamen innerhalb des Globus sicherzustellen, wird vorgeschlagen, den "invertierten" Domainnamen des Unternehmens zu verwenden. Dies ist jedoch keine Voraussetzung und im lokalen Projekt können beliebige Namen verwendet werden.Es wird auch empfohlen, Paketnamen in Kleinbuchstaben anzugeben. So können sie leicht von Klassennamen unterschieden werden.Verschleierung der Umsetzung
Ein weiterer Aspekt der Kapselung ist die Trennung von Schnittstelle und Implementierung. Wenn die Schnittstelle für die externen Teile des Programms zugänglich ist (außerhalb des Moduls oder der Klasse), ist die Implementierung ausgeblendet. In der Literatur wird häufig eine Black-Box-Analogie gezogen, wenn die interne Implementierung von außen „nicht sichtbar“ ist, aber was in den Eingang der Box eingespeist wird und was sie ausgibt, „sichtbar“ ist.In C werden Implementierungen innerhalb eines Moduls ausgeblendet, wobei Funktionen, die von außen nicht sichtbar sein sollten, mit dem Schlüsselwort static markiert werden. Die Prototypen der Funktionen, aus denen die Schnittstelle des Moduls besteht, werden in die Header-Datei eingefügt. Ein Modul in C bedeutet ein Paar: eine Quelldatei mit der Erweiterung * .c und einen Header mit der Erweiterung * .h.Java hat auch das statische Schlüsselwort, hat jedoch keinen Einfluss auf die „Sichtbarkeit“ der Methode oder des Felds von außen. Um die "Sichtbarkeit" zu steuern, gibt es 3 Zugriffsmodifikatoren: privat, geschützt, öffentlich.Felder und Methoden einer als privat gekennzeichneten Klasse sind nur darin verfügbar. Geschützte Felder und Methoden sind auch für Klassennachkommen zugänglich. Der öffentliche Modifikator bedeutet, dass auf das markierte Element von außerhalb der Klasse zugegriffen werden kann, dh dass es Teil der Schnittstelle ist. Es ist auch möglich, dass es keinen Modifikator gibt. In diesem Fall wird der Zugriff auf das Klassenelement durch das Paket eingeschränkt, in dem sich die Klasse befindet.Es wird empfohlen, beim Schreiben einer Klasse zunächst alle Felder der Klasse als privat zu markieren und die Zugriffsrechte nach Bedarf zu erweitern.Methodenüberladung
Eine der nervigen Eigenschaften der C-Standardbibliothek ist das Vorhandensein eines ganzen Zoos von Funktionen, die im Wesentlichen dasselbe ausführen, sich jedoch in der Art des Arguments unterscheiden, zum Beispiel: fabs (), fabsf (), fabsl () - Funktionen zum Erhalten des Absolutwerts für double, float und long Doppeltypen.Java (sowie C ++) unterstützt einen Methodenüberladungsmechanismus. Innerhalb einer Klasse gibt es möglicherweise mehrere Methoden mit einem völlig identischen Namen, die sich jedoch in Typ und Anzahl der Argumente unterscheiden. Anhand der Anzahl der Argumente und ihres Typs wählt der Compiler die erforderliche Version der Methode selbst aus - dies ist sehr praktisch und verbessert die Lesbarkeit des Programms.In Java können Operatoren im Gegensatz zu C ++ nicht überladen werden. Die Ausnahme bilden die Operatoren "+" und "+ =", die anfänglich für String-Strings überladen sind.Zeichen und Zeichenfolgen in Java
In C müssen Sie mit nullterminalen Zeichenfolgen arbeiten, die durch Zeiger auf das erste Zeichen dargestellt werden: char *str;
Solche Zeilen müssen mit einem Nullzeichen enden. Wenn es versehentlich "gelöscht" wird, wird eine Zeichenfolge als eine Folge von Bytes im Speicher bis zum ersten Nullzeichen betrachtet. Das heißt, wenn andere Programmvariablen nach der Zeile im Speicher abgelegt werden, können (und werden höchstwahrscheinlich) ihre Werte nach dem Ändern einer solchen beschädigten Zeile verzerrt werden.Natürlich ist ein C-Programmierer nicht verpflichtet, klassische Null-Terminal-Zeichenfolgen zu verwenden, sondern eine Drittanbieter-Implementierung anzuwenden. Hierbei ist jedoch zu beachten, dass für alle Funktionen aus der Standardbibliothek Null-Terminal-Zeichenfolgen als Argumente erforderlich sind. Darüber hinaus definiert der C-Standard nicht die verwendete Codierung, dieser Punkt sollte auch vom Programmierer gesteuert werden.In Java repräsentiert der primitive Zeichentyp (sowie der Zeichen-Wrapper über die folgenden Wrapper) ein einzelnes Zeichen gemäß dem Unicode-Standard. Es wird jeweils eine UTF-16-Codierung verwendet. Ein Zeichen belegt 2 Byte im Speicher, sodass Sie fast alle Zeichen der derzeit verwendeten Sprachen codieren können.Zeichen können durch ihren Unicode angegeben werden: char ch1 = '\u20BD';
Wenn der Unicode eines Zeichens das Maximum von 216 für char überschreitet, muss ein solches Zeichen durch int dargestellt werden. In der Zeichenfolge werden 2 Zeichen mit 16 Bit belegt, aber auch hier werden Zeichen mit einem Code von mehr als 216 äußerst selten verwendet.Java-Zeichenfolgen werden von der integrierten String-Klasse implementiert und speichern 16-Bit-Zeichen. Die String-Klasse enthält alle oder fast alles, was für die Arbeit mit Strings erforderlich sein kann. Es besteht keine Notwendigkeit, darüber nachzudenken, dass die Zeile notwendigerweise mit Null enden muss. Hier ist es unmöglich, dieses Null-Abschlusszeichen unmerklich zu "löschen" oder auf den Speicher jenseits der Zeile zuzugreifen. Bei der Arbeit mit Zeichenfolgen in Java denkt der Programmierer im Allgemeinen nicht darüber nach, wie die Zeichenfolge im Speicher gespeichert wird.Wie oben erwähnt, erlaubt Java kein Überladen von Operatoren (wie in C ++), jedoch ist die String-Klasse eine Ausnahme - nur für sie werden die Zeilenzusammenführungsoperatoren "+" und "+ =" anfänglich überladen. String str1 = "Hello, " + "World!"; String str2 = "Hello, "; str2 += "World!";
Es ist bemerkenswert, dass Zeichenfolgen in Java unveränderlich sind - einmal erstellt, erlauben sie ihre Änderung nicht. Wenn wir beispielsweise versuchen, die Zeile wie folgt zu ändern: String str = "Hello, World!"; str.toUpperCase(); System.out.println(str);
Die ursprüngliche Zeichenfolge ändert sich also nicht wirklich. Stattdessen wird eine geänderte Kopie der ursprünglichen Zeichenfolge erstellt, die wiederum unveränderlich ist: String str = "Hello, World!"; String str2 = str.toUpperCase(); System.out.println(str2);
Somit führt jede Änderung einer Zeichenfolge in der Realität zur Erstellung eines neuen Objekts (tatsächlich kann der Compiler beim Zusammenführen von Zeilen den Code optimieren und die StringBuilder-Klasse verwenden, die später erläutert wird).Es kommt vor, dass das Programm häufig dieselbe Zeile ändern muss. In solchen Fällen können Sie die Optimierung neuer Zeilenobjekte verhindern, um die Geschwindigkeit des Programms und den Speicherverbrauch zu optimieren. Für diese Zwecke sollte die StringBuilder-Klasse verwendet werden: String sourceString = "Hello, World!"; StringBuilder builder = new StringBuilder(sourceString); builder.setCharAt(4, '0'); builder.setCharAt(8, '0'); builder.append("!!"); String changedString = builder.toString(); System.out.println(changedString);
Separat ist der Vergleich von Strings zu erwähnen. Ein typischer Fehler eines unerfahrenen Java-Programmierers ist das Vergleichen von Zeichenfolgen mit dem Operator "==":
Ein solcher Code enthält formal keine Fehler in der Kompilierungsphase oder Laufzeitfehler, funktioniert jedoch anders als erwartet. Da alle Objekte und Zeichenfolgen, einschließlich in Java, durch Links dargestellt werden, führt der Vergleich mit dem Operator "==" zu einem Vergleich von Links und nicht von Werten von Objekten. Das heißt, das Ergebnis ist nur dann wahr, wenn 2 Links wirklich auf dieselbe Zeile verweisen. Wenn es sich bei den Zeichenfolgen um unterschiedliche Objekte im Speicher handelt und Sie deren Inhalt vergleichen müssen, müssen Sie die Methode equals () verwenden: if (usersInput.equals("Yes")) {
Das Überraschendste ist, dass in einigen Fällen der Vergleich mit dem Operator „==“ korrekt funktioniert: String someString = "abc", anotherString = "abc";
Dies liegt daran, dass sich someString und anotherString in Wirklichkeit auf dasselbe Objekt im Speicher beziehen. Der Compiler platziert die gleichen String-Literale im String-Pool - die sogenannte Internierung erfolgt. Jedes Mal, wenn dasselbe Zeichenfolgenliteral im Programm angezeigt wird, wird ein Link zur Zeichenfolge aus dem Pool verwendet. Die Internierung von Saiten ist aufgrund der Eigenschaft der Unveränderlichkeit von Saiten genau möglich.Obwohl das Vergleichen des Inhalts von Zeichenfolgen nur mit der Methode equals () zulässig ist, ist es in Java möglich, Zeichenfolgen in Switch-Case-Konstruktionen korrekt zu verwenden (beginnend mit Java 7): String str = new String();
Seltsamerweise kann jedes Java-Objekt in eine Zeichenfolge konvertiert werden. Die entsprechende toString () -Methode ist in der Basisklasse für alle Klassen der Object-Klasse definiert.Ansatz zur Fehlerbehandlung
Wenn Sie in C programmieren, haben Sie möglicherweise den folgenden Ansatz zur Fehlerbehandlung. Jede Funktion einer Bibliothek gibt einen int-Typ zurück. Wenn die Funktion erfolgreich ist, ist dieses Ergebnis 0. Wenn das Ergebnis ungleich Null ist, weist dies auf einen Fehler hin. Meistens wird der Fehlercode durch den von der Funktion zurückgegebenen Wert geleitet. Da die Funktion nur einen Wert zurückgeben kann und bereits vom Fehlercode belegt ist, muss das tatsächliche Ergebnis der Funktion über das Argument beispielsweise als Zeiger zurückgegeben werden: int function(struct Data **result, const char *arg) { int errorCode; return errorCode; }
Dies ist übrigens einer der Fälle, in denen in einem C-Programm ein Zeiger auf einen Zeiger verwendet werden muss.Manchmal verwenden sie einen anderen Ansatz. Die Funktion gibt keinen Fehlercode zurück, sondern direkt das Ergebnis ihrer Ausführung, normalerweise in Form eines Zeigers. Eine Fehlersituation wird mit einem Nullzeiger angezeigt. Dann enthält die Bibliothek normalerweise eine separate Funktion, die den Code des letzten Fehlers zurückgibt: struct Data* function(const char *arg); int getLastError();
Auf die eine oder andere Weise verschachteln sich beim Programmieren in C der Code, der die „nützliche“ Arbeit erledigt, und der Code, der für die Behandlung von Fehlern verantwortlich ist, gegenseitig, was das Programm offensichtlich nicht leicht lesbar macht.Wenn Sie möchten, können Sie in Java die oben beschriebenen Ansätze verwenden. Hier können Sie jedoch eine völlig andere Methode zum Umgang mit Fehlern anwenden - die Ausnahmebehandlung (jedoch wie in C ++). Der Vorteil der Ausnahmebehandlung besteht darin, dass in diesem Fall der „nützliche“ Code und der Code, der für die Behandlung von Fehlern und Eventualitäten verantwortlich ist, logisch voneinander getrennt sind.Dies wird mithilfe von Try-Catch-Konstruktionen erreicht: Der „nützliche“ Code wird im Try-Abschnitt und der Fehlerbehandlungscode im Catch-Abschnitt platziert.
Es gibt Situationen, in denen es nicht möglich ist, den Fehler am Ort seines Auftretens korrekt zu verarbeiten. In solchen Fällen wird in der Methodensignatur angegeben, dass die Methode diese Art von Ausnahme verursachen kann: public void func() throws Exception {
Jetzt muss der Aufruf dieser Methode notwendigerweise in einem Try-Catch-Block eingerahmt sein, oder die aufrufende Methode muss auch markiert sein, damit sie diese Ausnahme auslösen kann.Fehlender Präprozessor
Unabhängig davon, wie bequem der Präprozessor ist, der C / C ++ - Programmierern vertraut ist, fehlt er in der Java-Sprache. Java-Entwickler haben wahrscheinlich entschieden, dass es nur verwendet wird, um die Portabilität von Programmen sicherzustellen, und da Java (fast) überall ausgeführt wird, wird kein Präprozessor darin benötigt.Sie können das Fehlen eines Präprozessors mithilfe eines statischen Flagfelds kompensieren und gegebenenfalls dessen Wert im Programm überprüfen.Wenn wir über die Organisation von Tests sprechen, ist es möglich, Anmerkungen in Verbindung mit Reflexion (Reflexion) zu verwenden.Ein Array ist auch ein Objekt.
Bei der Arbeit mit Arrays in C ist der Indexausgang über die Grenzen des Arrays hinaus ein sehr heimtückischer Fehler. Der Compiler meldet dies in keiner Weise und während der Ausführung wird das Programm nicht mit der entsprechenden Meldung gestoppt: int array[5]; array[6] = 666;
Höchstwahrscheinlich setzt das Programm die Ausführung fort, aber der Wert der Variablen, die sich im obigen Beispiel nach dem Array-Array befand, wird verzerrt. Das Debuggen dieser Art von Fehler ist möglicherweise nicht einfach.In Java ist der Programmierer vor solchen schwer zu diagnostizierenden Fehlern geschützt. Wenn Sie versuchen, die Grenzen des Arrays zu überschreiten, wird eine ArrayIndexOutOfBoundsException ausgelöst. Wenn der Ausnahmefang nicht mit dem Try-Catch-Konstrukt programmiert wurde, stürzt das Programm ab und eine entsprechende Nachricht wird an den Standardfehlerstrom gesendet, in der die Datei mit dem Quellcode und der Zeilennummer angegeben ist, in der das Array überschritten wurde. Das heißt, die Diagnose solcher Fehler wird zu einer trivialen Angelegenheit.Dieses Verhalten des Java-Programms wird ermöglicht, weil das Array in Java durch ein Objekt dargestellt wird. Die Größe des Java-Arrays kann nicht geändert werden, da seine Größe zum Zeitpunkt der Speicherzuweisung fest codiert ist. Zur Laufzeit ist das Abrufen der Größe des Arrays so einfach wie das Schälen von Birnen: int[] array = new int[10]; int arraySize = array.length;
Wenn wir über mehrdimensionale Arrays sprechen, bietet Java im Vergleich zur C-Sprache eine interessante Möglichkeit, "Ladder" -Arrays zu organisieren. Für den Fall eines zweidimensionalen Arrays kann sich die Größe jeder einzelnen Zeile von den anderen unterscheiden: int[][] array = new int[10][]; for (int i = 0; i < array.length; i++) { array[i] = new int[i + 1]; }
Wie in C befinden sich die Elemente des Arrays nacheinander im Speicher, sodass der Zugriff auf das Array als am effizientesten angesehen wird. Wenn Sie Einfüge- / Löschvorgänge für Elemente ausführen oder komplexere Datenstrukturen erstellen müssen, müssen Sie Sammlungen verwenden, z. B. einen Satz (Satz), eine Liste (Liste), eine Karte (Karte).Aufgrund des Fehlens von Zeigern und der Unfähigkeit, Verknüpfungen zu erhöhen, ist der Zugriff auf die Elemente des Arrays mithilfe von Indizes möglich.Sammlungen
Oft reicht die Funktionalität von Arrays nicht aus - dann müssen Sie dynamische Datenstrukturen verwenden. Da die Standard-C-Bibliothek keine vorgefertigte Implementierung dynamischer Datenstrukturen enthält, müssen Sie die Implementierung in Quellcodes oder in Form von Bibliotheken verwenden.Im Gegensatz zu C enthält die Standard-Java-Bibliothek eine Vielzahl von Implementierungen dynamischer Datenstrukturen oder Sammlungen, ausgedrückt in Java. Alle Sammlungen sind in 3 große Klassen unterteilt: Listen, Sets und Karten.Listen - dynamische Arrays - ermöglichen das Hinzufügen / Entfernen von Elementen. Viele stellen die Reihenfolge der hinzugefügten Elemente nicht sicher, garantieren jedoch, dass keine doppelten Elemente vorhanden sind. Karten oder assoziative Arrays arbeiten mit Schlüssel-Wert-Paaren, und der Schlüsselwert ist eindeutig - es dürfen nicht zwei Paare mit denselben Schlüsseln auf der Karte vorhanden sein.Für Listen, Mengen und Karten gibt es viele Implementierungen, von denen jede für eine bestimmte Operation optimiert ist. Beispielsweise werden Listen von den Klassen ArrayList und LinkedList implementiert, wobei ArrayList eine bessere Leistung beim Zugriff auf ein beliebiges Element bietet und LinkedList effizienter beim Einfügen / Löschen von Elementen in der Mitte der Liste ist.In Sammlungen können nur vollständige Java-Objekte gespeichert werden (Verweise auf Objekte). Daher ist es unmöglich, eine Sammlung von Grundelementen direkt zu erstellen (int, char, byte usw.). In diesem Fall sollten die entsprechenden Wrapper-Klassen verwendet werden:Primitiv | Verpackungsklasse |
---|
Byte | Byte |
kurz | Kurz |
char | Charakter |
int | Ganzzahl |
lang | Lang |
float | Float |
doppelt | Doppel |
Boolescher Wert | Boolescher Wert |
Glücklicherweise ist es beim Programmieren in Java nicht erforderlich, die genaue Übereinstimmung des primitiven Typs und seines "Wrappers" zu verfolgen. Wenn die Methode beispielsweise ein Argument vom Typ Integer empfängt, kann ihr der Typ int übergeben werden. Und umgekehrt, wo der Typ int erforderlich ist, können Sie Integer sicher verwenden. Möglich wurde dies durch den in Java integrierten Mechanismus zum Packen / Entpacken von Grundelementen.Von den unangenehmen Momenten sollte erwähnt werden, dass die Standard-Java-Bibliothek alte Auflistungsklassen enthält, die in den ersten Versionen von Java nicht erfolgreich implementiert wurden und nicht in neuen Programmen verwendet werden sollten. Dies sind die Klassen Aufzählung, Vektor, Stapel, Wörterbuch, Hashtabelle, Eigenschaften.Verallgemeinerungen
Sammlungen werden üblicherweise als generische Datentypen verwendet. Das Wesentliche der Verallgemeinerungen in diesem Fall ist, dass wir den Haupttyp der Auflistung angeben, z. B. ArrayList, und in spitzen Klammern den Parametertyp angeben, der in diesem Fall den Typ der in der Liste gespeicherten Elemente bestimmt: List<Integer> list = new ArrayList<Integer>();
Auf diese Weise kann der Compiler den Versuch verfolgen, ein Objekt eines anderen Typs als des angegebenen Typparameters hinzuzufügen: List<Integer> list = new ArrayList<Integer>();
Es ist sehr wichtig, dass der Typparameter während der Programmausführung gelöscht wird, und es gibt keinen Unterschied zwischen beispielsweise einem Objekt der Klasse ArrayList <Integer>
und Klassenobjekt ArrayList <String>.
Daher gibt es keine Möglichkeit, den Typ der Sammlungselemente während der Programmausführung herauszufinden: public boolean containsInteger(List list) {
Eine Teillösung kann folgender Ansatz sein: Nehmen Sie das erste Element der Sammlung und bestimmen Sie dessen Typ: public boolean containsInteger(List list) { if (!list.isEmpty() && list.get(0) instanceof Integer) { return true; } return false; }
Dieser Ansatz funktioniert jedoch nicht, wenn die Liste leer ist.In dieser Hinsicht sind Java-Generalisierungen C ++ - Generalisierungen erheblich unterlegen. Java-Verallgemeinerungen dienen tatsächlich dazu, einige der potenziellen Fehler in der Kompilierungsphase abzuschneiden.Durchlaufen Sie alle Elemente eines Arrays oder einer Sammlung
Beim Programmieren in C müssen Sie häufig alle Elemente des Arrays durchlaufen: for (int i = 0; i < SIZE; i++) { }
Um hier einen Fehler zu machen, ist es einfacher, einfach die falsche Größe des SIZE-Arrays anzugeben oder "<=" anstelle von "<" einzugeben.In Java gibt es zusätzlich zur „üblichen“ Form der for-Anweisung ein Formular zum Durchlaufen aller Elemente eines Arrays oder einer Sammlung (in anderen Sprachen häufig foreach genannt): List<Integer> list = new ArrayList<>();
Hier wird garantiert, dass wir alle Elemente der Liste durchlaufen, wobei die Fehler, die der „üblichen“ Form der for-Anweisung inhärent sind, beseitigt werden.Verschiedene Sammlungen
Da alle Objekte vom Stammobjekt geerbt werden, bietet Java eine interessante Möglichkeit, Listen mit verschiedenen tatsächlichen Elementtypen zu erstellen: List list = new ArrayList<>(); list.add(new String("First")); list.add(new Integer(2)); list.add(new Double(3.0)); instanceof: for (Object o : list) { if (o instanceof String) {
Transfers
Beim Vergleich von C / C ++ und Java ist es unmöglich, nicht zu bemerken, wie viel mehr funktionale Aufzählungen in Java implementiert sind. Hier ist die Aufzählung eine vollwertige Klasse, und Aufzählungselemente sind Objekte dieser Klasse. Auf diese Weise kann ein Aufzählungselement mehrere Felder eines beliebigen Typs in Korrespondenz setzen: enum Colors {
Als vollwertige Klasse kann eine Aufzählung Methoden haben, und mit einem privaten Konstruktor können Sie die Feldwerte einzelner Aufzählungselemente festlegen.Es besteht die regelmäßige Möglichkeit, eine Zeichenfolgendarstellung eines Aufzählungselements, eine Seriennummer sowie ein Array aller Elemente abzurufen: Colors color = Colors.BLACK; String str = color.toString();
Und umgekehrt - durch die Zeichenfolgendarstellung können Sie ein Aufzählungselement erhalten und auch seine Methoden aufrufen: Colors red = Colors.valueOf("RED");
Natürlich können Aufzählungen in Switch-Case-Konstrukten verwendet werden.Schlussfolgerungen
Natürlich sind die Sprachen C und Java darauf ausgelegt, völlig unterschiedliche Probleme zu lösen. Wenn wir jedoch den Softwareentwicklungsprozess in diesen beiden Sprachen vergleichen, übertrifft die Java-Sprache nach den subjektiven Eindrücken des Autors C in Bezug auf die Bequemlichkeit und Geschwindigkeit des Schreibens von Programmen erheblich. Die Entwicklungsumgebung (IDE) spielt eine wichtige Rolle bei der Bereitstellung von Komfort. Der Autor arbeitete mit der IntelliJ IDEA IDE. Wenn Sie in Java programmieren, müssen Sie nicht ständig Angst haben, einen Fehler zu machen. Oft sagt Ihnen die Entwicklungsumgebung, was behoben werden muss, und manchmal erledigt sie dies für Sie. Wenn ein Laufzeitfehler aufgetreten ist, werden die Art des Fehlers und der Ort seines Auftretens im Quellcode immer im Protokoll angegeben - der Kampf gegen solche Fehler wird zu einer trivialen Angelegenheit. Ein C-Programmierer muss keine unmenschlichen Anstrengungen unternehmen, um zu Java zu wechseln, und das alles, weil sich die Syntax der Sprache geringfügig geändert hat.Wenn diese Erfahrung für Leser interessant sein wird, werden wir im nächsten Artikel über die Erfahrung mit der Verwendung des JNI-Mechanismus (Ausführen von nativem C / C ++ - Code aus einer Java-Anwendung) sprechen. Der JNI-Mechanismus ist unverzichtbar, wenn Sie die Bildschirmauflösung und das Bluetooth-Modul steuern möchten, und in anderen Fällen, wenn die Funktionen von Android-Diensten und -Managern nicht ausreichen.