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
  • 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;
  • 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

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 \\n0000

Diese Datentypen können mit sogenannten Wrapper-Klassen bei Bedarf auch als Objekte verwendet werden. Da mir der Sinn schleierhaft ist, wird das Ganze hier auch nicht weiter behandelt.

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 umgewandelt werden in einen Integerwert.

Variablen

Typ Name = Wert;

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.

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;

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.

Arrays kopieren (arraycopy)

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

Wert in Arrays suchen (binarySearch)

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.

Arrays vergleichen (equals)

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.

Array mit bestimmtem Wert füllen (fill)

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

Array sortieren (sort)

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

Länge des Arrays ermitteln (length)

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

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,8); 
    String string4 = new String(string1);
  } // end main
} // end MyClass

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

String Länge ermitteln (length)

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

Zeichen an einer gewissen Position ermitteln (charAt)

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

Substring extrahieren (substring)

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 1";
    System.out.println(string1.substring(1,3)); // ergibt 'tr'
  } // end main
} // end MyClass

Ergibt hier ein 'i'.

Strings auf Inhalte prüfen (z.B.:startsWith, equals, compareTo)

Je nach Bedarf kann man folgende Vergleiche machen:

Befehl Bedeutung Beispiel
equals Exakter Zeichen- und Längenvergleich
String string1 = "TEST";
String string2 = "Test";

System.out.println(string1.equals(string2)); // -> false
System.out.println(string1.equalsIgnoreCase(string2)); // true
equalsIgnoreCase Gross- Kleinschreibung wird ignoriert
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)
endsWith Der String muss auf die mitgegebene Sequenz enden
String string = "JAVA.ProgrammersBase.NET";
String search = "NET";

System.out.println(string.endsWith(search)); // -> true
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.
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 verglichen, da die Zeichen ja ignoriert werden.
regionMatches Prüft, ob zwei Sequenzen im String übereinstimmen. Bei Bedarf auch mit Gross- Kleinschreibung.
String string1 = "JAVA.ProgrammersBase.NET";
String search =  "CPP.ProgrammersBase.NET";
   
System.out.println(string1.regionMatches(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

Position eines Zeichens in String finden (indexOf)

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.

Zeichen ersetzen (replace, toLowerCase, toUpperCase)

Da Strings konstant sind, wird der entsprechende String kopiert und das angepasste Resultat zurückgeliefert.

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

Mit toLowerCase oder toUpperCase wird der komplette String angepasst.

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

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.

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

string.append("zusatz");
System.out.println(string); // -> Testzusatz
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
deleteCharAt entfernt das Zeichen an der mitgegebenen Position
StringBuffer string = new StringBuffer("Testi");
    
string.deleteCharAt(4);
System.out.println(string); // -> Test
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
setCharAt Ersetzt ein Zeichen an der gegebenen Position
StringBuffer string = new StringBuffer("Tast");
    
string.setCharAt(1, "e");
System.out.println(string); // -> Test
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
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
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
else {
  // Anweisungen
}

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 darf nur int sein. Es sind auch keine Wertebereiche möglich. Alle Anwesiungen nach dem case werden ausgeführt bis zum 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.

Break und Continue

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

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
  • . 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, 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

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

[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();
class MyClass {
  // Anweisungen
} // end MyClass
      
public class MyTest {
  public static void main(String[] args) {
    MyClass object = new MyClass(); // Diesen Befehl könnte man 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 muss der jeweilige Elternkonstruktor aufgerufen werden (super.Konstruktor();), falls man im Konstruktor Variablen "oberhalb" definieren will (hier die Variable i):

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

Schnittstellen (Interfaces)

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 s = "ProgrammersBase.NET";
  public void print();
} // end MyInterface

class MyClass implements MyInterface {
  public void print() {
    System.out.print(s);
  } // end print
} // end MyClass

public class MyTest {
  public static void main(String[] args) {
    MyClass object = new MyClass();
    object.print();
  } // 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. Man kann auch mehrere Schnittstellen implementieren. Diese werden jeweils mit Komma getrennt angegeben. Bis jetzt habe ich den Sinn dieser Schnittstellen noch nicht verstanden, da die Methoden ja abstrakt sind und somit jeweils programmiert werden müssen.

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.

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:

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

Modifikatoren (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 Von überall her (auch externe Klassen). Wird eine Klasse privat markiert, so kann man auch nicht mehr auf interne öffentliche Variablen zugreifen -> wirklich? - -
protected eigene Klasse und davon abgeleitete Klassen (Alle Klassen des Pakets) - -
private nur in der eigenen Klasse - -
package scoped 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.
final - - Fixiert den Wert. Variablen können nicht mehr verändert werden und Klassen/Methoden nicht abgeleitet.
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.
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.

Zugriff auf Variablen

öffentliche Variablen

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.

Methoden

Wie beim Konstruktor gesehen, kann man einer Methode Werte übergeben und auch wieder daraus auslesen.

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

Fehlerbehandlung (Try-Catch)

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.
  • fehlerhafte Typkonvertierung
  • Zugriff über die Arraygrenze hinaus
  • Zugriff auf einen leeren Zeiger

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.

Der Ablauf ist folgendermassen:

  • Aufruf des try-Blocks
  • 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.
  • 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.

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

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 Exception ableiten:

class MyException extends Exception {
  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 ankündigen (throws)

Falls eine Methode einen Fehler (Exception) auslösen könnte (Datei nicht schreibbar usw.), muss dies wegen des Sicherheitskonzepts angekündigt werden, indem man beim erstellen der Methode throws mitgibt und dann die Art des Fehlers. Gibt es verschiedene Fehlerarten, so werden diese komplett mit Komma getrennt aufgelistet. Dazu muss die Methode den problematischen Block jeweils mit try umschliessen.

[Modifikator] Typ Methode() throws Exception1, Exception2, ... {
  // ....
  try {
  // problematische Anweisung
  } // end try
} // end Methode

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

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