Java-Syntax

aus Wikipedia, der freien Enzyklopädie
Dies ist eine alte Version dieser Seite, zuletzt bearbeitet am 18. September 2008 um 18:56 Uhr durch 136.172.253.11 (Diskussion) (Der Hinweis ist nutzlos, da *keine* Sprache IEEE754 einhält->siehe Diskussion). Sie kann sich erheblich von der aktuellen Version unterscheiden.
Zur Navigation springen Zur Suche springen

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 (siehe unter Weblinks).

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 Typ 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 U+0000 … U+FFFF Unicode-Zeichen (z. B. 'A' oder '\uC3A4')
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)
  • „Größe“ gibt den minimalen Speicherverbrauch an.
Typumwandlung

Die Hierarchie für das Umwandeln (type casting) der primitiven Datentypen lässt sich aus der oberen Tabelle erahnen. Die numerischen Datentypen lassen sich verlustfrei in den nächst größeren Datentyp umrechnen, jedoch nicht umgekehrt. So kann ein „int“ implizit, also ohne speziellen Operator, in ein „long“ umgewandelt werden, jedoch nicht umgekehrt.

int  i = 12345;
long l = i;

Um jedoch in umgekehrter Richtung beispielsweise einen „long“ in ein „int“ umzuwandeln, muss der Typumwandlungsoperator verwendet werden. Dabei können auch Informationen verloren gehen.

long l = 12345678901L;
int  i = (int) l;

Hier tritt ein Informationsverlust auf: „i“ hat nach der Zuweisung den Wert −539222987, weil die höherwertigen Bytes abgeschnitten werden.

Der primitive Datentyp „boolean“ kann implizit in keinen anderen Datentyp umgewandelt werden. Zeichen vom Typ „char“ können implizit in jeden ganzzahligen Typ ab „int“ umgewandelt werden. Dies hängt insbesondere damit zusammen, dass „char“ den einzigen in Java bekannten vorzeichenlosen Datentyp darstellt. Damit ist eine verlustfreie Konvertierung nach „short“ ab dem Wert 32768 nicht mehr möglich.

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 Zugriffsmodifikatoren (access modifier):

Die Klasse selbst Paket-Klassen/
innere Klassen
Unterklassen Sonstige
Klassen
private Ja Nein[A 1] Nein Nein
(ohne)[A 2] Ja Ja Nein Nein
protected Ja Ja Ja Nein
public Ja Ja Ja Ja
  1. Um inneren Klassen den Zugriff auf private Methode und Eigenschaften dennoch zu ermöglichen, werden vom Compiler statische, paket-private Methoden erstellt, die den Aufruf, das Setzen oder das Auslesen emulieren. Diese Methoden tragen den Namen access$xxx, wobei xxx für eine fortlaufende Nummer steht.
  2. Häufig auch als „package private“ bezeichnet, obwohl es diesen Zugriffsmodifikator nicht gibt.

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).

static kennzeichnet Implementierungen, die ohne eine Objekt-Referenz verwendbar sind. Das Schlüsselwort wird bei der Deklaration von Feldern, Methoden und inneren Klassen verwendet. Felder, Methoden und Klassen, die mittels static gekennzeichnet sind, werden im Kontext der Klasse verwendbar und sind an kein Objekt gekoppelt.

abstract kann vor Klassen und Methoden stehen. Abstrakte Methoden verfügen über keine Implementierung und müssen in abgeleiteten Klassen überschrieben werden. Klassen, die abstrakte Methoden enthalten, müssen selbst als abstrakt deklariert werden. Abstrakte Klassen können nicht instanziiert werden, auch wenn sie vollständig implementiert sind, wie z. B. im Fall der EventListener-Adapterklassen des Swing-Frameworks.

final kann vor Variablen, Feldern, Methoden und Klassen stehen und erzwingt Unveränderlichkeit. Bei Variablen und Membervariablen ihre Konstanz, bei Methoden die Unmöglichkeit, sie in abgeleiteten Klassen zu überschreiben und schließlich bei Klassen, dass von ihnen keine weiteren Ableitungen erfolgen können. Einer finalen Variable wird einmalig ein Wert zugewiesen, der nachträglich nicht mehr verändert werden kann. Eine finale Variable muss nicht in der Deklaration initialisiert werden, sondern kann auch erst in folgenden, alternativen Anweisungen einmalig belegt werden:

public int calcFinal(int a)
  final int b; // hier findet noch keine Zuweisung statt
  switch (a) {
    case 1:
      b = 7;
    break;
    case 2:
      b = 3;
    break;
    default:
      b = 1;
    break;
  }
  return b;
}

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 Internet-Domain des Eigentümers oder Erstellers.

