Java

Aus m-wiki
Zur Navigation springen Zur Suche springen

Einführung

Tipps zu Büchern unten bei den Links

Kommentare

  • // Kommentar -> Einzeiliger Kommentar
  • /* Kommentar */ -> Mehrzeiliger Kommentar
  • /** Kommentar */ -> Mehrzeiliger Kommentar für Dokumentation

Dateiaufbau und Bibliotheken/Packages

  • Nur eine Klasse darf pro Datei öffentlich sein und diese muss den Namen der Datei haben: public class application {} -> application.java (Klassennamen werden normalerweise klein geschrieben, doch dies ist nur eine Konvention.)
  • Nach der Kompilierung mit javac wird der Dateiname ohne class übergeben -> java application
  • Der Einstiegspunkt in jedes Programm ist immer: public static void main(String[] args) (Der Parameter args kann frei gewählt werden, hat sich aber als Standard durchgesetzt).
  • Die Java-Bibliotheken enthalten Packages mit Klassen, Schnittstellen (Interfaces) und Ausnahmebehandlungen (Exceptions)
  • Externe Bibliotheken werden über die Pfadangabe eingebunden: java.util.Date d = new java.util.Date();
    • Um alle Klassen eines Paketes / einer Bibliothek einzufügen (und damit Schreibarbeit zu sparen) wird der import-Befehl verwendet: import java.util.*;
    • Will man nur eine einzelne Klasse eines Paketes importieren (Overhead beim kompilieren reduzieren): import java.util.Date;
    • Enthalten 2 Pakete den gleichen Klassennamen java.util.Date java.sql.Date, so muss trotz des Imports der volle Paketnamen angegeben werden, damit der Compiler weiss, welche Klasse gemeint ist.
  • Packages kann man selber erstellen auch wieder mit der Angabe des Pfades ab dem Java-Verzeichnis: package Paketname;
    • Die Angabe package muss immer zuoberst in der Datei stehen (noch vor dem Import), sonst gibt es einen Fehler
    • Wie bei normalen Klassen darf nur eine Klasse öffentlich sein, welche dann den Dateinamen gibt: \Tool\Network\MyClass.java

Syntax

Sonderzeichen

Zeichen Bedeutung
\b Backspace
\t horizontaler Tabulator
\n Zeilenumbruch
\f Seitenumbruch
\r Wagenrücklauf
" doppeltes Anführungszeichen
' einfaches Anführungszeichen
\ Backslash
\nnn oktaler Code
\uxxxx hexadezimaler Code

System.out.print("ProgrammersBase.NET \\u0021");

Datentypen

generische Typen

Typ Byte Wertebereich Standardwert
boolean 1 true, false false
byte 1 - 27 bis 27 0
short 2 - 215 bis 215 0
int 4 - 231 bis 231 0
long 8 - 263 bis 263 0
float 4 - 3.40282347*1038 bis 3.40282347*1038 0.0
double 8 - 1.79769313486231570*10308 bis 1.79769313486231570*10308 0.0
char 2 alle Unicode - Zeichen (Werden in einfache Hochkommas gesetzt: char x = 'd';) \n0000
java.math.BigDecimal ? grosse Zahlen ?

Diese Datentypen können mit sogenannten Wrapper-Klassen bei Bedarf auch als Objekte verwendet werden, damit man nur noch Objekte hat, wie etwa in Smalltalk, doch da sich dadurch nur die Geschwindigkeit negativ ändert, ist mir der Sinn schleierhaft und daher wird das Ganze hier auch nicht weiter behandelt.

Die Datentypen Byte, Integer, Long, Short, Character, Double und Float besitzen die Konstanten MAX_VALUE und MIN_VALUE, die den grössten und kleinsten zulässigen Wert des Datentyps bezeichnen. So kann man etwa Tests für Konvertierungen durchführen um zu schauen, ob es zu Werteverlusten kommt.
if(x > Float.MAX_VALUE ) { print("Konvertierungsfehler"); }

Um grosse Zahlen oder Zahlenblöcke besser lesbar zu machen, darf man ab Java 7 als Hilsmittel den Unterstrich einsetzen: 86_400_000 oder 0b01101001_01001101_11100101_01011110

Datentyp bei Strukturen angeben

Manchen Strukturen wie etwa die ArrayList übernehmen nur bestimmte Werte. Dieser Wertetyp muss der Struktur bei der Erzeugung übergeben werden. Da die Position vor dem Objekt den Typ des Objekts bestimmt, muss der Inhalt der Struktur anders übergeben werden. In Java geschieht dies mit der nachträglichen Angabe in spitzen Klammern:

ArrayList<Player> players = new ArrayList<Player>();

In diesem Fall nimmt die ArrayList nur Objekte vom Typ Player auf.

Datentypen umwandeln

Kleine DT werden (bei Bedarf) automatisch umgewandelt, während man beim umgekehrten Weg einen expliziten Cast anwenden muss, da dabei Werte verloren gehen können. Die Zuweisung geschieht mit: (gewünschter Datentyp) Variable

int i = 65;
char c = (char) i; // -> Nun Buchstabe A

Wichtig: Boolean kann nicht direkt umgewandelt werden in einen Integerwert. Hier muss man eine Abfrage treffen und dann den gewünschten Wert direkt an eine Variable vergeben:

boolean a = true;
int b = 0;
if(a) { b = 1; }

Da gewisse kleine Datentypen wie Byte und Short automatisch in einen Integer gewandelt werden, muss man einen expliziten Cast durchführen, wenn man Short haben möchte:

short a = 1;
short b = 2;
short c = (short) (a + b) // ohne expliziten cast gibt es einen Compiler-Fehler

Allerdings ist dies nur auf Embedded-Systemen sinnvoll, da die Platzersparnis auf "normalen" Rechnern den Aufwand nicht lohnt.
Bei der Konvertierung von Zahlen in einen String kann man die Methode toString() verwenden:

int a = 2;
System.out.println(a.toString());

enum (eigene Aufzählungen)

Wenn man eine bestimmte Anzahl von etwas unveränderlichem hat, wie etwa die Wochentage, so kann man diese mit enum zusammenfassen. So entsteht ein eigenes Objekt, welches fixe Werte enthält:

public enum Weekday {
  MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
} // end enum Weekday

Weekday day = Weekday.FRIDAY;

if (day == Weekday.MONDAY) { System.out.println("Ich hasse Montage!"); } // end if

switch (day) {
  case SATURDAY: // nicht Weekday.SATURDAY!
  case SUNDAY: System.out.println( "Wochenende. Party!" );
} // end switch

Da die Werte statische Variablen (also Konstanten) sind, sollte man sie zur Kennzeichnung entsprechend gross schreiben.
Da die Werte auch NULL sein können, sollte man vor deren Zugriff auf NULL testen.

public void setWeekday(Weekday day) {
  if (day == null) { throw new IllegalArgumentException( "null is not a valid argument!" ); }
  this.day = day;
} // end setWeekday

Variablen

Da der Typ fix ist, muss man bei Java im Gegensatz zu c(++) nichts weiter beachten. Bei der Deklaration kann/sollte man der Variablen auch einen Anfangswert mitgeben, doch dies ist prinzipiell nicht notwendig. Der Name kann theoretisch aus allen Unicode-Zeichen bestehen, doch ich empfehle, beim ASCII-Zeichensatz zu bleiben. Bei zusammengesetzten Namen sollte zur besseren Lesbarkeit jeder weitere Wortteil gross geschrieben werden (isBigHuman).
Die Definition geschieht daher nach folgendem Schema: Typ Name (= Wert);.
Die Zuweisungen kann man zusammenfassen, doch ich empfehle, dies nicht zu tun, denn dann kann man mit einem kleinen Kommentar hinter der Variable deren Zweck notieren.

Schreibweise

Damit man direkt im Code sieht, was man für Variablen vor sich hat, gibt es verschiedene Notationsweisen, welche dadurch auf ihre Deklaration hinweisen sollen. Die Verwendung einer einheitlichen Schreibweise wird empfohlen, doch ist diese natürlich frei wählbar und solange die Zeichenwahl erlaubt ist, wirft der Compiler auch keine Fehler, wenn man vom Typ abweicht.

Variablenart Beispiel Schreibweise
öffentliche Konstante ZEIT_DES_TAGES Grossschreibweise. Worttrennung mit Unterstrich.
private Konstante _DATUM Wortbeginn mit Unterstrich. Grossschreibweise. Worttrennung mit Unterstrich.
private (Klassen)Variablen _zeitDesTages Wortbeginn mit Unterstrich. camelCase Schreibweise mit Grossbuchstaben als Worttrennung, doch Start mit Kleinbuchstaben.
Parameter, lokale Variablen und auch Methoden tagesZeit camelCase Schreibweise mit Grossbuchstaben als Worttrennung, doch Start mit Kleinbuchstaben.
int alter;
int autos = 2; // Anzahl Autos pro Haushalt
boolean isHuman = true; // Handelt es sich um einen Menschen?
float einkommen, ausgaben;
double steuern = 0.0, zinsen = 1.5, anzahlPersonen;
double PI = 3.14;
private void setTime(Calendar startDate) {...}

lokale Variablen

Nur innerhalb des definierten Blocks (Schleife) oder der Methode gültig. Wenn nicht über den Rückgabewert zurückgegeben, wird ihr Inhalt beim Verlassen verworfen.

konstante Variablen (Konstanten)

Kann den Wert nach der Definition nicht mehr ändern. Etwa für Pi oder ähnliches verwendet, doch kein Muss.

final Typ NAME = Wert;
final float PI = 3.14;

Will man die Variable nur einmal pro Klasse brauchen, kann man sie auch auslagern und statisch verwenden.

statische Variablen

Gleicher Gültigkeitsbereich wie lokale Variablen, doch sie behalten ihren Wert auch über das Ende der Methode hinaus und können beim nochmaligen betreten weiter verwendet werden (Aufrufe zählen (Instanzen usw.)). Das heisst, die Variable bleibt bis zum Programmende oder dem expliziten "zerstören" aller Klasseninstanzen persistent im Speicher.

class Test {  
  static public int i = 2; 
} // end class Test

public class MyClass {
  public static void main(String[] args) {
    System.out.print(Test.i);      
  }
} // end class MyClass

Wie man sieht, muss die statische Variable in eine externe Klasse ausgelagert werden, damit das Ganze auch funktionieren kann. Damit man die Variable auch sieht, muss sie öffentlich (public) definiert sein. Sonst kann man eine Methode erstellen und den Wert entsprechend zurückgeben lassen.

Klassenvariablen

Alle benutzerdefinierten Variablen, welche jedoch nicht direkt erzeugt werden oder an eine Instanz gebunden sind. -> Genauer beschreiben

Instanzvariablen

Alle von einer Klasse instantiierten und an ein Objekt gebundenen Variablen -> Noch etwas genauer schreiben

Arrays

Unterscheiden sich optisch durch die Klammern, doch es sind andere Typen, da Arrays aus Klassen gebaut werden.

Arrays definieren und Werte zuweisen

int[][] array2 = new int[10][20];
array2[3][17] = 127;
int[] prim  = {2, 3, 5, 7, 11};

