Typen und Operatoren

Java Schlüsselworte

Java hat 50 Schlüsselworte (keywords)

abstract  continue  for         new        switch
assert    default   if          package    synchronized
boolean   do        goto        private    this
break     double    implements  protected  throw
byte      else      import      public     throws
case      enum      instanceof  return     transient
catch     extends   int         short      try
char      final     interface   static     void
class     finally   long        strictfp   volatile
const     float     native      super      while

Java Schlüsselworte dürfen nicht für Bezeichner (identifier), d. h. Variablennamen, Klassennamen oder Methodennamen verwendet werden.

Das Schlüsselwort goto ist zwar reserviert, wird aber in der Sprache gar nicht verwendet. Vermutlich hat man es zum Schlüsselwort gemacht, weil in der Sprache C goto ebenfalls ein Schlüsselwort ist (mit dem man unbedingte Sprünge machen kann) und man sich die Möglichkeit offen halten wollte, später Sprünge in Java einführen zu können. Da goto aber zum sogenannten Spaghetticode führt, ist es als positiv zu werten, dass Java solche Sprünge nicht unterstützt.

Im Rahmen dieser Vorlesung werden nahezu alle Schlüsselworte behandelt werden. Die einzige Ausnahme stellen strictfp, native und assert dar.

  • strictfp kennzeichnet Methoden und Klassen, bei denen Gleitkommaberechnungen strikt nach den Regeln von IEEE745 ablaufen müssen.
  • native kennzeichnet Methoden, die nicht in Java, sondern direkt nativ für die Plattform, z. B. in C++, entwickelt wurden.
  • assert erlaubt es, Zusicherungen (Assertions) direkt im Quelltext anzugeben.

Kommentare

// Dies ist ein Kommentar in einer Zeile

/* dies ist ein Kommentar
   über mehrere Zeilen
   (Blockkommentar) */

/** Hier kommt JavaDoc */

/** Hier kommt JavaDoc,
  * gerne auch Mehrzeilig */

/* Das geht nicht, da
   /* Mehrzeilige Kommentare nicht
      geschachtelt werden können
    */
*/

Java kennt drei Arten von Kommentaren:

  • Einzeilige Kommentare, die vom // bis zum Ende der Zeile gehen
  • Mehrzeilige Kommentare, die von /* und */ eingeschlossen werden müssen
  • JavaDoc-Kommentare, die mit /** beginnen und mit */ enden

Kommentare können nicht geschachtelt werden, d. h. es ist nicht möglich mehrere mehrzeilige Kommentare ineinander zu verschachteln. Eine Ausnahme sind einzeilige Kommentare, die von einem mehrzeiligen Kommentar umschlossen werden können, d. h. man kann schreiben:

/* Kommentar
   // Einzeiliger Kommentar
*/

Praxis-Tipp: Kommentare

  • Innerhalb von Methoden immer nur Zeilenkommentare verwenden
  • So können Methoden einfach durch einen Blockkommentar auskommentiert werden
void methodeMitKommentaren() {
    // Erzeuge zwei Strings mit sinnvollem Inhalt
    String a = "Hallo";
    String b = "Welt";

    // Vertausche die Variablen
    swap(a, b);

    // Drucke Ergebnis aus
    System.out.println(a + " " + b);
}

Wenn Sie Eclipse verwenden, können Sie sehr komfortable Code aus- und wieder einkommentieren, indem Sie einfach CTRL-SHIFT-C drücken.

Primitive Datentypen

Java hat eine Reihe von eingebauten Datentypen, die nicht durch Klassen repräsentiert werden. Sie werden primitive Datentypen (primitive types) genannt

  • Logisch – boolean
    • Zwei mögliche Werte: true und false
  • Ganzzahl – byte, short, int, long
    • 8, 16, 32, 64 Bit vorzeichenbehaftete Ganzzahlen
  • Fließkomma – float, double
    • 32 bzw. 64 Bit Fließkommazahlen
  • Zeichen – char
    • 16 Bit Unicode-Zeichen

Die primitiven Datentypen existieren in Java primär aus Performancegründen. Im Gegensatz zu anderen Sprachen (z. B. Ruby) hat man sich entschlossen, neben den klassenbasierten auch noch primitive Datentypen in Java zu verwenden. Da die primitiven Datentypen eine einfache Wertsemantik haben, kann man Operationen auf ihnen deutlich schneller ausführen als auf entsprechenden Klassentypen. Bei einem klassenbasierten Datentyp enthält die Variable nur eine Referenz auf das Objekt, das dann die Daten trägt. Jeder Zugriff auf die Variable im Objekt benötigt daher zwei Operationen: (1) Adresse des Objektes aus der Referenz lesen, (2) über die Adresse auf den Wert im Objekt zugreifen. Bei primitiven Datentypen enthält die Variable direkt den Wert, sodass man sich einen Schritt spart und der Zugriff damit schneller erfolgen kann.

