Java-Syntax
Die Java-Syntax, also die Syntax der Programmiersprache Java ist, ebenso wie deren Semantik, in der Java Language Specification definiert.
Im folgenden soll nur ein grober Überblick über die Java-Syntax gegeben und deren Besonderheiten herausgestellt werden, für die Details sei auf die Java-Sprachspezifikation von SUN Microsystems und das WikiBook Java verwiesen.
Lexikalische Struktur
Java-Programme werden in Unicode geschrieben. Im Gegensatz zu anderen Sprachen können Bezeichner (englisch Identifier) von Klassen, Methoden, Variablen usw. nicht nur die Buchstaben des lateinischen Alphabets und Ziffern enthalten, sondern auch Zeichen aus anderen Alphabeten, wie z. B. deutsche Umlaute oder chinesische Schriftzeichen.
Sämtliche Schlüsselworte in Java werden klein geschrieben, z. B. "class
" oder "if
".
Buchstabensymbole (englisch literals) sind in Java die kleinstmöglichen Ausdrücke für Zahlen, einzelne Zeichen, Zeichenketten (Strings), logische Werte (true
oder false
) und das spezielle Wort null
.
Trennzeichen (englisch separators) sind verschiedene Arten von Klammern sowie Komma, Punkt und Semikolon.
Java kennt die in Programmiersprachen üblichen logischen, Vergleichs- und mathematischen Operatoren. Die Syntax orientiert sich dabei an der Programmiersprache C++. So dient zum Beispiel das einfache Gleichheitszeichen "=
" als Zuweisungsoperator, während für Vergleiche das doppelte Gleichheitszeichen "==
" verwendet wird.
Leerzeichen, Zeilenenden und Kommentare können an beliebigen Stellen zwischen den Bezeichnern, Schlüsselworten, Buchstabensymbolen, Trennzeichen und Operatoren eingefügt werden. Eine Besonderheit von Java sind die so genannten Dokumentationskommentare, die mit "/**
" beginnen und mit "*/
" abgeschlossen werden. Diese Dokumentationskommentare werden von dem Werkzeug Javadoc ausgewertet, um aus Java-Quelltext z. B. Dokumentationsdateien in HTML zu generieren.
Syntax
Datentypen
Java kennt zwei Datentyparten: primitiver Datentyp und Referenz. Erstere gehört zu den Gründen, die Java strenggenommen von einer reinen objektorientierten Sprache unterscheidet.
Primitive Datentypen
Es gibt acht primitive Datentypen. Sie haben unterschiedliche Größen und Eigenschaften und werden zum Berechnen und Speichern diskreter Zahlenwerte benutzt. Sie sind ebenfalls plattformunabhängig, weil sie stets im Big Endian-Format gespeichert werden. Für jeden Typen existiert eine entsprechende Wrapper-Klasse, um auch diese als echte Objekte behandeln zu können.
Datentyp | Größe¹ | Wrapper-Klasse | Wertebereich | Beschreibung |
---|---|---|---|---|
boolean | 8 bit | java.lang.Boolean | true / false | Boolescher Wahrheitswert |
char | 16 bit | java.lang.Character | 0 ... 65.535 (z. B. 'A') | Unicode-Zeichen (UTF-16) |
byte | 8 bit | java.lang.Byte | −128 ... +127 | Zweierkomplement-Wert |
short | 16 bit | java.lang.Short | −32.768 ... +32.767 | Zweierkomplement-Wert |
int | 32 bit | java.lang.Integer | −2.147.483.648 ... +2.147.483.647 | Zweierkomplement-Wert |
long | 64 bit | java.lang.Long | −9.223.372.036.854.775.808 ... +9.223.372.036.854.775.807 |
Zweierkomplement-Wert |
float | 32 bit | java.lang.Float | ±1,4E−45 ... ±3,4E+38 | Gleitkommazahl (IEEE 754) |
double | 64 bit | java.lang.Double | ±4,9E−324 ... ±1,7E+308 | Gleitkommazahl doppelter Genauigkeit (IEEE 754) |
¹: minimaler Speicherverbrauch
Referenzen
Alle Objekte und Felder liegen im Heap-Speicher und werden deshalb über eine Adresse referenziert. Der Zeiger mit einer solchen Adresse ist eine so genannte Referenz und deren Adresse kann nicht direkt, sondern nur durch Zuweisung verändert werden.
Object a = new Object(); // a hat eine Referenz zu einem gerade neuerstellten Objekt Object b = a; // b hat eine Kopie der Referenz von a a = null; // a hat keine Referenz mehr
Reservierte Wörter
const
und goto
sind zwar reserviert, aber ohne Funktion, also keine Schlüsselwörter im eigentlichen Sinn. Sie dienen lediglich dem Compiler zur Ausgabe sinnvoller Fehlermeldungen für Umsteiger von C++ oder C.
true
, false
und null
sind Literale, jedoch ebenfalls eigentlich keine Schlüsselwörter im engeren Sinn.
Mit assert
werden Assertions realisiert.
private
, protected
und public
sind Zugriffsmodifizierer (access modifier):
Die Klasse selbst | Paket-Klassen/ innere Klassen |
Unterklassen | Sonstige Klassen | |
---|---|---|---|---|
private | Ja | Nein | Nein | Nein |
(ohne) | Ja | Ja | Nein | Nein |
protected | Ja | Ja | Ja | Nein |
public | Ja | Ja | Ja | Ja |
Private Methoden sind von der Polymorphie ausgenommen, d. h. die Definition einer Methode derselben Signatur in einer Subklasse gilt nicht als Überschreiben (sondern als Verbergen).
abstract
, final
, static
sind Polymorphie-Modifizierer.
static
kann vor allen Membern außer Konstruktoren stehen und bedeutet, dass der betreffende Member im Kontext der Klasse verwendbar ist und kein Objekt benötigt.
abstract
kann vor Klassen und Methoden stehen und bedeutet, dass dem betreffenden Member die Implementierung fehlt. Unterklassen sind gezwungen, sich selbst als abstrakt zu deklarieren oder die fehlende Implementierung zu liefern.
final
kann vor allen Membern außer Konstruktoren stehen und bedeutet, dass der betreffende Member nicht mehr verändert werden darf. Finale Variablen sind ähnlich wie Konstanten, finale Methoden dürfen von Subklassen nicht überschrieben werden und von finalen Klassen darf man erst gar keine Subklassen bilden. Zugriffe auf finale Variablen, deren Wert dem Compiler bekannt ist, dürfen vom Compiler durch den Wert der Variable ersetzt werden. Aufrufe finaler Methoden dürfen vom Compiler durch eingebundenen Code (Inlining) ersetzt werden. Private Methoden sind automatisch final.
native
kann nur vor Methoden stehen und bedeutet, dass die Implementierung der betreffenden Methode nicht in Java, sondern einer anderen Programmiersprache geschrieben wurde, und von der virtuellen Maschine über eine Laufzeitbibliothek eingebunden werden muss.
strictfp
kennzeichnet Klassen und Methoden, deren enthaltene Fließkommaoperationen streng nach IEEE ablaufen müssen.
package
deklariert die Paketzugehörigkeit eines komplexen Datentyps. Die Namensgebung eines Pakets sollte eindeutig sein und orientiert sich meist an der URI/URL des Eigentümers bzw. Erstellers.
import
importiert Symbole (vorher nur Typen, ab 5.0 auch statische Member von Klassen), so dass sie ohne voll qualifizierten Namen verwendet werden können. Der Import kann hierbei über das Wildcard *
auf komplette Pakete ausgedehnt werden.
boolean
, char
, byte
, short
, int
, long
, float
, double
und void
sind Typen. void
ist der Nichtstyp, notwendig, um Methoden ohne Rückgabewerte zu kennzeichnen. Für die primitiven Typen: siehe oben.
class
, interface
, enum
(ab 5.0) und @interface
(ab 5.0) dienen zur Deklaration eigener Typen: Klassen, Interfaces (Schnittstellen für Klassen), Enums (Aufzählungstyp für typsichere Aufzählung, engl. typesafe enumeration) und Annotations für Metadaten. enum
und @interface
sind mit Java 5.0 in die Sprache aufgenommen worden.
try
, catch
, finally
, throw
, throws
beziehen sich auf die Ausnahmebehandlung (englisch exception handling). Mit throw
wird eine Ausnahme ausgelöst. Alle eventuell ausgelösten Ausnahmen einer Methode, die nicht von RuntimeException
oder Error
abstammen, müssen mit throws
in der Deklaration der Methode (Signatur) angegeben werden. Es handelt sich also um so genannte checked exceptions. Mit try
umschließt man einen Block, in dem eventuell eine Ausnahme auftreten könnte. Mit catch
fängt man nach einem try
-Block die dort aufgetretene Ausnahme ab, mit finally
hängt man an einen try
- oder catch
-Block einen Block für Aufräumarbeiten an (z. B. Schließen von Dateien).
extends
und implements
dienen der Vererbung: extends
der genetischen Erweiterungsvererbung von Klasse zu Klasse oder Interface zu Interface und implements
der Implementierungsvererbung von Interface zu Klasse. Außerdem wird extends
bei Generics für Typerweiterung verwendet.
super
und this
beziehen sich im Objekt-Kontext auf das aktuelle Objekt in seinem tatsächlichen Morph (this
) bzw. im Morph der Superklasse (super
). this
wird verwendet, um in Konstruktoren überladene Konstruktoren aufzurufen und um in Membern auf verdeckte Member äußerer Strukturen zu verweisen. super
wird in Konstruktoren zum Aufruf des Superklassenkonstruktors und in überschreibenden Methoden zum Aufruf der überschriebenen Methode verwendet. Außerdem wird super
bei Generics für Typeingrenzung verwendet.
new
reserviert Speicher für neue Objekte (inklusive Arrays) auf dem Heap.
if
, else
, switch
, case
, default
, while
, do
, for
, break
, continue
dienen der Bedingungsprüfung und Schleifensteuerung und bedienen sich einer imperativen Syntax.
return
liefert Werte aus einer Methode an die aufrufende Methode zurück.
volatile
ist ein Modifizierer für nicht-lokale Variablen und verbietet dem JIT-Compiler Registeroptimierungen auf diese Variable, weil mehrere Threads sie gleichzeitig verwenden könnten (insbesondere im Kontext nativer Methoden).
synchronized
verwendet ein Objekt als Semaphor mit Lock-Pool und sorgt so dafür, dass jeglicher mit synchronized
an denselben Semaphor gebundener Code nur ein einziges Mal gleichzeitig ausgeführt werden kann. Alle anderen Threads, die solche Code-Blöcke ausführen möchten, werden solange blockiert, bis der Semaphor wieder frei ist. Des weiteren dient es auch der Synchronisation eventuell vorhandener lokaler Caches bei Multiprozessormaschinen.
transient
kennzeichnet nicht-persistente Variablen, die nicht serialisiert werden dürfen.
instanceof
ist ein Java-Operator, der prüft, ob ein Objekt Instanz eines Typs oder Subtyps ist.
Pakete, Namen, Klassen
Ein Java-Paket (englisch package) enthält mehrere Klassen, Schnittstellen und Ausnahmen und bildet einen eigenen Namensraum.
Java-Quelltext ist auf mehrere Übersetzungseinheiten (englisch compilation units) aufgeteilt, von denen jede in einer eigenen Datei abgelegt ist. Jede einzelne Übersetzungseinheit definiert als erstes das Paket, dem sie angehört, importiert dann mehrere Klassen oder Schnittstellen aus anderen Paketen, und definiert schließlich eine oder mehrere Klassen und Schnittstellen.
Java unterscheidet einfache Namen, die nur aus einem Bezeichner bestehen, und vollqualifizierte Namen, die aus einer Reihe von Bezeichnern bestehen, die durch Punkte getrennt sind. Die einfachen Namen sind nur ein Hilfsmittel, das dem Programmierer Tipparbeit ersparen und die Lesbarkeit des Programms erhöhen soll. Der Compiler übersetzt sie immer in vollqualifizierte Namen.
Das folgende Beispiel zeigt ein Hallo-Welt-Programm in Java.
package org.wikipedia; import static java.lang.System.out; public class HalloWelt { public static void main(String[] arguments) { out.println("Hallo Welt."); } }
In diesem Beispiel wird eine Klasse "HalloWelt
" definiert, die dem Paket "org.wikipedia
" angehört. Per Konvention orientieren sich die Paketnamen in Java an den Internet-Domänen ihrer Entwickler, in diesem Fall also "org.wikipedia
" für die Domäne "wikipedia.org
". Die Richtung des Namens wird umgedreht, weil bei Internet-Domänen der Name der äußersten Einheit hinten steht, während er in Java vorne steht. Das Paket "org.wikipedia
" befindet sich also "innerhalb" des Pakets "org
".
Eine Import-Anweisung definiert einen einfachen Namen für einen vollqualifizierten Namen. So definiert z. B. die Anweisung "import java.io.File
" den einfachen Namen "File
" für die Klasse, die mit vollem Namen "java.io.File
" heißt.
Neben Imports von Klassen und Schnittstellen können seit Java Version 5 auch statische Felder oder Methoden von Klassen importiert werden. Ein solcher "statischer Import" wird durch das zusätzliche Schlüsselwort "static
" festgelegt. Im obigen Beispiel wird das statische Feld "out
" der Klasse "java.lang.System" importiert und anschließend unter dem kurzen Namen "out
" verwendet.
Das wichtigste Konstrukt der Programmiersprache Java ist, da sie eine objektorientierte Sprache ist, die Klasse. Die Deklaration einer Klasse wird mit dem Schlüsselwort "class
" eingeleitet. Anschließend folgt der Name der Klasse und dann werden - in geschweiften Klammern - die Felder und Methoden der Klasse definiert.
Eine Besonderheit von Java stellt die Schnittstelle dar. Eine solche Schnittstelle besteht nur aus abstrakten Methoden, deren Implementierung erst von den Klassen festgelegt werden, die sie "implementieren". Die Deklaration einer Schnittstelle sieht ähnlich aus wie die Deklaration einer Klasse, sie wird jedoch mit dem Schlüsselwort "interface
" eingeleitet.
package org.wikipedia; public interface Article { String getName(); void setContent(String aContent); }
Da alle Methoden einer Schnittstelle abstrakt sind, kann das Schlüsselwort "abstract
" bei den einzelnen Methoden entfallen. Ebenso kann das Schlüsselwort "public
" vor den einzelnen Methoden entfallen, weil Methoden einer Schnittstelle immer öffentlich sind, also aus allen anderen Paketen sichtbar.
Methoden und Felder
Das eigentliche Verhalten einer Klasse wird in ihren Methoden definiert. Die Signatur einer Methode besteht aus ihrem Namen und den Typen ihrer Parameter. Außerdem hat jede Methode einen bestimmten Rückgabetyp oder "void
", wenn sie nichts zurück gibt, und kann eine Reihe von Ausnahmen in einer sogenannten "throws
"-Klausel definieren.
Beispiel einer konkreten Methode.
public double summe(double a, double b) throws NotANumberException { if (Double.isNaN(a) || Double.isNaN(b)) { throw new NotANumberException(); } return a + b; }
Diese Methode erwartet zwei doppelt genaue Fließkommazahlen als Parameter und gibt die Summe der beiden ebenfalls als Fließkommazahl zurück. Falls eine der beiden Zahlen keine gültige Fließkommazahl ist, wirft sie eine Ausnahme namens "NotANumberException
".
Beispiel einer abstrakten Methode.
public abstract double summe(double a, double b) throws NotANumberException;
Felder sind Variablen, die zu einem Objekt gehören. Sie sind durch einen Typ und einen Namen definiert und können wahlweise bereits wenn das Objekt erzeugt wird, initialisiert werden.
Beispiel einer Variablendeklaration ohne Initialisierung.
private int x;
Klassenvariablen werden als statische Felder deklariert. Diese Felder existieren nicht einmal pro Objekt, sondern nur einmal pro Klasse. Das Schlüsselwort "final
" verhindert, das eine Variable nach ihrer Initialisierung ihren Wert ändert. Es wird zum Beispiel verwendet, um Konstanten zu definieren.
Beispiel einer statischen und finalen Variablendeklaration mit Initialisierung.
private static final int X = 2;
Anweisungen
Java unterstützt die üblichen Anweisungen, die auch von anderen Programmiersprachen bekannt sind. Mehrere Anweisungen werden durch Semikolons getrennt und mit geschweiften Klammern zusammengefasst.
Als Kontrollstrukturen stehen zur Verfügung:
- Die gewöhnliche binäre Verzweigung (ein Schlüsselwort "
then
" existiert nicht):
if (Bedingung) { Anweisung; } else { Anweisung2; }
- Eine Fallunterscheidung, wobei der Ausdruck nur ein logischer Wert, ein Zeichen oder eine ganze Zahl sein kann.
switch (Ausdruck) { case Konstante1: Anweisung1; break; case Konstante2: Anweisung2; break; default: Anweisung3; }
Java unterscheidet drei verschiedene Arten von Schleifen.
- Wiederhole eine Anweisung solange die Bedingung wahr ist, wobei die Bedingung vor der Anweisung geprüft wird.
while (Bedingung) { Anweisung; }
- Wiederholung einer Anweisung solange die Bedingung wahr ist, wobei die Bedingung erst nach der Ausführung geprüft wird.
do { Anweisung; } while (Bedingung);
- Initialisiert einen Wert, wiederholt die Anweisung solange die Bedingung wahr ist und führt eine zweite Anweisung nach jedem Schleifendurchlauf aus.
for (Initialisierung; Bedingung; Anweisung2) { Anweisung1; }
Ausdrücke
Im Gegensatz zu einigen älteren Programmiersprachen, wie beispielsweise C, ist die Reihenfolge der Auswertung von Ausdrücken in Java bereits in der Sprachdefinition festgelegt: Operatoren gleichen Rangs werden immer von links nach rechts ausgewertet. Im folgenden Beispiel wird also garantiert zuerst geprüft, dass "object
" nicht "null
" ist, bevor dessen Methode "pruef
" aufgerufen wird. Da außerdem der rechte Teil des logischen Und-Operators nur dann ausgewertet wird, ist dieser Ausdruck sicher und kann niemals eine "NullPointerException
" verursachen. (Eine "NullPointerException
" wird in Java immer dann geworfen, wenn versucht wird, die Objektreferenz "null
" aufzulösen.)
if (objekt != null && objekt.pruef()) { System.out.println("geprüft"); } else { System.out.println("ungeprüft oder Prüfung misslungen"); }
Weblinks
http://java.sun.com/docs/books/jls/third_edition/html/j3TOC.html - die Java Language Specification (Java-Sprachspezifikation) definiert die Semantik und die Syntax der Programmiersprache Java.