Man könnte die Klammern bei der Definition auch hinten beim Namen schreiben, doch da die Übergabe eines Arrays an eine Methode auch so geschieht, machen wir es einheitlich. Wie man sieht, sind auch mehrdimensionale Arrays möglich. Man kann auch eigene Arrays definieren (es sind ja Klassen) und diese dann jeweils als Elemente behandeln und darauf zugreifen.

Zugriff auf Elemente im Array

wert = array[0][3];

Man sieht, dass die Zählung bei Null beginnt. Hier hat man beim zweidimensionalen Array das 4. Element der ersten "Reihe" gewählt.

arraycopy() Arrays kopieren

Da Arrays Klassen sind, kann man diese nicht einfach mit array2 = array1; kopieren. Sonst zeigen die Zeiger nämlich weiterhin auf array1. Man verwendet daher folgende Methode:

public class MyClass {
  public static void main(String[] args) {
    int[] array1 = {0,1,2,3,4};
    int[]  copy = {0,0,0,0,0};

    System.arraycopy(array1,2,copy,1,3); // -> Beginne im array array1 an der Position 3 (Index beginnt bei 0) und kopiere dann ins array copy. 
                                         //    Beginne dort an Position 2 und schreibe 3 kopierte Werte rein.

    for(int i=0; i<copy.length; i++) {
      System.out.println(copy[i]);
    } // end for
  } // end main
} // end MyClass

Es gibt auch die Methode clone(). Dies ist aber mit Vorsicht zu geniessen. Hier wird zwar bei 1-dimensionalen Arrays eine Kopie erstellt, doch bei mehrdimensionalen Feldern zeigen die Unterfelder immer noch auf die Original-Objekte. Dies ist in den meisten Fällen nicht wünschenswert und daher sollte dies nicht verwendet werden.

binarySearch() Wert in Arrays suchen

Die binäre Suche ist nur dann korrekt, wenn das Array vorher sortiert wurde. Sonst kann es fehlerhafte Resultate geben.

import java.util.*;

public class MyClass {
  public static void main(String[] args) {
    int[] array = {1,3,5,8};

    System.out.println(Arrays.binarySearch(array,5));
    System.out.println(Arrays.binarySearch(array,6));
  } // end main
} // end MyClass

Wird nach einem nicht vorhandenen Eintrag gesucht, so wird als Ergebnis der mögliche Einfügungsindex zurückgegeben und nicht die gefundene Indexposition.

equals() Arrays vergleichen

import java.util.*;

public class MyClass {
  public static void main(String[] args) {
    int[] array1 = {1,2,3,4};
    int[] array2 = {1,2,3,4};
    int[] array3 = {4,3,2,1};
    int[] array4 = {1,2    };

    System.out.println(Arrays.equals(array1,array1));
    System.out.println(Arrays.equals(array1,array2)); 
    System.out.println(Arrays.equals(array2,array3));
    System.out.println(Arrays.equals(array1,array4));
  } // end main
} // end MyClass

Hier wird zuerst zwei mal true geliefert und dann zwei mal false, da die Arrays auch mit den Elementen übereinstimmen müssen und nicht nur in der Anzahl. Dies bedeutet, dass Arrays vor dem Vergleichen sortiert sein müssen, falls dies gewünscht ist.

fill() Array mit bestimmtem Wert füllen

Hier gibt es zwei Möglichkeiten. Alle Elemente:

Arrays.fill(array,2); // füllt das Array mit dem Wert 2

Bestimmter Bereich:

Arrays.fill(array,2,4,1); // füllt das Array von Index 2 bis Index 4 mit dem Wert 1

length() Länge des Arrays ermitteln

x = array.length;

Wird vor allem in Schleifen zum befüllen verwendet:

for(int i=0;i<array.length;i++) {
  System.out.print(array[i]);
} // end for

Damit die Berechnung nicht jedes Mal durchgeführt wird, sollte die Anweisung vor der Schlaufe an eine Hilfsvariable übergeben werden, damit man in der Schlaufe nur noch darauf prüfen kann.

sort() Array sortieren

Hier gibt es wieder zwei Möglichkeiten. Alle Elemente:

Arrays.sort(array); // sortiert das komplette Array

Bestimmter Bereich:

Arrays.sort(array,2,4); // sortiert das Array von Index 2 bis Index 4 aufsteigend

Arrays an eine Methode übergeben

public static void print(int[] a) {
    for(int i=0;i<a.length;i++) {
      System.out.print(a[i]);
    } // end for
} // end print

Wie man sieht, wird über den Typ angezeigt, dass es ein Array ist.
Da Arrays als Referenz übergeben werden, modifiziert man hier im Gegensatz zu Variablen automatisch das Original.

Array zurückgeben

Hier wird einfach der Rückgabewert void angepasst auf das Array und schon ist es bereit:

public static int[] init() {
  int[] tmp = new int[10];
  for(int i=0;i<tmp.length;i++) {
    tmp[i] = i;
  } // end for
  return tmp;
} // end init

Array durchlaufen und Werte zurückgeben

Ist eine Variante der for-Schleife. In php als for each umgesetzt.

static double avg( double[] array ) {
  if ( array == null || array.length == 0 ) { throw new IllegalArgumentException( "Array null oder leer" ); }
  double sum = 0;
  for ( double n : array ) { sum += n; } // Die Werte werden der Hilfsvariable n übergeben
  return sum / array.length;
} // end avg()

Listen

ArrayList (dynamisches Array)

Da Arrays in Java starr sind, ist die Verwendung bei einer dynamischen Anzahl mühsam und deren aktive Grössenanpassung rechenaufwändig. In diesem Fall ist eine Liste java.util.ArrayList die bessere Wahl, da diese auf eine entsprechende Grössenanpassung ausgerichtet ist.

add() (Element hinzufügen)

import java.util.ArrayList
int x = 10;
ArrayList<int> liste = new ArrayList<int>();
liste.add(x);

Fügt ein Objekt vom Typ int hinzu. Der Typ kann auch ein beliebiges Objekt sein.

size() (Listengrösse)

get() (Element liefern)

Dient dazu, ein Element an einer bestimmten Poistion in der Liste zurückzugeben.

Liste durchlaufen und alle Elemente ausgeben

Will man einfach alle Objekte auflisten, dann ist eine for-Schleife einfacher als eine einzelne Ausgabe mit get().

for (Player player : players) {
  System.out.println(player.name);
} // end for

Strings

Definition

  • Wurden als Klasse implementiert.
  • Wichtigste Einschränkung: Aus Performancegründen gilt die Klasse als konstant und erzeugte Strings können daher nicht mehr angepasst werden.
  • Strings sind Unicode und brauchen daher keine Zeichensätze.

Die Erzeugung kann wie eine Variable oder über den Konstruktor der Klasse erfolgen. Man kann aber auch ein Array oder einen vorhandenen String übergeben (Kopie).

public class MyClass {
  public static void main(String[] args) {
    char[] array = {'S','t','r','i','n','g',' ','3'};

    String string1 = "String 1";
    String string2 = new String("String 2");  
    String string3 = new String(array,0,7); 
    String string4 = new String(string1);
  } // end main
} // end MyClass

+ oder concat() Strings verketten

Durch das verknüpfen mit + kann man Strings verknüpfen. Man kann dabei auch Zahlen mitgeben:

public class MyClass {
  public static void main(String[] args) {
    String string1 = "String 1";
    String string2 = new String("String 2");   
    String string3 = string1 + string2 + "Test " + "5";
  } // end main
} // end MyClass

Hier muss man aufpassen, dass man nicht ungewollt falsch zusammenzählt, da reine Zahlen je nach Reihenfolge oder Klammernangabe ausgerechnet werden, bevor sie mit dem Rest verkettet werden. Daher möglichst keine Berechnungen in der Verkettung ("x" + (3 + 4) + "y" -> x7y) und Buchstaben/Zahlen nur in doppelten Hochkommas, da einfache Hochkommas als Char gewertet werden und dann diese miteinander verrechnet werden und so keine Verkettung geschieht('A'(65) + 'a'(97) -> ó(162)).

Auf Codebasis schneller und effizienter wäre aber das verwenden von concat:

    String string1 = "String 1";
    String string2 = new String("String 2");   
    String string3 = string1.concat(string2);

charAt() Zeichen an einer gewissen Position ermitteln

Das Zeichen an der gewünschten Position wird ausgegeben. Das erste Zeichen ist wie beim Array an Position 0.

public class MyClass {
  public static void main(String[] args) {
    String string1 = "String 1";
    System.out.println(string1.charAt(3)); // ergibt 'i'
  } // end main
} // end MyClass

compareTo() contains, equals(), startsWith() Strings auf Inhalte prüfen

Je nach Bedarf kann man folgende Vergleiche machen:

Befehl Bedeutung Beispiel
compareTo Führt einen lexikalischen Vergleich durch. Dabei werden die Zeichen verglichen sowie die Stringlänge. Endet ein String oder ist ein Zeichen kleiner beziehungsweise größer, so wird ein numerischer Ergebniswert geliefert. Bei Gleichheit gibt es 0.
Die Methode ist wichtig bei einer alphabetischen Sortierung. Dabei muss man bedenken, dass landesspezifische Sortierungen nicht berücksichtigt werden, wie etwa deutsche Umlaute. Dazu muss man Collator-Klassen nutzen, welche dafür ausgelegt sind.
String string1 =  "CPP.ProgrammersBase.NET";
String string2 = "JAVA.ProgrammersBase.NET";
    
int i = string1.compareTo(string2);
 
if(i<0) { 
  System.out.println("Der 1. String ist lexikalisch kleiner"); // kleiner oder Zeichen früher im Alphabet 
} // end if
else if(i>0) { 
  System.out.println("Der 1. String ist lexikalisch grösser"); // grösser oder Zeichen später im Alphabet 
} // end else if
else { 
  System.out.println("Die Strings sind lexikalisch gleich"); // identisch 
} // end else
compareToIgnoreCase Wie oben, aber es wird nur die Länge/Buchstaben verglichen, da die Gross-/Kleinschreibung ignoriert wird.
contains Testst, ob der angegebene Teilstring im übergebenen String enthalten ist (true oder false)
Es wird entsprechend auf Gross- Kleinschreibung geachtet.
System.out.println("Hallo zusammen, ich besitze Viagra!".contains("Viagra") ); // true
endsWith Der String muss auf die mitgegebene Sequenz enden
String string = "JAVA.ProgrammersBase.NET";
String search = "NET";

System.out.println(string.endsWith(search)); // -> true
equals Exakter Zeichen- und Längenvergleich.
Wichtig ist dabei, dass es zu einer NullPointerException kommen kann, wenn der vordere der beiden Strings null ist. Wenn einer der beiden Strings definiert ist, so sollte dieser in den vorderen Teil genommen werden, da in diesem Fall keine Ausnahme generiert wird, wenn der zweite Teil null ist.
String string1 = "TEST";
String string2 = "Test";
String string3;

System.out.println(string1.equals(string2)); // -> false
System.out.println(string1.equalsIgnoreCase(string2)); // true

System.out.println(string1.equals(string3)); // false ohne Ausnahmefehler
System.out.println(string3.equals(string1)); // false mit Abbruch durch Ausnahmefehler
equalsIgnoreCase Gross- Kleinschreibung wird ignoriert
regionMatches Prüft, ob zwei Sequenzen im String übereinstimmen. Bei Bedarf auch mit Gross- Kleinschreibung. Dies, wenn man als ersten Parameter ein true mitgibt.
String string1 = "JAVA.ProgrammersBase.NET";
String search =  "CPP.ProgrammersBase.NET";
   