Zu jedem primitiven Datentyp gibt es einen sogenannten Wrapper, d. h. einen korrespondierenden Typen, der auf einer Klasse basiert. Diese werden später in der Vorlesung noch behandelt.

Mit Java SE 5.0 wurde durch das Autoboxing ein Mechanismus eingeführt, bei dem der Compiler automatisch einen primitiven Typen in eine Wrapper umwandelt und umgekehrt, wenn an einer entsprechenden Stelle im Quelltext ein Objekt bzw. ein primitiver Typ benötigt wird. Dieses Feature werden wir später noch genauer beleuchten.

Bei char handelt es sich um ein einzelnes 16 Bit Unicode-Zeichen (in einer UCS2-Codierung), das nicht mit dem Klassentyp String verwechselt werden darf, der eine beliebig lange Kette von Zeichen enthalten kann. Technisch gesehen ist ein char tatsächlich eine vorzeichenlose Zahl. Seit Java 5 wird der Unicode 4.0-Standard unterstützt, der 32 Bit pro Zeichen vorsieht. Da die Breite des Datentyps char aber auf 16 Bit beschränkt ist, hat man sich entschieden, Strings intern als UTF-16 abzubilden. Damit sind die allermeisten, gebräuchlichen Zeichen in 16 Bit abbildbar und für die wenigen Ausnahmen werden dann mehr als ein char zur Kodierung benötigt.

Bei den Fließkommazahlen handelt es sich um Datentypen, die für wissenschaftlich-technische Anwendungen gedacht sind, die aber gerade nicht in der Lage sind bestimmte Dezimalzahlen, z. B. bei der Arbeit mit Währungen, mit exakter Genauigkeit darzustellen. So ist es zum Beispiel nicht möglich, die Zahl 0.1 exakt mithilfe von Fließkommazahlen darzustellen, da es immer zu Rundungseffekten kommt. Wenn exakte Dezimalzahlen benötigt werden, muss man entweder mit int und long arbeiten und den Dezimalpunkt selbst verwalten oder auf die Klasse BigDecimal zurückgreifen.

Literale für Ganzzahlen

  • Vorzeichen
    • Positives Vorzeichen kann weggelassen werden: 476 oder +467
    • Negatives Vorzeichen ohne Leerzeichen: -3521
  • Datentyp
    • Standardmäßig hat das Literal den Typ int
    • Für long muss ein L angehängt werden 7_000_000_000L
  • Prefix (ohne für Dezimalzahlen)
    • 0 für Oktalzahlen
    • 0x für Hexadezimalzahlen
    • 0b für Binärzahlen

Der Prefix für Oktalzahlen ist eine häufige Falle für Programmierneulinge. In der Mathematik ist es völlig egal, ob man 42 oder 042 schreibt. Beides bezeichnet dieselbe Zahl. In Java ist das anders, da durch die führende 0 eine Oktalzahl angezeigt wird. 042 entspricht der Dezimalzahl 34.

Seit Java 8 darf man an beliebigen Stellen einen Unterstrich _ zur Trennung der Ziffern einfügen.

Strings

Für String-Literale sind die doppelten Anführungszeichen " reserviert, einfache Anführungszeichen ' werden für Zeichen-Literale vom Typ char verwendet. Damit folgt Java der Tradition von C und weicht von anderen Sprachen wie JavaScript, Python, PHP und Ruby ab, bei denen Strings sowohl mit ' als auch mit " angegeben werden können.