import importiert Symbole (vorher nur Typen, ab Java 5 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 Java 5) und @interface (ab Java 5) dienen zur Deklaration eigener Typen: Klassen, Interfaces (Schnittstellen für Klassen), Enums (Aufzählungstyp für typsichere Aufzählung, englisch typesafe enumeration) und Annotations für Metadaten. enum und @interface sind mit Java 5 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 angegeben werden. Es handelt sich also um so genannte checked exceptions. try umschließt einen Block, in dem eventuell eine Ausnahme auftreten könnte. catch fängt nach einem try-Block die dort aufgetretene Ausnahme ab, finally wird an einen try- oder catch-Block für Aufräumarbeiten wie das Schließen von Dateien angehängt.

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) oder 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 Modifikator 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 kennzeichnet einen kritischen Abschnitt im Quelltext, der nur von einem Thread gleichzeitig ausgeführt werden darf. Ein Monitor sperrt den Abschnitt, sobald ihn ein Thread betritt. Versucht ein anderer Thread den gesperrten Abschnitt zu betreten, blockiert dieser Thread solange bis der Monitor den Abschnitt freigibt. Jedes beliebige Java-Objekt kann als Monitor verwendet werden. Ist eine Methode mit synchronized gekennzeichnet, wird automatisch das Objekt bzw. bei statischen Methoden das Klassenobjekt als Monitor verwendet.

transient kennzeichnet nicht-persistente Variablen, die nicht serialisiert werden dürfen.

instanceof ist ein Java-Operator, der prüft, ob ein Objekt Exemplar eines angegebenen 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 Methoden-Deklarationen, 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 eines Objekts wird in 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 so genannten „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 Gleitkommazahlen als Parameter und gibt die Summe der beiden ebenfalls als Gleitkommazahl zurück. Falls eine der beiden Zahlen keine gültige Gleitkommazahl ist, wirft die Methode 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 Klassenvariablendeklaration mit Initialisierung:

private static final int X = 2;

Anweisungen

Java unterstützt die üblichen Anweisungen, die auch von anderen Programmiersprachen bekannt sind. Einfache Anweisungen werden mit Semikolon abgeschlossen. Mehrere Anweisungen können mit geschweiften Klammern zu einem Anweisungsblock zusammengefasst werden.

Als Kontrollstrukturen stehen zur Verfügung:

  • Die gewöhnliche binäre Verzweigung (ein Schlüsselwort „then“ existiert nicht):
 if (''Bedingung'') { ''Anweisung1''; } 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 vier verschiedene Arten von Schleifen.

  • Wiederhole eine Anweisung solange die Bedingung wahr ist, wobei die Bedingung vor der Anweisung geprüft wird (While-Schleife).
while (''Bedingung'') { ''Anweisung''; }
  • Wiederholung einer Anweisung solange die Bedingung wahr ist, wobei die Bedingung erst nach der Ausführung geprüft wird (Do-while-Schleife).
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-Schleife).
for (''Initialisierung''; ''Bedingung''; ''Anweisung2'') { ''Anweisung1''; }
  • Zudem existiert eine erweiterte For-Schleife, auch Foreach-Schleife genannt, die es erlaubt, für jedes Element eines iterierbaren Objektes (Reihungen, Listen, etc.) Anweisungen auszuführen.
for (''Element'' : ''Kollektion'') { ''Anweisung1''; ... ''AnweisungN''; }

Die nachfolgenden Beispiele zeigen den Einsatz einer solchen erweiterten For-Schleife, einmal angewendet auf den Basisdatentyp char und einmal auf eine Liste.

char[] chars = new char[] { 'a', 'b', 'c' }; // neues Zeichen-Array anlegen
for (char c : chars) {
    System.out.println("Zeichen: " + c); // Jedes Zeichen auf die Konsole schreiben.
}

List<Character> charlist = new ArrayList<Character>(); // neue Zeichen-Liste anlegen
charlist.add('a'); // Werte hinzufügen (mit Autoboxing)
charlist.add('b');
charlist.add('c');
for (Character c : charlist) {
    System.out.println("Zeichen: " + c); // Jedes Zeichen auf die Konsole schreiben.
}

Ausdrücke

Im Gegensatz zu anderen 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. Außerdem gilt – wie in C auch – dass die Auswertung von logischen Ausdrücken verkürzt werden kann, wenn das Ergebnis bereits feststeht. Im folgenden Beispiel wird also garantiert zuerst überprüft, ob „object“ nicht „null“ ist. Ist dieser Teilausdruck false (also object ist eine Nullreferenz), wird der rechte Operand von && gar nicht ausgewertet, d. h. die Methode inOrdnung() wird nicht gesucht oder gar gerufen. Somit ist der Gesamtausdruck 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.inOrdnung()) {
    System.out.println("in Ordnung");
} else {
    System.out.println("ungeprüft oder Prüfung misslungen");
}

Werden statt der logischen Operatoren && und || die Bit-Operatoren & und | verwendet, so müssen beide Teilergebnisse bitweise verknüpft werden und die Verarbeitung kann nicht beim linken Ausdruck abbrechen, falls der false ist. Hat also die Variable object den Wert null, so versucht die Java-VM, den aus dem ersten Ausdruck resultierenden Wert false mit dem Ergebnis des zweiten Ausdrucks bitweise zu verknüpfen. Um den zweiten Ausdruck zu berechnen, versucht die VM object zu dereferenzieren und wirft eine NullPointerException.

Generische Programmierung

Mit Version 5 von Java wurde das Sprachmittel der generischen Programmierung eingeführt.

Hauptartikel: Generische Programmierung in Java

Wikibooks: Java – Lern- und Lehrmaterialien
  • Die Java Language Specification (Java-Sprachspezifikation) definiert die Semantik und die Syntax der Programmiersprache Java. (englisch)
  • Java Syntax Diverse Tabellen zur Java Syntax (deutsch)

Quellen