System.out.println(string1.regionMatches(16, search, 15, 4));
System.out.println(string1.regionMatches(true, 16, search, 15, 4));

Sucht im string1 ab Position 16 und schaut, ob im übergebenen String (search) an Position 15 die nächsten 4 Zeichen übereinstimmen

startsWith Der String muss ab der gewünschten Stelle mit der mitgegebenen Sequenz übereinstimmen
String string = "JAVA.ProgrammersBase.NET";
String search = "NET";

System.out.println(string.startsWith(search)); // -> false
System.out.println(string.startsWith(search, 21)); // -> true (Suche beginnt wie immer mit 0)

indexOf() Position eines Zeichens in String finden

Diese Funktionen liefern die Position des ersten oder letzten Vorkommens des gesuchten Zeichens.

String string = "Such mich";
String search = "mi";

System.out.println(string.indexOf(search)); // liefert 5

Man kann auch eine definierte Startposition mitgeben und so mittels einer Schleife alle Vorkommnisse zählen, indem man die gefundene Position als neue Startposition mitgibt.
Will man das letzte Vorkommen finden, verwendet man lastIndexOf.
Wird der Wert nicht gefunden, so liefert die Funktion -1 zurück.

length() String Länge ermitteln

Wird wie bei Arrays gemacht:

public class MyClass {
  public static void main(String[] args) {
    String string1 = "String 1";
    System.out.println(string1.length());
  } // end main
} // end MyClass

parseXXX() String in Zahl konvertieren

Ist das Gegenteil von valueOf und steht für jeden Typ gesondert zur Verfügung, so dass es parseInt(), parseDouble(), usw. gibt.
Da man bei Benutzereingaben mit Fehlern rechnen muss, muss man immer mit NumberFormatException Fehlern rechnen und diese entsprechend abfangen. Dazu gehören bei int auch Leerzeichen, da diese im Gegensatz zu etwa double nicht von der Bibliothek abgefangen werden (ja das ist inkonsequent). Entsprechend muss die Abfrage als try-catch formuliert werden.

String s = " 1234    "; // Hier müsste natürlich ein .trim() angehängt werden, doch der String könnte auch andere Zeichen enthalten
try {
  int i = Integer.parseInt(s);
} // end try
catch(NumberFormatException error) {
  System.out.printf("String konnte nicht in einen Integer konvertiert werden! %s", error.toString());
} // end catch

replace() Zeichen ersetzen

Da Strings konstant sind, wird der entsprechende String kopiert und das angepasste Resultat zurückgeliefert. Es gibt verschiedene Varianten, je nachdem, ob man nur einzelne Zeichen oder Zeichenbereiche ersetzen will und auch, ob man mit einem regulären Ausdruck arbeiten möchte.

String string = "Hallo";
System.out.println(string.replace('a','e')); // -> Hello

String s = "Schnecken  erschrecken, wenn  Schnecken an Schnecken schlecken,    weil zum Schrecken  vieler Schnecken Schnecken   nicht schmecken.";
System.out.println( s.replace("Schnecke", "Katze") );

System.out.println( s.replaceAll( " +", " " ) );   // -> Ersetzt mehrmaliges Vorkommen von Leerzeichen durch ein einmaliges an allen Fundorten.
System.out.println( s.replaceFirst( " +", " " ) ); // -> Ersetzt mehrmaliges Vorkommen von Leerzeichen durch ein einmaliges, doch bricht die Suche nach dem ersten Finden ab.

split() String aufteilen

Der übergebene String wird in ein Array abgefüllt. Als Trennzeichen werden reguläre Ausdrücke übergeben.

String test = "Das ist ein Test.";
String[] segs = test.split("\s"); // Das Trennzeichen sind Leerräume
System.out.println( Arrays.toString(segs) ); // Das, ist, ein, Test. -> Wichtig ist hier, dass Satzzeichen verbleiben, da sie ja keine Leerräume beinhalten
String string = "Hört es euch an, denn das ist mein Gedudel!";
int nrOfWords = string.split( "(\\s|\\p{Punct})+" ).length; // Hier wird als Option Leerraum oder Satzzeichen übergeben.
System.out.println( nrOfWords );       // 9

substring() Teilstring extrahieren

Will man mehrere Zeichen extrahieren, verwendet man substring. Die Parameter werden immer vom Anfang aus gerechnet, wobei das Zeichen an der Endeposition nicht mehr mit ausgegeben wird.

public class MyClass {
  public static void main(String[] args) {
    String string1 = "String";
    System.out.println(string1.substring(1,3)); // Teil in der Mitte ausschneiden ergibt 'tr'
    System.out.println(string1.substring(2)); // Ende ausschneiden ergibt 'ring'. Da bis zum Ende übernommen wird, muss nur die Startposition angegeben werden.
  } // end main
} // end MyClass

toLowerCase(), toUpperCase() Umwandlung in Gross- oder Kleinbuchstaben

Mit toLowerCase oder toUpperCase wird der komplette String angepasst.

String string = "klein";
System.out.println(string.toUpperCase()); // Liefert KLEIN

Je nach lokalen Begebenheiten kann dies zu einer inkorrekten Verarbeitung führen. Hier sollte dann die locale berücksichtigt werden:
System.out.println("TITANIK".toLowerCase(new Locale("tr"))); // tıtanık -> im türkischen ohne Punkte auf dem i
Ohne Angabe wird einfach die Einstellung des Betriebssystems übernommen.

trim() Leerzeichen eliminieren

Wenn man nicht sicher ist, ob der String noch (unsichtbare) Leerzeichen (vorher oder nachher) enthält, kann man diese vor einer weitern Verarbeitung automatisch entfernen:
s.trim();

Will man nachher noch testen, ob der String nun leer ist, kann dies folgendermassen kombiniert werden:
boolean isBlank = s.trim().isEmpty();

valueOf() Wert in String konvertieren

Auch wenn man primitive Datentypen explizit mit String s1 = String.valueOf(10); erzeugen kann, findet die Typumwandlung auch ohne explizite Typumwandlung statt, so dass man dies bei der Initialisierung auch weglassen kann.String s1 = "10";
Muss man allerdings im Code eine Variable in einen Sting konvertieren und nicht einfach nur als Parameter bei print mitgeben, dann braucht man die Angabe, da ein + den Wert einfach hinten an den String anhängt.

String s = "10";
int a = 10;
s.valueOf(a);
System.out.println(s);

Allerdings kann diese Konvertierung auch bei beliebigen Objekten gemacht werden. String r = String.valueOf( new java.awt.Point() ); // java.awt.Point[x=0,y=0]. Intern wird dabei einfach die Methode toString() aufgerufen.

Stringbuffer (variable Strings)

Strings sind ja per Definition konstant. Mit einem Stringbuffer kann man aber variable Strings erzeugen. Das bedeutet, dass man diese danach manipulieren kann. Das Ganze geht auch über die vorhandenen Methoden mit Strings, doch die Stringbuffer sind komfortabler. Um das Ganze zu realisieren, wird eine bestimmte Grösse verwendet, welche mit der capacity Methode abgefragt werden kann. Diese Grösse ist aber nur auf Embedded-Systemen mit wenig Speicher relevant. Man kann die neue Buffergrösse manuell mit ensureCapacity(Grösse) setzen.
Es gibt auch noch die StringBuilder Klasse, welche ein wenig schneller ist, dafür bei Threads nicht synchron. Aus diesem Grund lohnt sich deren Einsatz nur in Spezialfällen. Die Verwendung ist aber äquivalent zu Stringbuffer.

Befehl Bedeutung Beispiel
append Zeichen am Ende anfügen
StringBuffer string = new StringBuffer("Test");

string.append("zusatz");
System.out.println(string); // -> Testzusatz
contentEquals Buffer mit String vergleichen
StringBuffer string = new StringBuffer("Test");
String s = "Test";

System.out.println(s.contentEquals(string) ); // true
delete Löscht eine Buchstabensequenz
StringBuffer string = new StringBuffer("Teststring");
    
string.delete(4, 6); // An Position 4 beginnen und 6 Zeichen entfernen
System.out.println(string); // -> Test
deleteCharAt entfernt das Zeichen an der mitgegebenen Position
StringBuffer string = new StringBuffer("Testi");
    
string.deleteCharAt(4);
System.out.println(string); // -> Test
getChars Kopiert einen Bereich in ein Array.
char[] a = new char[20];
StringBuffer b = new StringBuffer("Teststring");
b.getChars(4, 9, a, 0); // Kopiert von Position 4 bis Position 9 ins Array a ab Position 0.
    
System.out.println(a); // -> string
insert Hier kann die Einfügeposition selber bestimmt werden
StringBuffer string =  new StringBuffer("Der String");
    
string.insert(3, "neue ");
System.out.println(string); // -> Der neue String
replace Ersetzt eine bestimmte Sequenz mit der Angegebenen.
StringBuffer string = new StringBuffer("Test");
    
string.replace(3, 1, "aurus"); // Entfernt 1 Zeichen an Position 3 und fügt die mitgegebene Sequenz ein
System.out.println(string); // -> Tesaurus
reverse Dreht den String um
StringBuffer string = new StringBuffer("Test");
    
string.reverse();
System.out.println(string); // -> tseT
setCharAt Ersetzt ein Zeichen an der gegebenen Position
StringBuffer string = new StringBuffer("Tast");
    
string.setCharAt(1, "e");
System.out.println(string); // -> Test
substring Kopiert eine angegebene Sequenz
StringBuffer string = new StringBuffer("Teststring");
 
System.out.println(string.substring(5)); // -> string -> Ohne zweiten Parameter, wird bis zum Ende kopiert, sonst nur bis zur zweiten Position

Bedingungen und Schlaufen

if then else

if(Bedingung 1) {
  // Anweisungen
} // end if
else if (Bedingung 2) {
  // Anweisungen
} // end else if
else {
  // Anweisungen
} // end else

Die else if Anweisung kann beliebig oft erfolgen. Hier ist aber ein switch meist besser. Das schlussendliche else wird nur aufgerufen, wenn keine Vorherbedingung erfüllt wurde.

switch

switch(Parameter) {
  case Wert1:  // Anweisungen
  break;
  case Wert2:  // Anweisungen
  break;
  default:     // Anweisungen
} // end switch

Der Parameter muss konstant sein. Ab Java 7 kann man neben Integer auch Aufzählungen (enum) und Strings/Char verwenden.
Es sind keine Wertebereiche (7-15) möglich. Alle Anwesiungen nach dem case werden ausgeführt bis zum break. Auf diese Weise kann man "primitiv" Bereiche festlegen: case 1: case 2: case 3: print"1-3"; break;
Default wird nur ausgeführt, wenn kein anderer Wert zutraf.

while

while(Bedingung) {
  // Anweisungen
} // end while
  • Solange die Bedingung stimmt, wird durchlaufen
  • Stimmt die Bedingung schon am Anfang nicht, wird die Schleife übersprungen -> Will man dies nicht, muss man do-while verwenden
  • Hat man kein oder ein falsches Abbruchkriterium, ergibt sich eine Endlosschleife