String ist ein Referenzdatentyp in Java

  • Literale mit "
  • Verkettung mit +
  • Mehrzeilige Literale mit """ (führende Leerzeichen werden entfernt)
String s1 = "Ein Literal";
String s2 = "Ein verkettetes\n"
            + "Literal";
String s3 = """
     Ein mehrzeiliger
     String""";

Strings sind unveränderlich (immutable). Dies bedeutet, dass Strings nach ihrer Erzeugung nicht mehr verändert werden können. Jede Änderung erzeugt ein neues String-Object.

String s1 = "Hallo";
String s2 = s1 + " Welt";
System.out.println(s2 == s1); // -> false

Das Beispiel zeigt, dass durch die Verkettung ein neuer String entsteht. Dies kann man sich syntaktisch noch gut erklären. Aber spätestens beim folgenden Beispiel erkennt man die Unveränderlichkeit deutlich.

String s = "Hallo";
s.replace('a', '4');
System.out.println(s); // -> "Hallo"

Die Ersetzung geht ins Leere, weil der Rückgabewert der replace-Methode (der neu erzeugte String) nicht „abgeholt“ wird.

StringBuilder und StringBuffer

Dadurch, dass Strings unveränderlich sind, sind viele Operationen ineffizient. Baut man z. B. einen String schrittweise mit + zusammen, werden sehr viele unnötige Objekte erzeugt, die sofort wieder verworfen werden. Deswegen gibt es in Java zwei andere Klassen, die veränderbare Strings zur Verfügung stellen: StringBuilder und StringBuffer.

StringBuilder und StringBuffer

  • stellen veränderliche Strings zur Verfügung
  • sind effizienter als die Klasse String
  • sind, wie auch String, von CharSequence abgeleitet
StringBuilder sb = new StringBuilder();

sb.append("Hallo").append(' ').append("Welt");
System.out.println(sb.toString()); // -> Hallo Welt

Der Unterschied zwischen StringBuilder und StringBuffer liegt darin, dass StringBuffer threadsicher ist und StringBuilder nicht. Wenn es zu keinem konkurrierenden Zugriff (siehe unten) durch mehrere Threads kommt, ist die Klasse StringBuilder vorzuziehen, weil sie effizienter Arbeitet.

equals von StringBuilder und StringBuffer vergleicht nicht den Inhalt.
var sb1 = new StringBuilder("Hallo");
var sb2 = new StringBuilder("Hallo");
System.out.println(sb1.equals(sb2)); // -> false

Arrays sind Objekte

  • Deklaration einer Referenzvariablen auf ein Array
    char[] charArray;
    String[] stringArray;
  • Arrays werden mit new angelegt und haben eine fixe Größe
    charArray = new char[10];
    stringArray = new String[10];
  • Der Index eines Arrays beginnt bei 0
  • Arrays können nicht mehr in der Größe verändert werden
  • Array-Literale
    int[] intArray = { 1, 5, 7, 9 ,11, 22, 0 };
    String[] s = { "Alfred", "Franz", "Peter" };

Arrays sind Objekte und Array-Variablen sind ganz normale Referenzen auf ein Objekt. Daher wird durch das Deklarieren einer Array-Referenz das eigentliche Array nicht angelegt, sondern es wird nur eine Referenzvariable erzeugt. Damit verhalten sich Java-Arrays anders als Arrays z. B. in C wo bereits bei der Deklaration der Array-Variablen der Speicher für das Array beschafft wird. Die Array-Referenz hat den Wert null bzw. ist undefiniert, solange noch kein Array erzeugt und ihr zugewiesen wurde.

Neue Arrays werden wie Objekte mit new in erzeugt. Hierbei muss die Größe des Arrays angegeben werden, die dann nicht mehr verändert werden kann. Der einzige Weg ein Array zu vergrößern ist es, ein neues, größeres Array anzulegen und den Inhalt umzukopieren. Hierzu dient die für diesen Zweck hochoptimierte System.arraycopy() Methode.

Die Elemente eines Arrays von primitiven Typen können direkt Daten aufnehmen. Sie sind nach der Erzeugung mit dem Standard-Wert des jeweiligen Typs initialisiert. Bei Arrays von Objekttypen sind die Elemente null und die entsprechenden Objekte müssen erst noch angelegt werden.

Da mehrdimensionale Arrays in Wirklichkeit Arrays von Arrays sind, kann man nicht rechteckige mehrdimensionale Arrays konstruieren.

int twoDim [][] = new int [4][];
twoDim[0] = new int[2];
twoDim[1] = new int[4];
twoDim[2] = new int[6];
twoDim[3] = new int[8];

Hilfsklasse Arrays

Arrays sind zwar normale Objekte, sie haben aber keine Methoden, außer den von Object geerbten und dem read-only Attribut length. Diese Entscheidung lässt sich nachträglich auch nicht mehr ändern.

Es gibt allerdings die Klasse Arrays in java.util, welche eine ganze Reihe von hilfreichen Methoden für Arrays zur Verfügung stellt.

Hilfsmethoden für Arrays finden sich in der Klasse java.util.Arrays

  • sort: Sortieren von Arrays
  • equals: Vergleichen von Arrays
  • deepEquals: Vergleichen von Arrays (deep)
  • fill: Füllen eines Array mit Daten
  • copyOf: Kopieren von Teilen eines Arrays
  • binarySearch: Binäres suchen auf einem sortierten Array

Tatsächlich kann man zwei Arrays nur mit der equals-Methode von Arrays vergleichen und nicht mit der equals-Methode der Arrays selbst.

int[] a = { 1, 2, 3 };
int[] b = { 1, 2, 3 };
System.out.println(a.equals(b)); // -> false
System.out.println(Arrays.equals(a, b)); // -> true

Die deepEquals-Methode vergleicht auch Arrays, die wieder Arrays enthalten, indem Sie die inneren Arrays ebenfalls miteinander vergleicht.

Deklaration und Zuweisung

    // Deklaration
    char c;
    String str;
    int x, y;

    // Deklaration mit Initialisierung
    float z = 1.712f;
    double w = 3.1415;
    boolean wahrheit = true;
    String str1 = "Hi";
    var v = 99;

    // Zuweisung
    c = 'Z';
    str = "Hallo Welt";
    x = 60;
    y = 400;

Man kann mehrere Variablen in einer Zeile deklarieren, indem man die einzelnen Variablen durch Kommata trennt. Es ist auch möglich, eine Variable in der Deklaration direkt mit einem Wert zu versehen, d. h. Deklaration und Zuweisung zu kombinieren.

Es ist sogar möglich, die Deklaration von normalen und Array-Variablen in einer Zeile zu mischen. Dies ist aber sehr unübersichtlich und fehleranfällig und sollte daher auf keinen Fall gemacht werden. Der Java Coding-Standard verbietet daher auch syntaktisch vollkommen korrekte Konstrukte wie das folgende:

int x, y[], z[][];

Sie können sogar in eine solche Deklaration noch eine oder mehrere Zuweisungen einbauen und damit die Lesbarkeit komplett zerstören:

int i = 5, j[] = { 1,2,3 }, k[][] = new int[2][3];

Beachten Sie, dass String-Literale in doppelten Anführungszeichen stehen, wohingegen Zeichen-Literale in einfachen Anführungszeichen geschrieben werden. Während das Zeichen-Literal einen primitiven Typen definiert, nämlich char, erzeugt ein String-Literal ein echtes Java-Objekt vom Typ String.

Der Java-Compiler stellt sicher, dass einer deklarierten lokalen Variablen auf jeden Fall ein Wert zugewiesen wurde, bevor sie das erste Mal benutzt wird. D. h. der folgende Code kompiliert nicht:

int x;
x++;

Die Fehlermeldung ist in diesem Fall „The local variable x may not have been initialized!“

Arten von Operatoren

Drei Arten von Operatoren

  • Unäre Operatoren haben nur einen Operanden
    • Syntax: op Operand
    • Beispiel: Negation einer Zahl (a = -b)
  • Binäre Operatoren verknüpfen zwei Operanden
    • Syntax: Operand1 op Operand2
    • Beispiel: Multiplikation zweier Zahlen (a = b * c)
  • Ternäre Operatoren verknüpfen drei Operanden (selten)
    • Syntax: Operand1 op Operand2 op Operande3
    • Beispiel: Bedingter Ausdruck (x = a > b ? a : b)

Man kann Operatoren danach klassifizieren, wie viele Operanden sie haben. Am häufigsten sind Operatoren mit zwei Operanden (binäre Operatoren). Deutlich seltener sind solche mit nur einem Operanden (unäre Operatoren). Mit drei Operanden (ternärer Operator) existiert in Java nur ein einziger Operator, der sogenannte Fragezeichen-Operator.

Operatoren

Assoziativität Operator
R to L ++ – + - ~ ! (data type)
L to R * / %
L to R + -
L to R << >> >>>
L to R < > ⇐ >= instanceof
L to R ! ==
L to R &
L to R ^
L to R |
L to R &&
L to R ||
R to L ?:
R to L *= /= %= += -= <<= >>= >>>= &= ^= |= =

Da häufig mehrere Operatoren aufeinandertreffen, muss man festlegen, welche Operatoren zuerst ausgewertet werden. Diese Reihenfolge wird durch die Rangfolge der Operatoren eindeutig bestimmt, d. h. für jeden Operator ist festgelegt, vor welchen anderen Operatoren er ausgewertet wird.

Wenn ein Ausdruck mehrere Operatoren mit identischer Rangfolge enthält, wird die Richtung der Ausführung durch die Assoziativität bestimmt.

Zum Beispiel sind Multiplikations- und Modulo-Operator linksassoziativ. Daher sind die beiden folgenden Anweisungen austauschbar:

int result = 5 * 70 % 6;  // ergibt 2
int result = (5 * 70) % 6; // ergibt 2

Nicht aber

int result = 5 * (70 % 6); // ergibt 20

Rechtsassoziativ sind zum Beispiel die Vorzeichenoperatoren (+, -) oder die Zuweisung. Hier wird der Ausdruck von rechts nach links ausgewertet.

int summe;
int result = summe = 12; // summe ist 12, result ist 12

Copyright © 2025 Thomas Smits