for

for(Typ Variable = Wert; Bedingung; Update) {
  // Anweisungen
} // end for
  • for(int i=0; i<10; i++)

Kann auch durch eine while-Schleife gebaut werden, ist aber einfacher zu lesen.

Um ein Element zu durchlaufen (Array, Liste usw.) kann eine Variante von for verwendet werden, welche unter PHP als for each bekannt ist.

for (Player player : players) {
  System.out.println(player.name);
} // end for

Es wird die Objektliste players durchlaufen und deren Inhalt pro Durchgang der Hilfsvariable player vom Typ Player übergeben. Diese Variable kann dann auf die Attribute des entsprechenden Objekts zurückgreifen. Hier in diesem Beispiel auf den Namen und diesen ausgeben.

Break und Continue und Sprungmarken

  • break; Abbruch der Schleife
  • continue; Wiederholung der Schleife

continue springt sofort nach der Anweisung wieder zum Kopf und beginnt mit einem neuen Durchlauf. So kann man bei ungültigen Werten gleich den nächsten Wert einlesen.
Beim break kann man eine undefinierte Schleife beim Ende der Daten definiert verlassen.
Marken werden verwendet, wenn die obigen Befehle nicht eindeutig sind. Etwa bei geschachtelten Schleifen. In diesem Fall wird die Sprungmarke an der Stelle gesetzt, an die man beim Befehl springen möchte.

test1:
while (true) {
  test2:
  while (true) {
    if (a < b) { break test1; }
    else { break test2; }
  } // end while
} // end while

Operatoren und Operationen

Bei mehreren Operationen in einer Anweisung, werden die Anweisungen entsprechend ihrer Priorität abgearbeitet. Diese Reihenfolge entspricht den mathematischen Regeln. Bei Unsicherheit sollten Klammern eingesetzt werden, da diese dafür sorgen, dass die Reihenfolge entsprechend eingehalten wird.

Mathematik

  • + Führt eine Addition durch.
  • - Führt eine Subtraktion durch.
  • * Führt eine Multiplikation durch.
  • / Führt eine Division durch.
  • % Führt eine Division durch, wobei nur der Restwert geliefert wird.
  • == Gleichheit (Achtung, Klassen müssen entsprechend ihrer Methoden verglichen werden und dazu gehören auch Arrays)
  • != Ungleichheit (Siehe Hinweis bei Gleichheit)

inc und dec

  • ++ Erhöht den Wert um 1
  • -- Erniedrigt um 1

Je nach Stellung wird zuerst erhöht und dann berechnet oder umgekehrt. Am einfachsten ist der Prefix, doch je nach Verwendung ist auch der Postfix notwendig:

  • i = j++; // Postfix -> zuerst bekommt i den Wert von j und danach wird j um 1 erhöht.
  • i = ++j; // Prefix -> zuerst wird j um 1 erhöht und danach i zugewiesen.

Logische Vergleiche

  • ! NICHT // Liefert immer das Gegenteil
  • && UND mit "Short Circuit Evaluation"
  • & UND ohne "Short Circuit Evaluation"
  • || ODER mit "Short Circuit Evaluation"
  • | ODER ohne "Short Circuit Evaluation"
  • ^ ODER (exklusiv) // Liefert nur dann TRUE, wenn die Werte unterschiedlich sind (TRUE+FALSE oder FALSE+TRUE)
Short Circuit Evaluation

Verwendet eine Operation dieses Verfahren, so wird der weiter rechts stehende Ausdruck nur dann ausgewertet, wenn er für das Gesamtergebnis noch von Bedeutung ist. Dadurch kann man viele Vergleiche abkürzen.

Bitvergleiche

Auswertung wie oben. Die Vergleiche werden auf die jeweiligen Bits angewendet und nicht auf den gesamten Ausdruck

  • ~ Komplement // Binäres Komplement bilden: 110 -> 001
  • & UND // 110 + 101 -> 100
  • | ODER // 110 + 101 -> 111
  • ^ ODER (exklusiv) 110 + 101 -> 011

Bits schieben

Die Bits der übergebenen Variable werden um die gewünschte Anzahl verschoben und die füllenden Positionen werden mit 0 gefüllt. Je nach Wahl kann das Vorzeichen belassen werden (pos- / neg.).

  • << \ Linksverschiebung
  • >> \ Rechtsverschiebung mit Vorzeichen
  • >>> \ Rechtsverschiebung ohne Vorzeichen

Spezialabfragen

  • ?: wird als Kurzform eines if then else verwendet. Variable?Truewert:Falsewert; // Lieber sauber mit if then else arbeiten.
  • instanceof ermittelt, ob der übergebene Wert eine Instanz der angeforderten Klasse oder einer Subklasse davon ist
  • . (Punkt) wird verwendet, um auf Subklassen und Methoden zuzugreifen (Siehe Kapitel Klassen)
  • + kann auch Strings miteinander verknüpfen

Objektorientierung

Theorie

Das Ziel ist, alle Implementierungsdetails zu kapseln und nur über definierte Schnittstellen (Methoden) mit dem Objekt (Klasse) zu kommunizieren. Wird eine Klasse von einer anderen abgeleitet (Vererbung), so nennt man dies entweder Superklasse -> Subklasse oder Elternklasse -> Kindklasse. Je weiter nach "unten", umso spezifischer wird die Implementierung, während nach oben generalisiert (verallgemeinert) wird. Eine Klasse stellt den Bauplan dar, während schliesslich ein Objekt durch eine Instantiierung aus diesem Bauplan erstellt wird.
Bevor man sich eine neue Klasse erstellt, sollte man sich die folgenden Fragen stellen (und beantworten), da sich daraus automatisch die Referenz der zu bildenden Klasse ergibt:

  • Welche Fähigkeiten hat das Objekt? -> Methoden
  • Welche Eigenschaften hat das Objekt? -> Variablen
  • Welche Initialisierungen müssen vorgenommen werden? -> Konstruktor(en)
  • Welche Daten sollen dem Benutzer zugänglich sein? -> Variablen und Methoden intern oder extern zugänglich definieren
  • (Ist das Objekt Teil einer Vererbungshierarchie?) -> Nur notwendig, wenn mehrere Klassen mit Subklassen geplant werden (Aufteilung)
  • (Ist das Objekt von anderen Klassen abhängig?) -> Bibliotheken einbinden

Schlagwörter

  • Von Aggregation spricht man, wenn ein Objekt andere aufnehmen kann (Behälter), ohne von ihnen abhängig zu sein. -> is a (a ist b zugeordnet)
  • Bei der Komposition stellt man das Objekt aus verschiedenen Objekten zusammen und ist somit von den Subobjekten abhängig. -> part of (a ist Teil von b)

Vererbung

Die Vererbung geschieht mit dem Schlüsselwort extends: Kindklasse extends Elternklasse.
In Java kann immer nur von einer Klasse abgeleitet werden, wobei man selber bestimmen kann, welche Eigenschaften man übernimmt und welche man anpasst. Möchte man hingegen Eigenschaften und Methoden von anderen Klassen integrieren, so verwendet man eine Schnittstelle (Interface). Das sind (leere) Methoden einer anderen Klasse, welche dann benutzerspezifisch implementiert werden. Alle Klassen werden in Java von Object abgeleitet.

Klassen

Klasse definieren

Klassen beginnen immer mit Grossbuchstaben und werden in CamelCase geschrieben. Dies unterscheidet sie von den Methoden.

[public] class Test1 {
  // Anweisungen
} // end Test1

Es darf pro Datei nur eine öffentliche Klasse geben und diese muss dem Namen der Datei entsprechen. Hier wäre dies Test1.class. Alle anderen Klassen in der Datei sind automatisch intern, falls noch weitere Klassen definiert werden. Will man eine weitere öffentliche Klasse, so hat man diese in einer eigenen Datei zu speichern.

Klasse verwenden

Die oberhalb definierte Klasse wird folgendermassen eingebunden:

Klasse Anwendung1;
Anwendung1 = new Test1();

oder in Kurzform

Klasse Object = new Klasseninstanz();
Test1 object = new Test1();

In einem konkreten Beispiel:

class MyClass {
  // Anweisungen
} // end MyClass
      
public class MyTest {
  public static void main(String[] args) {
    MyClass object = new MyClass(); // Diesen Befehl könnte man wie oben vorgestellt auch aufteilen ins Objekt erstellen und Objekt instantiieren.
  } // end main
} // end MyTest

innere und anonyme Klassen

Werden verwendet, um eingebundene äussere Klassen kurzfristig für eigene Zwecke zu überschreiben. Sind nur innerhalb der Definition sicht- und verwendbar.

Superklassen und Subklassen (Klassen ableiten)

Die Superklasse ist die Elternklasse oder Basisklasse, von der abgeleitet wird. Die neue Klasse ist die Kind- oder Subklasse:

class MySuperClass{
}

class MySubClass extends MySuperClass{
}

Die Subklasse erbt alle Variablen und Methoden der Superklasse mit Ausnahme der Objekte, welche als final gekennzeichnet wurden. Diese können in der neuen Klasse nicht mehr verändert werden. Zusätzlich werden Konstruktoren und Destruktoren nicht vererbt, da diese klassenspezifisch sind. Genauso statische Methoden, da diese an die jeweilige Klasse gebunden sind und private Variablen, da die der Klasse "gehören". Im Gegensatz zu c++ kann man nur immer von einer Klasse ableiten. Alles andere wird über die Schnittstellen "importiert".

Konstruktoren in abgeleiteten Klassen

Im Konstruktor wird bei Java immer implizit der jeweilige Elternkonstruktor aufgerufen super();. Entsprechend muss man bei einer abgeleiteten Klasse schauen, ob die Elternklasse einen "leeren" Konstruktor besitzt. Ist dies nicht der Fall, muss man einen manuellen Aufruf mit den entsprechenden Parametern durchführen, damit es keinen Kompilierfehler gibt. Im Beispiel wird etwa der Konstruktor mit der Variablen i aufgerufen, da es keinen "leeren" Konstruktor in MySuperClass gibt:

class MySuperClass {
  public MySuperClass(int i) {
    this.i = i;
  } // end Konstruktor

  public int i;
} // end MySuperClass

class MySubClass extends MySuperClass {
  public MySubClass(int i,String s) {
    super(i);
    this.s = s;
  } // end Konstruktor

  public String s;
} // end MySubClass

public class MyClass {
  public static void main(String[] args) {
    MySubClass object = new MySubClass(0,"ProgrammersBase.NET");

    System.out.println(object.i);
    System.out.println(object.s);
  } // end main
} // end MyClass
abgeleitete Methoden

Damit abgeleitete Methoden das Verhalten der Basisklasse überschreiben, muss die Originalmethode komplett überschrieben werden (gleiche Parameter und Anzahl). Wählt man nun eine Methode, so wird immer die "tiefste" Stufe genommen, in der eine übereinstimmende Methode gefunden wird. Die direkte Auswahl mittels Punkt ist nur bei der direkten Elternklasse erlaubt und zwar mittels super.methode(). Ohne wird einfach hierarchisch nach oben weiter gesucht, bis es eine entsprechende Methode gibt. Falls keine gefunden wird, kommt es zu einem Fehler.

instanceof (Instanz einer Klasse)

Diese spezielle Methode wird verwendet, um zu prüfen, ob ein Objekt einer bestimmten (abgeleiteten) Klasse oder Schnittstelle entspricht.

System.out.print(object instanceof MyClass);

Damit kann man sicherstellen, dass das gewählte Objekt bestimmten Voraussetzungen entspricht, die man im anschliessenden Code voraussetzt (Methoden, Variablen usw.).

Singleton (Klassen mit genau einer Instanz)

Diese speziellen Klassen werden erzeugt, wenn man sicherstellen will, dass es genau eine Instanz davon gibt. Dies kann ein Fehlerkanal, ein Log-Objekt oder sonst etwas sein.

public final class Logger {
  private static Logger _logger;
  private logger() { } // privater Konstruktor, damit kein Objekt von aussen gebildet werden kann
  public static synchronized _logger getInstance() { // Methode um eine einmalige Instanz zu erzeugen
    if (_logger == null) { _logger = new logger(); } // end if
    return _logger;
  } // end getInstance()

  public void log(String s) { System.out.println(s); } // Methode zum Testen
} // end class Logger

public class LoggerUser {
  public static void main(String[] args) {
    Logger.getInstance().log("Log mich!"); // Aufruf des Loggers, der erstellt wird, wenn er noch nicht existiert
  } // end main
} // end class LoggerUser

Schnittstellen (Interfaces)

Eine Schnittstelle definiert das was und ist daher abstrakt. Das wie wird schlussendlich in der Klasse programmiert, in der sie eingebunden wird.
Da Schnittstellen schlussendlich Methoden sind, werden sie auch camelCase mit klienm Buchstaben am Anfang geschrieben.
Die Methoden und Variablen müssen öffentlich sein und auch initialisiert. Sie gelten als konstant.

interface myInterface {
  public int i = 0;
  public void print();
} // end myInterface

Hier ein Beispiel:

interface myInterface {
  public String SPRACHE = "ProgrammersBase.NET"; // Konstanten sind erlaubt
  public void print(); // wie abstrakte Methoden werden die Schnittstellen mit Strichpunkt angegeben.
} // end myInterface

class MyClass implements myInterface {
  @Override 
  public void print() { // konkrete Implementierung mit dem Override, um dies zu kennzeichnen.
    System.out.print(SPRACHE);
  } // end print
} // end MyClass

public class MyTest {
  public static void main(String[] args) {
    MyClass object = new MyClass();
    object.print(); // Einsatz des Interfaces in der Klasse.
  } // end main
} // end MyTest

Einmal implementierte Schnittstellen werden auch an Subklassen weitergegeben. Das heisst, Subklassen können diese verwenden, ohne diese noch einmal implementieren zu müssen. Bei Bedarf darf man diese aber einfach überschreiben und somit neu definieren. Laut Namenskonvention enden Schnittstellennamen auf -ble wie runnable oder implementable.
Schnittstellen sollten nach einer Implementierung nicht mehr geändert, sondern wie bei einer Versionierung mit einer neuen Nummer versehen werden, damit die alte Implementierung nicht plötzlich "defekt" ist.

mehrere Schnittstellen einbinden

Man kann auch mehrere Schnittstellen implementieren. Diese werden jeweils mit Komma getrennt angegeben.

public class Heft implements buyable, comparable<Heft> {
  private double _price;
  public Heft(String name, double price) {
    super(name);
    this._price = _price;
  } // end constructor

  @Override
  public double buyable() {
    return _price;
  } // end interface buyable

  @Override
  public int comparable(Heft another) {
    return Double.compare(this.price(), another.price());
  } // end interface comparable
} // end class Heft

Damit soll eine Auslagerung ermöglicht werden, welche dann eine gleiche Implementierung in allen Klassen ermöglicht, weil der Rumpf fix vorgegeben wird. Im Gegensatz zu polymorpher Vererbung muss die zu implementierende Schnittstelle nicht absolut angegeben werden. Könnten Methoden von zwei verschiedenen Klassen geerbt werden, hätte man sonst eine Mehrdeutigkeit, weil der Compiler bei gleichem Namen nicht wüsste, welche Methode nun genommen werden soll. Dadurch dass in der Shcnittstellendefinition keine Logik drin ist, kann man bei gleichem Namen einfach die Schnittstelle implementieren und es interessiert nicht, von welcher Vorlage sie genommen wurde.
Mit instanceof kann geprüft werden, ob ein Objekt einer bestimmten Schnittstelle entspricht, bevor man ihn einsetzt.

Schnittstellen vererben

Genau wie eine Klasse kann man auch eine Schnittstelle mit extends vererben.

interface x extends y
vererbte statische Variablen

Da bei abgeleiteten Schnittstellen auch die Variablen aus der Oberschnittstelle übernommen werden, muss man schauen, dass man diese korrekt referenziert, wenn man von mehreren Schnittstellen abgeleitet hat, welche beide die gleiche Variable zur Verfügung stellen. Man darf dann nicht einfach über die Unterklasse auf die Variable verweisen, da der Compiler sonst nicht weiss, welche gemeint ist und er den Fehler The field x is ambiguous wirft.

interface farbe; // Markierungsschnittstelle

interface schwarzweiss extends farbe {
  int BLACK = 1;
  int WHITE = 2;
} // end interface schwarzweiss 

interface farben extends farbe
{
  int BLACK  = 1;
  int WHITE  = 2;
  int GREEN  = 10;
  int PURPLE = 11;
} // end interface farben

interface allColors extends schwarzweiss, farben
{
  int REGENBOGEN = 100;
} // end interface allColors 

public class Colors implements allColors {
  public static void main(String[] args) {
    System.out.println(schwarzweiss.BLACK); // 1
    System.out.println(farben.BLACK);       // 1
    System.out.println(allColors.GREEN);    // 10
    System.out.println(allColors.BLACK );   // Fehler The field AllColors.BLACK is ambiguous
  } // end main
} // end class Colors

Konstruktoren

Konstruktoren werden erst aufgerufen, nachdem die internen Variablen initialisiert sind. Dies ermöglicht es, die Variablendefinition im Code erst anschliessend zu machen, ohne dass es zu einem Fehler kommt. Je nach Vererbungsstatus müssen im Konstruktor noch Variablen der vererbenden Superklassen initialisiert werden.
Wenn Variablen über eine Methode gesetzt werden kann, dann muss sie nicht über den Konsturktor verarbeitet werden. Soll die Variable hingegen nach der Initialisierung nicht mehr von Aussen verändert werden, dann ist es eine gute Idee, diese im Konstruktor mit dem gewünschten Wert zu initialisieren.

Aufbau

Dies sind spezielle Methoden, welche beim erstellen eines Objekts aufgerufen werden, um Werte zu initialisieren. Der Konstruktor ist optional und trägt immer den Namen der Klasse. Entsprechend wird er im Gegensatz zu normalen Methoden gross geschrieben:

class MyClass {
  public MyClass() {
    i = 10;
  } // end Konstruktor
  private int i;
} // end Klasse

Parameterübergabe

Da der Konstruktor eine normale Methode ist, können an ihn entsprechende Parameter übergeben werden:

class MyClass {
  public MyClass(int a, int b) {
    i = a * b;
  } // end Konstruktor
  private int i;
} // end MyClass

public class MyTest {
  public static void main(String[] args) {
    MyClass object = new MyClass(2, 5);
  } // end main
} // end MyTest

Der Konstruktor kann auch überladen werden, wobei sich entweder die Parameteranzahl oder die Datentypen unterscheiden müssen.

Statische Konstruktoren

Dienen zur Initialisierung der Klassenvariablen. Können keine Parameter bekommen, da der Aufruf zur Laufzeit erfolgt.

class MyClass {
  public static   int i;
  public static short s;
  static {
    i = 10;
    s = 20;
  } // end Konstruktor
} // end MyClass

copy-Konstruktor

Bei diesem Konstruktor wird ein Objekt "der eigenen Sorte" übergeben. Somit können die Werte des Originals übernommen werden.

public class Player {
  public String name;
  public String item;

  public Player() { } // end leerer Konstruktor 

  public Player(Player player) {
    name = player.name;
    item = player.item;
  } // end player Konstruktor 
} // end Player

this-Konstruktor

Genauso wie man mit this.x auf eigene Variablen zugreifen kann, ist somit auch ein Zugriff auf den eigenen Konstruktor möglich. Der Sinn dabei ist, dass man bei mehreren Konstruktoren schlussendlich immer nur einen mit Logik aufruft und bei einer Änderung entsprechend auch nur diesen anpassen muss.

public class Player {
  public String name;
  public String item;

  public Player() {
    this( "", "" ); // ruft den wirklichen Konstrukor mit leeren Werten auf
  } // end leerer Konstruktor

  public Player(Player player) {
    this( player.name, player.item ); // ruft den wirklichen Konstrukor mit einem bestehenden Objekt auf
  } // end copy Konstruktor

  public Player(String name, String item) {
    this.name = name;
    this.item = item;
  } // end wirklicher Konstruktor
} // end class Player

Wichtig ist, dass man bei dieser Verwendung nur auf statische Variablen in der Klasse zugreifen darf, da die anderen Variablen noch nicht definiert sind.

Exemplar-Initialisierer

Der folgende Code kann dazu dienen, Konstruktoren zu vereinfachen, da die Routine automatisch in jeden Konstruktor an den Anfang kopiert wird und so in jedem Konstruktor ausgeführt wird. Somit kann der this-Konstruktor entschlackt werden. Nachteilig kann sein, dass man den Code übersieht, da er nicht mehr im Konstruktor drin ist und dass der Code nur mit new() durchlaufen wird. Falls man Objekte "neu" initialisieren will, muss man dies über eine eigene "initialize" Methode bewerkstelligen. Aus diesem Grund muss man den Einsatz abwägen, doch es ist gut zu wissen, was der Code macht, wenn man ein "fremdes" Programm liest. Man kann genauso wie die static-Blöcke auch mehrere { } Blöcke erstellen, welche alle vor dem Konstruktor durchlaufen werden. Aus Übersichtsgründen empfiehlt es sich aber, darauf zu verzichten.

public class JavaInitializers {
  static { // wird nur einmal beim generieren der Klasse durchlaufen
   System.out.println( "Statischer Initialisierer");
  } // end static

  {
    System.out.println( "Exemplarinitialisierer" ); // wird mit jeder Instanz durchlaufen
  }

  JavaInitializers() {
   System.out.println( "Konstruktor" ); // der "Exemplarinitialisierer" wird vom Compiler vor diese Zeile kopiert
  } // end Konstruktor
} // end class JavaInitializers

super() und this() im Konstruktor

Da beide Anweisungen die erste im Konstruktor sein wollen, verliert jemand. In diesem Fall muss man auf this() verzichten und die Initialisierung der Variablen in eine gemeinsame private Methode auslagern.

public ColoredLabel(String label, Color color) {
  super(label);
  initialize(color);
} // end Konstruktor

private void initialize(Color color) {
  setForeground(color);
} // end initialize()

Modifikatoren/Modifizierer (public, static usw.)

Java kennt folgende Modifikatoren für Klassen, Methoden, Variablen usw. Diese regeln die Sichtbarkeit und die Lebensdauer.

Modifikator Sichtbarkeit Lebensdauer Bemerkungen
public (UML: +) Von überall her (auch externe Klassen).
Wird eine Klasse als privat markiert, so kann man auch nicht mehr auf interne öffentliche Variablen zugreifen.
-
private class X { public int i; } // end X
private class Y { 
  X x = new x();
  x.i = 5; // Kompilierfehler
} // end Y

Aus Gründen der Kapselung sollten möglichst keine Variablen public sein und nur über Methoden angesprochen werden.

protected (UML: #) eigene Klasse und davon abgeleitete Klassen und alle anderen Klassen in einem Paket. - Die Eigenschaft protected wird bei abgeleiteten Klassen übernommen.
private (UML: -) nur in der eigenen Klasse - Möglichst explizit für alle Variablen und interne Methoden verwenden, denn aus Grund der Datenkapselung sollte möglichst viel als private deklariert werden.
package scoped (UML: ~) Innerhalb des Pakets sichtbar - Ist theoretisch -> wird intern angewendet, wenn man keinen Modifikator verwendet.
static - Solange ein Objekt der Klasse existiert. Das Objekt (Variable / Methode) wird über die Klasse (klasse.methode) und nicht über die Instanz (instanz.methode) angesprochen. Dies bedeutet, dass solche Objekte auch nicht abgeleitet werden können, da sie klassenbezogen sind. Dies bedeutet, dass man entsprechend nicht zuerst ein Objekt der Klasse erzeugen muss, um auf seine Methoden zugreifen zu können. Die main-Methode ist etwa so definiert.
final - - Fixiert den Wert. Variablen können nicht mehr verändert werden und Klassen/Methoden nicht abgeleitet. Damit erreicht man, dass etwa Konstanten nicht fälschlicherweise einen neuen Wert erhalten oder dass definierte Methoden sich plötzlich anders verhalten.
abstract - - Die Methode / Klasse muss in einer abgeleiteten Klasse konkret implementiert werden. In der als abstract definierten Methode oder Klasse werden die Methoden nicht auscodiert. Es werden nur Anweisungen geschrieben, mit welchen Parametern usw. die Methoden schlussendlich arbeiten sollen.
Dies wird etwa bei Schnittstellen verwendet, da diese Methoden dann in anderen Klassen verwendet werden können.

Von einer abstrakten Klasse können entsprechend keine Objekte gebildet werden.

public abstract class GameObject {
  public String name;
  public abstract boolean use(GameObject object); // abstrakte Methode mit Strichpunkt
} // end GameObject

public class Door extends GameObject { // konkrete Implementierung der abstrakten Klasse
  @Override 
  public boolean use(GameObject object) { // konkrete Implementierung der abstrakten Methode
    return false;
  } // end use
} // end Door
native - - Sind in einer anderen Sprache wie etwa c++ geschrieben.
synchronized - - Wird fürs Multithreading verwendet.
transient - - Diese Objekte werden bei Serialisierung und Deserialisierung ignoriert -> weiter erklären. Normalerweise nicht notwendig.
volatile - - Stellt Datenintegrität bei Multithreading sicher. Sonst nicht verwendet.

Generics

Mit Generics kann man Typen genauer definieren. Wie etwa bei einem Array dem man auch sagt welcher Art die Einträge sind. Bei einer Liste List kann man daher mit einem Generic angeben, welcher Art diese Liste ist. Der Generic wird in spitzen Klammern nach der Definition gesetzt:
List <Point> liste

Annotationen

Ähnlich wie Generics werden Annotationen eingesetzt, um etwas genauer zu spezifizieren. Sie wirken aber nicht auf Objekte, sondern auf Klassen oder Methoden und sind wie static oder public Modifikatoren. Java kennt ab V7 vier Stück (es gibt noch mehr im Paket javax.annotation, doch die sind meist nicht von Relevanz). Die Annotationen können je nach Typ auch Werte besitzen, welche die Hinweise spezifizieren. Ob und welche Werte sie besitzen wird in der Spezifikation festgelegt. Der Wert wird, falls verwendet, hintenan gestellt in Klammern: @Annotation (Wert)
Damit die Klasssenbezeichnung nicht zu lang wird und die Annotation besser sichtbar ist, kann man sie auch in eine eigene Zeile schreiben.

Annotationstyp Wirkung Bemerkung
@Override Die annotierte Methode überschreibt eine Methode aus der Oberklasse oder implementiert eine Methode einer Schnittstelle. Keine Wertangabe erlaubt.
@Override 
public void add(int x) {
...
} // end add
@Deprecated Das markierte Element ist veraltet und sollte nicht mehr verwendet werden. Keine Wertangabe erlaubt.
@Deprecated
public void nop(int x) {
...
} // end nop
@SuppressWarnings Unterdrückt bestimmte Compiler-Warnungen. Somit kann man Warnungen bei älterem Code verhindern oder wenn man bei einer Definition keine Generics einsetzt. Am umfassendsten ist ("all") vor der Klassendefinition. Will man bestimmte Werte kombinieren, so verwendet man geschweifte Klammern: ({"rawtypes", "unchecked"})
@SuppressWarnings({"rawtypes", "unchecked"})
public double old(int x) {
...
} // end old
@SafeVarargs Besondere Markierung für Methoden mit variabler Argumentzahl und generischem Argumenttyp. Unterdrückt entsprechende Compiler-Warnungen, da dieser nicht weiss, ob man durch die variable Anzahl den Heap sprengt.
// die Methode erwartet mindestens ein Argument und zusätzlich dann beliebige, welche in der for-Schleife ausgelesen werden
@SafeVarargs
static double calculateSum(Buyable price1, Buyable... prices) {
  double result = price1.price();
  for(Buyable price : prices) {
    result += price.price(); // die Objekte enthalten eine Methode price() welche ihren Preis zurückgibt
  } // end for
  return result;
} // end calculateSum

In der Praxis verwendet man normalerweise nur @Override häufig. @Deprecated und @SuppressWarnings sollte man vermeiden, denn sie weisen entsprechend auf alten oder fehlerhaften Code hin.

Zugriff auf Variablen

öffentliche Variablen

Der Zugriff erfolgt wie bei Methoden über den Punktoperator: Klasse.variable

class MyClass {
  public int i = 10;
} // end MyClass

public class MyTest {
  public static void main(String[] args) {
    MyClass object = new MyClass();
    System.out.print(object.i);
  } // end main
} // end MyTest

Man sieht, dass die Variable über die Instanz der Klasse angesprochen wird und somit Teil der Instanz ist. Würde man eine zweite Instanz erstellen und den Wert der einen Instanz ändern, so wäre die Variable der zweiten Instanz nicht betroffen.

statische Variablen

class MyClass {
  public static int i = 10;
} // end MyClass

public class MyTest {
  public static void main(String[] args) {
    System.out.print(MyClass.i);
  } // end main
} // end MyTest

Hier wird keine Instanz gewählt, da man schliesslich unabhängig der Instanzen ist, sondern man greift auf die Klasse selber zu. Das heisst, wenn man diese Variable anpasst, ist diese für alle Instanzen gültig, da es auf die Klasse wirkt (Instanzenzähler). Siehe statische Variablen.

this

Hierbei handelt es sich um einen Verweis auf die eigene Instanz. Möchte man nämlich eine interne Variable ansprechen, so muss das Objekt wissen, dass jetzt die eigene Instanz gemeint ist. Will man etwa aus Unterblöcken auf gleichnamige äussere Variablen der Klasse zugreifen, so kann man dies mit this:

class MyClass {
  public MyClass(int i,String s) {
    this.i = i;
    this.s = s;
  } // end Konstruktor

  private    int i;
  private String s;
} // end MyClass

Die Zeile mit dem this.i verweist auf das äussere i, welches sonst vom internen i unsichtbar gemacht worden wäre.

getter (get()) und setter (set(x))

In Java sollte man Variablen nicht direkt modifizieren, sondern dies immer definiert über Methoden erledigen. Methoden, welche nur den Wert einer Variablen zurückliefern oder diese nur setzen, werden entsprechend getter und setter genannt. Bei get() bekommt man als Rückgabe einfach den Inhalt der Variable zurück, während bei set kein Rückgabewert verwendet wird.
Ich selber würde aber auch bei einem set(x) min. einen Boolean zurückgeben, damit man weiss, dass die Zuweisung erfolgreich war. Falls nicht (Wert nicht im erlaubten Bereich), könnte man dann entsprechend reagieren. Sonst muss man nicht nur die Fehlerbehandlung sondern auch die Ausgabe in der Methode erledigen. Sonst kann man einfach den Fehlerstatus setzen und den Fehlercode in einer speziellen Fehlervariable des Objektes ablegen, welche man anschliessend zur Ausgabe an eine andere Methode übergeben kann.

public String getName() { return name; } // end getName()

public void setName(String name) { // Standard
  if ( name != null && !name.trim().isEmpty()) { this.name = name; } // end if
} // end setName()

public boolean setName(String name) { // Mit Statusrückgabe
  if ( name != null && !name.trim().isEmpty()) { 
    this.name = name; 
    return true;
  } // end if
  this.errorText = "setName Aufruf schlug fehl"; // der Fehler sollte natürlich genau eingegrenzt werden (null, leer, zu lang usw.)
  this.errorCode = 94; // die Fehlernummer sollte intern natürlich aufgelöst werden können
  return false;
} // end setName()

Interne Variablen sind natürlich über die entsprechenden Modifikatoren zu kapseln.

Methoden (Funktionen)

In Java werden Subroutinen als Methoden bezeichnet im Gegensatz zu c, wo von Funktionen gesprochen wird.
Wie beim Konstruktor gesehen, kann man einer Methode Werte übergeben und auch wieder daraus auslesen. Auf Methoden wird wie auf Variablen mit dem Punkt-Operator zugegriffen Klasse.methode()
Die Schreibweise von Methoden ist zwar wie bei Konstruktoren camelCase, doch mit kleinem Buchstaben zu Beginn im Gegensatz zur Klasse.

class MyClass {
  public int myMethod(int a) {
    System.out.print("Intern: "+a);
    a++;
    return a;
  } // end myMethod
} // end MyClass

public class MyTest {
  public static void main(String[] args) {
    int b = 5;
    MyClass object = new MyClass();
    b = object.myMethod(b);
    System.out.print("Neu:"+b);
  } // end main
} // end MyTest
  • Damit man Werte übergeben kann, muss der Übergabeparameter übereinstimmen -> sonst evtl. Cast notwendig
  • Ist der Rückgabewert void, so gibt die Methode nichts zurück. Sonst zeigt man an, was zurückgegeben wird
  • Die Übergabe geschieht immer als Wert. Ausnahmen bei Arrays und Klassen, da dann ein Zeiger als Referenz übergeben wird
  • Als Rückgabewert kann auch eine Klasse definiert werden, so dass beliebige Rückgabemengen möglich sind.
  • Methoden können durch unterschiedliche Parameteranzahl oder Datentypenunterschied überladen werden. methode(int) methode(int, int) methode(int, string)

Möchte man Methoden aus der Java-Bibliothek verwenden, so muss die entsprechende Bibliothek vorher mit import eingebunden werden. Manchmal ist nicht ganz klar, in welchem Paket die gewünschte Methode ist, da manche Methoden zwar gleich heissen, doch eine andere Funktion besitzen. In diesem Fall muss man über die API-Dokumentation herausfinden, welches nun die korrekte Methode ist oder halt im Internet nachfragen.

Methoden überladen

Im Gegensatz zu php kann man in Java nicht einfach fixe Werte an nicht übergebene Parameter zuweisen. static double rechner( double eins = 5, double zwei = 10) ist nicht erlaubt. Möchte man dies erreichen, so muss man die Methode von einer anderen Methode aus aufrufen und dort den Fixwert definieren.
Diese mehrfache Definition der gleichen Methode nennt man "überladen".

double rechner (eins, zwei) {
  return eins * zwei;
}

double rechner () {
  int eins = 5;
  int zwei = 10;
  return rechner(eins, zwei);
}

Nun kann man rechner aufrufen und erhält einen Standardwert. Dies geht entsprechend in diversen Varianten, bedingt gegenüber anderen Sprachen halt einfach mehr Code.

Methoden verketten

Muss man auf ein Objekt mehrere (komatible) Methoden nacheinander anwenden, so kann man diese auch mit dem Punkt-Operator verketten.

java.awt.Point  p = new java.awt.Point(2,7);
System.out.println( p.toString().length() );

Variable Anzahl Parameter übergeben

Wenn bei der Definition nicht klar ist, wie viele Parameter man der Methode übergibt, dann gibt es die Möglichkeit, diese als variables Feld (Array) zu definieren. Dies geschieht mit typ... array, wobei man diesen Parameter auch anders nennen könnte, doch da es sich schlussendlich um ein Array handelt, wird es so gleich deutlich. Dies kann nun einfach mit einer for (int a:array) ausgelesen werden.

static int sum(int... array) {
  for (int a:array) {
    a += a;
  } // end for
  return a;
} // end sum

Nützliche Methoden

Die folgenden Methoden sind alphabetisch nach dem Methodennamen sortiert. Es handelt sich um eine Auflistung von nützlichen allgemeinen Methoden.

equals() Objekte vergleichen

Im Gegensatz zu primitiven Typen kann man Objekte nicht einfach mit "=" vergleichen, da damit nur geprüft würde, ob sie auf das gleiche Objekt referenzieren. Hier muss man stattdessen die Methode equals() verwenden, die etwa schon bei den Arrays gezeigt wurde. Wenn man eigene Objekte erstellt, muss man die Methode natürlich selber implementieren, da man dann festlegen muss, welche Variablen für den Vergleich beigezogen werden sollen.
Wenn man dies nicht macht, dann erbt man die Methode von Object, welche einfach nur die Referenz testet.

exit() Wert ans aufrufende Programm zurückgeben

wenn das Programm einfach beendet wird, so gibt Java keinen Rückgabewert zurück. Entsprechend kann nicht geprüft werden, ob die Verarbeitung fehlerfrei abgeschlossen wurde. Aus diesem Grund gibt es die statische MethodeSystem.exit(Statuscode);, welche den Code zurückgibt. Im Erfolgsfall sollte man immer 0 zurückgeben, da dies als "erfolgreich" interpretiert wird.

...length() Länge zurückgeben

Wird meist auf Strings oder Streams angewandt.

String s = "Test";
System.out.println( s.length() );
long size = new java.io.File( "file.txt" ).length(); // Dateilänge in Byte speichern
printXXX() Werte (auf der Konsole) ausgeben

Befindet sich in der Klasse System.out

  • print(); -> Ausgabe des enthaltenen Arguments ohne Zeilenumbruch.
  • println(); -> Ausgabe des enthaltenen Arguments mit anschliessendem Zeilenumbruch.
  • printf(); -> Ausgabe des enthaltenen Arguments mit Formatierungen.
    %n ergibt einen Zeilenumbruch, %d steht für eine ganze Zahl, %f ist für eine Fliesskommazahl und %s steht für eine Zeichenkette.%b steht für Boolean, %t für Datum und Zeit, während %e für die wissenschaftliche Schreibweise einer Zahl steht. Dazu wird mit %% ein Prozentzeichen ausgegeben, während man mit %x hexadezimale Zahlen ausgibt und %c explizit Unicode-Zeichen verarbeitet.

printf("%d Prozent ist nicht ganz %s.%n", 99, "Hundert" );

  • Der Fehlerkanal wird mit System.err.printXXX() angesprochen. Dies ist ein anderes Objekt und ermöglicht es, die Fehlerausgabe umzuleiten (etwa in eine Logdatei).
  • Bei diesen Methoden kann man Strings und "einfache Datentypen" kombinieren mit dem + Zeichen: print("Ausgabe: " + wert);

Für die Formatierung kann man die Stellenanzahl (auch mit führenden Nullen %05d) und bei Fliesskommazahlen auch die Nachkommastellen angeben %010.2f.
Beim Parameter %t muss entsprechend angegeben werden, welche Werte ausgegeben werden sollen. Die mögliche Liste ist sehr lang (%tA -> Wochentag oder %tc komplettes Datum mit Zeit in der Form %ta %tb %td %tT %tZ %tY).

random() Zufallszahl generieren

Die Methode der Klasse Math liefert als Ergebnis einen Zufallswert zwischen 0 und 1. Je nach gewünschtem Wert muss der Bereich entsprechend berechnet werden.
x = Min + (int)(Math.random() * ((Max - Min) + 1))
int r = (int)(Math.random() * 5 + 1); Liefert einen Integer-Wert zwischen 1 und 5

Scanner() Werte (von der Konsole) einlesen

Der Methode aus der Klasse java.util muss noch gesagt werden, von wo man Daten einlesen will und auch der zu erwartende Datentyp muss mitgegeben werden, da Java keine impliziten Casts vornimmt. Entsprechend kann bei fehlerhafter Eingabe ein InputMismatchException geworfen werden, den man entsprechend abfangen muss.
Man kann für die Eingabe auch die lokale Spracheinstellung explizit setzen, auch wenn Java sonst einfach standardmässig die vom Betriebssystem nimmt useLocale(Locale.German(Switzerland));.

Konsole
  • String: String name = new java.util.Scanner(System.in).nextLine();
  • Int: int age = new java.util.Scanner(System.in).nextInt();
  • Double: double value = new java.util.Scanner(System.in).nextDouble();
Datei
import java.io.*;
import java.util.Scanner;
public class ReadAllLines {
  public static void main( String[] args ) throws FileNotFoundException { // Da es einen Fehler geben kann, sollte entsprechend das catch implementiert werden.
    Scanner scanner = new Scanner(new File("Datei.txt"));
    while(scanner.hasNextLine()) {
      System.out.println(scanner.nextLine());
    } // end while
    scanner.close(); // Speicher freigeben
  } // end main
} // end class
toString() In einen String umwandeln

Jedes Objekt kann in einen String umgewandelt werden. Ob die entsprechende Ausgabe dann aber sinnvoll ist, ist nicht unbedingt definiert.

java.awt.Point  p = new java.awt.Point(5,10);
System.out.println( p.toString() );

Fehlerbehandlung (try-catch(-finally))

Java ermöglicht es, dass man bei Fehlern nicht einfach einen Rückgabecode in Form einer Zahl zurückgibt, welche mehrdeutig sein kann, sondern im Fehlerfall wird in einen speziellen Block verzweigt, welcher den Fehler behandelt. Damit dies funktioniert, muss jeder Codeblock, in dem ein Fehler auftreten kann, in einen bestimmten Block eingeschlossen werden, damit Java entsprechend auf den Fehler reagieren kann.

Definition der Fehlerklassen

Wenn ein Fehler im Programmablauf auftritt, so ruft Java spezielle Klassen zur Fehlerbehandlung auf:

Fehlerart Beschreibung
Error Diese Klasse repräsentiert die schwersten Fehler im System. Auch wenn sie recht selten auftreten, so ist fortan ein störungsfreier Programmverlauf kaum noch zu garantieren. Subklassen dieser Kategorie sollten nicht selber ausgelöst werden. In der Regel wird bei diesen Fehlern das Programm vollständig beendet. Diese Fehlerklasse wird normalerweise vom System aufgerufen und nicht programmiert.
Exception Sie ist die wichtigste Hauptkategorie, auf die man sich als Programmierer konzentrieren sollte. Prinzipiell ist auch diese noch einmal zweigeteilt. Und zwar in alle Ausnahmen die von RuntimeException abgeleitet sind oder nicht. Alle benutzerdefinierten Ausnahmen sollten sich in diese Sparte einordnen.
Runtime Exception Hierbei handelt es sich um Laufzeitfehler, die während des Programmverlaufs auftreten. Die Kategorie vereint alle Fehler, die auf Grund eines Programmierfehlers auftreten können. Dazu gehören unter anderem die folgenden Fälle.
Fehlerart Beschreibung Beispiel
ArithmeticException Division durch 0 println(15/0);
ArrayIndexOutOfBoundsException Zugriff über die Arraygrenze hinaus (new int[0])[1]. Eine ArrayIndexOutOfBoundsException ist neben StringIndexOutOfBoundsException eine Unterklasse von IndexOutOfBoundsException.
ClassCastException Nicht mögliche Typkonvertierung So löst (java.util.Stack) new java.util.Vector() eine ClassCastException mit der Meldung »java.util.Vector cannot be cast to java.util.Stack« aus.
EmptyStackException Zugriff auf einen leeren Stack Der Stapelspeicher ist leer. new java.util.Stack().pop() provoziert den Fehler.
IllegalArgumentException Eine häufig verwendete Ausnahme, mit der Methoden falsche Argumente melden.
Damit man nicht alle Parameter selber prüfen muss, gibt es dafür öffentliche Bibliotheken zum einbinden: org.apache.commons.lang.Validate (http://commons.apache.org/lang/) oder com.google.common.base.Preconditions (http://code.google.com/p/guava-libraries/)
Integer.parseInt("tutego") löst eine NumberFormatException, eine Unterklasse von IllegalArgumentException, aus.
IllegalMonitorStateException Ein Thread möchte warten, ist aber nicht am Laufen. new String().wait();
IllegalStateException Eine Operation soll auf ein Objekt angewendet werden, doch im jetzigen Zustand ist das Objekt dazu nicht in der Lage. -
NullPointerException Zugriff auf einen leeren Zeiger. Soll nicht vom Programmierer ausgelöst werden, da es sich ja um einen Programmierfehler handelt. Ausnahmen gibt es nur dann, wenn ein null als Argument an eine Methode übergeben wurde, welche dies eigentlich nicht erlaubt. ((String) null).length()
UnsupportedOperationException Operation ist nicht gestattet. Dies kann auch auftreten, wenn eine Schnittstelle oder eine abstrakte Methode implementiert werden soll, man dies aber nicht gemacht hat. Wenn nun diese Methode aufgerufen sird, sollte man nicht einfach "nichts tun" durch einen leeren Methodenrumpf, sondern stattdessen diese Fehlermeldung zurückgeben. java.util.Arrays.asList(args).add("chris")
-

Alle nicht von RuntimeException abgeleiteten Klassen repräsentieren Fehler, die sich durch ungünstige Umstände und nicht etwa durch fehlerhaften Code ergeben. Beispiele dafür sind folgende Gegebenheiten.

  • Fehler beim Lesen einer Datei
  • ungenügend Systemressourcen
  • Fehlfunktion externer Dienste

Je nach Umständen kann das Programm bis auf die aufgerufene Funktion je nach Benutzerentscheid normal weiter laufen.

Ablauf einer Fehlerbehandlung

Der Ablauf ist folgendermassen:

  1. Aufruf des try-Blocks
  2. Fehlerbehandlung, falls notwendig durch catch
    Wird der Fehler nicht abgefangen, wird er schlussendlich durch die generelle Fehlerbehandlung abgefangen, doch dann sieht man meist nur, dass ein Fehler auftrat, doch man kann dann evtl. nicht mehr genau sagen, was passiert ist.
  3. Aufruf des finally-Blocks

try-catch

Im try-Block wird versucht, eine bestimmte Anweisung auszuführen. Schlägt diese fehl, so wird der catch Block aufgerufen. Hier zeigt sich, dass der Programmierer selbst herausfinden muss, welche Fehler wann auftreten können und wie man diese am besten behandelt.

public class MyClass {
  public static void main(String[] args) {
    try {
      Thread.sleep(2000);
    } // end try
    catch(InterruptedException e) {
      System.out.print("Fehler aufgetreten!");
    } // end catch
  } // end main
} // end myClass

Der catch-Block erhält als Parameter die Instanz der auslösenden Fehlerklasse. Dies kann bei der Auswertung hilfreich sein. Bei der Bearbeitung muss man wissen, dass die Klasse von throwable abgeleitet wird und somit schon vordefinierte Methoden enthält, mit welchen man arbeiten kann.

try wiederholt ausführen

Während man in anderen Programmiersprachen nach einem Abbruch wieder direkt zur fehlerauslösenden Zeile zurückkehren kann, muss dies in Java (etwas umständlich) mit einer (while)-Schleife gelöst werden.

while(true) {
  try {
    String s = javax.swing.JOptionPane.showInputDialog("Bitte Zahl eingeben");
    number = Integer.parseInt(s);
    break; // wird erst erreicht, wenn eine gültige Zahl übergeben wurde
  } // end try
  catch (NumberFormatException x) {
    System.err.println("Das war keine Zahl!");
  } // end catch
} // end while

Der Einsatz einer Endlosschleife ist natürlich nur dann sinnvoll, wenn der Benutzer auch etwas dagegen unternehmen kann. Entsprechend muss in einem Programm auch die Möglichkeit eines Abbruchs gegeben sein, wenn etwa ein Server nicht erreichbar ist oder der Benutzer die gewünschte Eingabe nicht liefern kann.

try mit Ressourcenfreigabe

Ab Java 7 hat man die Möglichkeit, die Freigabe von manchen Ressourcen direkt übers System regeln zu lassen. Bis dahin musste man dies mit entsprechenden try-catch Konstrukten im finally-Block selber bewerkstelligen. Damit dies möglich ist, muss die Ressource die Schnittstelle java.lang.AutoCloseable implementieren. Dies ist etwa bei Scanner, InputStream und Writer. Diese try-Funktion wird auch ARM-Block genannt von Automatic Resource Management.
Die Syntax ist ein erweiterter try, bei der die Ressource wie ein Parameter an try übergeben wird:

try (Ressource) {
  ...
} // end try

InputStream input = ClassLoader.getSystemResourceAsStream("EastOfJava.txt");
try (Scanner res = new Scanner(input)) { // die Variable "res" ist final und wird mit dem "input" gefüllt, der eine Instanz von AutoCloseable ist
  System.out.println(res.nextLine());
} // end try

static String readFirstLine(File file) {
  try (BufferedReader br = new BufferedReader(new FileReader(file))) {
    return br.readLine();
  } //end try
  catch (IOException e) {
    e.printStackTrace(); 
    return null;
  } // end catch
}

Beim unteren Beispiel mit dem InputStream muss ein catch hinzugefügt werden, weil dies in der Definition mit einem throws explizit gefordert wird. Entsprechend muss der catch-Block eingefügt werden, da es sonst einen Kompilierfehler gibt.

catch-Typen

Für die Behandlung muss man natürlich wissen, welche Ausnahme ausgelöst wird. Das heisst, es muss bekannt sein, welcher Art Fehler aufgetreten ist. Können mehrere Fehlerarten auftreten, so müssen diese alle aufgeführt werden. Stimmt der Fehlertyp nicht überein, versucht Java, den Fehler in einer höheren Klasse abzufangen. Gelingt dies nicht, so wird das Programm mit einem Runtime-Error beendet.

try {
  // Anweisungen
} // end try

catch(Typ1 Objekt) {
  // Anweisungen
} // end catch Typ1

catch(Typ2 Objekt) {
  // Anweisungen
} // end catch Typ2

multi-catch

Wenn man mehrere Fehler gleich behandeln und dabei Code-Duplizierung vermeiden möchte, dann gibt es ab Jav 7 die multi-catch Variante:

try {
  ...
}
catch (E1 | E2 | ... | En  exception)

Auswertung im catch-Block

Damit man Fehlermeldungen loggen kann, sind folgende Auswertungen sinnvoll:

catch (NumberFormatException e) {
  String name = e.getClass().getName();
  String msg  = e.getMessage();
  String s    = e.toString();

  System.out.println( name );// java.lang.NumberFormatException
  System.out.println( msg ); // For input string: "19 %"
  System.out.println( s ); // java.lang.NumberFormatException: For input string: "19 %"

  e.printStackTrace();
} // end catch

eigene Fehlerklassen

Möchte man auf "eigene" Fehler reagieren können, so kann man auch eigene Fehlerklassen definieren und diese dann verwenden. Dabei sollte man von (Runtime)Exception ableiten:

class MyException extends RuntimeException{
  public MyException() {
  } // end leerer Konstruktor

  public MyException(String s) {
    super(s);
  } // end Konstruktor mit string, welcher den Konstruktor von Exception aufruft
} // end MyException

public class MyClass {
  public static void main(String[] args) {
    try {
      throw new MyException("Fehler aufgetreten..."); // Fehlerbehandlung explizit aufrufen
    } // end try

    catch(MyException e) {
      System.out.print(e.getMessage());
    } // end catch MyException
  } // end main
} // end MyClass

Fehler abfangen und weiterleiten

Manche Fehler möchte man nicht selber komplett regeln. Man gibt dann einfach einen Fehler aus und wirft eine neue Ausnahme, welche dann "nach oben" gereicht wird.

public static URI createUriFromHost(String host) throws URISyntaxException {
    try {
      return new URI("http://" + host);
    }
    catch (URISyntaxException e) {
      System.err.println( "Hilfe! " + e.getMessage() );

      throw e;
    }
  }

Hier wird der Fehler mit dem throw weitergereicht.

throws (Fehler ankündigen)

Falls eine Methode einen Fehler (Exception) auslösen könnte (Datei nicht schreibbar usw.) und diesen Fehler nicht selber behandeln will, kann sie dies wegen des Sicherheitskonzepts von Java ankündigen, indem sie beim erstellen der Methode throws mitgibt und dann die Art des Fehlers.
Gibt es verschiedene Fehlerarten, so werden diese komplett mit Komma getrennt aufgelistet. Dann muss die implementierende Methode den problematischen Block jeweils mit try umschliessen. Macht sie dies nicht, wirft der Compiler einen Fehler. Damit kann man Fehler auch weiter auslagern und die Behandlung erst eine Stufe "weiter oben" durchführen.

[Modifikator] Typ Methode() throws Exception1, Exception2, ... {
  // ....
  try {
  ... // problematische Anweisung
  } // end try
  catch(Exception1 x) {
  ...  // Fehlerbehandlung der Ausnahme 1
 } // end catch
  catch(Exception2 x) {
  ...  // Fehlerbehandlung der Ausnahme 2
 } // end catch
} // end Methode

Dies wird entsprechend bei Bibliotheken und Schnittstellen angewandt, um eine korrekte Implementierung zu gewährleisten.
Die Exceptions müssen entsprechend mit import eingebunden werden, wenn man die Methode explizit eingebunden hat. Vergisst man dies so meckert der Compiler, weil er das Fehlerbearbeitungsobjekt nicht findet.

import Methode;
import Exception1;
import Exception2;

finally

Dieser Block ist optional und wird immer aufgerufen, wenn er vorhanden ist. Er wird genutzt, um wichtige Arbeiten auszuführen, auch wenn ein Fehler auftrat (Verbindungen schliessen usw.).

[Modifikator] Typ Methode() throws Exception1, Exception2, ... {
  // ....
  try {
  // problematische Anweisung
  } // end try
  catch {
  // Fehlerbehandlung
  } // end catch
  finally {
  // Schlussblock, welcher immer aufgerufen wird
  } // end finally
} // end Methode

Wichtig ist, dass wenn im finally Block Fehler auftreten, dann wird ein vorher im try Block erzeugter Fehler überschattet. Aus diesem Grund muss man hier entweder mit einem Flag arbeiten, welches den Fehler registriert oder man verwendet eine andere Programmlogik.

Reguläre Ausdrücke

Siehe dazu den Artikel Reguläre Ausdrücke. Hier werden nur Java spezifische Sachen behandelt. So ist etwa das Ganze im Paket java.util.regex zu finden.
Neben den "normalen" Aufzählungen wie .*? gibt es noch die Aufzählung X(?!Y) welche bedeutet, dass Y nicht auf X folgen darf, damit der Ausdruck true ist.
Im Gegensatz zu PHP verwendet Java die Modifikatoren als Flags in Klammern. So bedeutet (?i), dass ab diesem Ausdruck insensitive verglichen wird:System.out.println("wauWau".matches("(?i)wauwau")); // true

Vergleich

Für einen Vergleich verwendet man entweder die Methode java.util.regex.Pattern.matches() oder bei Strings die Objektmethode matches().

Pattern.matches( "'.*'", "'Hallo Welt'" ) // true -> Hochkomma + beliebiger Inhalt + Hochkomma

Suche

String s = "'Demnach, welcher verheiratet, der tut wohl; welcher aber nicht verheiratet, der tut besser.' 1. Korinther 7, 38";
Matcher matcher = Pattern.compile( "\\d+" ).matcher( s ); // sucht Zahlen
while ( matcher.find() ) // true, wenn etwas gefunden wird, das dem Suchkriterium entspricht
  System.out.printf( "%s an Position [%d,%d]%n",
                     matcher.group(), // Gibt das Suchresultat zuück (in diesem Fall die Zahlen 1, 7, 38)
                     matcher.start(), matcher.end() ); // Start- und Endposition im String (Die Endposition ist immer eins höher. Bei der Position von der 7 wird 107,108 zurückgegeben.)

Links

Link Beschreibung Niveau
http://java.programmersbase.net/ Einführung in die Java-Programmierung Anfänger
http://openbook.galileocomputing.de/javainsel9/ Buch: Java ist auch eine Insel Anfänger-Fortgeschrittene
https://www.oracle.com/java/technologies/javase-downloads.html Dokumentation Java-API zum Download Anfänger-Fortgeschrittene
https://docs.oracle.com/javase/9/docs/api/index.html?overview-summary.html Dokumentation Java-API zum Online nachlesen Anfänger-Fortgeschrittene