Java "Einführung"



Einleitung

Dieses Tutorial ist keine "Einführung" im eigentlichen Sinne. Es werden nur einige ausgewählte Themen behandelt. Die Auswahl der behandelten Themen richtet sich nach den Erfahrungen aus dem Praktikum Wissenschaftliches Rechnen und Numerische Mathematik, dabei sollen häufig gestellte Fragen, sowie einige Problem-Gebiete abgedeckt sein.
Des Weiteren werden die Themen in dieser Einführung keinesfalls vollständig behandelt, es werden stattdessen nur einige Aspekte an Beispielen erlätert. Für weiterführende Informationen empfehle ich das Online-Buch Java ist auch eine Insel .

Variablen in Java

Zur Speicherung von Daten werden in Java Variablen benutzt. Jede Variable besitzt dabei einen Typ (Datentyp). Dieser Datentyp gibt die Art der Daten vor, die in der Variable abgelegt werden können.
In Java gibt es grundsätzlich zwei Arten von Datentypen: Primitive Datentypen und Referenztypen (auch Klassentypen). Zu den Primitiven Datentypen gehören
				boolean, char, short, int, long, float, double.
Die Klassentypen werden durch den Programmierer festgelegt, so stellt jede Java-Klasse einen solchen Klassentyp dar.

Bevor eine Variable zum Ablegen von Daten benutzt werden kann muss diese deklariert (auch definiert) werden, das geschieht immer auf die gleiche Weise:
				Typname Variablenname;
Wobei Typname ist die Name des Typs der Variable, und Variablenname ist der Name der Variablen, unter dem sie dann angesprochen werden kann.

Geltungsbereiche von Variablen

In einem Java-Programm kann man auf eine Variable nicht von überall zugreifen. Entscheidend darüber, wo die Variable sichtbar (benutzbar) ist (Geltungsbereich) ist im Wesentlichen die Stelle der Deklaration.
Es gibt in Java drei Arten von Variablen hinsichtlich des Geltungsbereiches: Klassenvariablen, Objektvariablen und lokale Variablen.
  • Klassenvariablen - sind innerhalb einer ganzen Klasse sichtbar. Eine Klassenvariable wird innerhalb einer Klasse mit Hilfe des Schlüsselworts static deklariert:
    static Typname Variablenname;
    Eine solche Variable ist dann auch in jedem Objekt dieser Klasse verfügbar und hat somit insbesondere unabhängig von dem jeweiligen Objekt immer den gleichen Wert;


  • Objektvariablen - sind innerhalb eines Objektes sichtbar. Eine Objektvariable wird wie eine Klassenvariable innerhalb einer Klasse deklariert:
    Typname Variablenname;
    Eine solche Variable kann in jedem Objekt dieser Klasse einen anderen Wert haben. Genau genommen wird für jedes neue Objekt eine neue Variable mit dem gleichen Namen erzeugt;

  • Lokale Variablen - sind innerhalb von Blöcken sichtbar in denen sie deklariert wurden. Ein Block wird dabei durch geschweifte Klammern definiert, d.h. etwa eine Methode;
Hier ist nun ein Beispiel für Geltungsbereiche der Variablen:
  1. public class meineKlasse {

  2. static int nummer; // Klassenvariable

  3. double laenge; // Objektvariable

  4.  

  5. public void meineMethode()

  6. {

  7. int k = 0;

  8.  

  9. for(int i = 0; i < 100; i++)

  10. {

  11. k = k + i;

  12. }//end for

  13.  

  14. }//end meineMethode

  15.  

  16. }//end class meineKlasse

Die Variable k ist dabei nur innerhalb der Methode meineMethode sichtbar und i nur innerhalb der for-Schleife.

Bem.: Es ist zu beachten, dass die Klassenvariablen und Objektvariablen automatisch mit null initialisiert werden, die lokalen Variablen müssen dagegen stets "manuell" initialisiert werden;

Variablenübergabe

In Java werden die Variablen primitiver Datentypen und Referenztypen bei der Übergabe auf unterschiedliche Weise gehandhabt:
bei der Übergabe einer Variablen eines primitiven Datentyps wird der Wert der Variable kopiert (Call By Value), im Fall eines Referenztyps wird dagegen nur die Referenz (der Verweis) kopiert, sodass die neue Variable auf dasselbe Objekt verweist. Der Unterschied lässt sich einfacher an einem Beispiel erklären: wir vergleichen den Primitien Datentyp int, und den von uns definierten Klassentyp meinInt, der nichts weiter macht als eine Variable von Typ int zu kapseln
  1. public class meinInt {

  2. public int value;

  3. }//end class meinInt

In der folgenden Klasse werden nun zwei Variablen vom Typ int und meinInt erstellt, und anschließend an die jeweilige Methoden inc übergeben:
  1. public class callTester {

  2.  

  3. public static void main(String[] args)

  4. {

  5. int i = 0;

  6. meinInt j = new meinInt();

  7.  

  8. inc(i);

  9. inc(j);

  10.  

  11. System.out.println(i);

  12. System.out.println(j.value);

  13. }//end main

  14.  

  15.  

  16. private static void inc(int k)

  17. {

  18. k = k + 1;

  19. }//end inc

  20.  

  21.  

  22. private static void inc(meinInt k)

  23. {

  24. k.value = k.value + 1;

  25. }//end inc

  26.  

  27. }//end class callTester

Die Methoden inc bewirken in beiden Fällen das gleiche: im Fall von int wird der übergebene Wert um eins erhöht, und im Fall von meinInt wird der gekapselte Wert um eins erhöht, aber es wird nichts zurückgegeben. Nach dem Ausführen erhalten wir die Ausgabe
				0
1
Das bedeutet dass der Wert von i nicht verändert wurde, sehr wohl aber der Wert innerhalb von j (!). Folgende Grafik veranschaulicht, was bei der Übergabe der Variablen passiert ist:


Die eckigen Kästen kennzeichnen dabei Speicherzellen. Die Änderungen der Variable k in der Methode inc(int k) haben keinerlei Auswirkungen auf die Variable i, im Gegensatz zu inc(meinInt k), wo j und k auf dasselbe Objekt zugreifen.

Bem.: Auf dieselbe Weise wird auch beim Zuweisen der Variablen verfahren:
				a = b;
Im Fall eines primitiven Datentyps wird der Wert kopiert (neuer Speicherbereich), im Fall eines Referenztyps wird nur der Verweis kopiert, beide Variablen verweisen dann auf dasselbe Objekt;

Klassen und Objekte

Java ist eine Objekt-orientierte Programmiersprache. Was Objekte sind, wie man sie erzeugt und wie man sie benutzt wird nun kurz erläutert. Ein Java-Programm besteht im wesentlichen aus Klassen. Eine Klasse beschreibt die Eigenschaften der Objekte und kann als eine Schablone (Vorlage) betrachtet werden. Die Objekte sind dann Realisierungen einer Klasse, und werden auch als Instanzen bezeichnet.

Hier nun ein einfaches Beispiel für eine Java-Klasse:
  1. public class quadratischeFunktion implements function

  2. {

  3.  

  4. // Koeffizienten

  5. private double a;

  6. private double b;

  7. private double c;

  8.  

  9. /** Erstellt eine neue Instanz der Klasse quadratischeFunktion */

  10. public quadratischeFunktion(double a, double b, double c)

  11. {

  12. this.a = a;

  13. this.b = b;

  14. this.c = c;

  15. }//end constructor quadratischeFunktion

  16.  

  17.  

  18. public double f(double x)

  19. {

  20. return a + b * x + c * Math.pow(x, 2);

  21. }//end f

  22.  

  23. }//end class quadratischeFunktion


Diese Klasse beschreibt eine einfache quadratische Funktion f(x) = a +bx +bx^2. Ein wichtiger Teil einer Klasse ist der Konstruktor. Der Konstruktor ist eine spezielle Methode, die zur Erzeugung eines Objektes verwendet wird. Im Konstruktor steht also alles, was getan werden muss um ein Objekt einer Klasse zu erzeugen, z.B. Initialisierung der Variablen.
Bem.: Eine Klasse kann mehrere Konstruktoren besitzen, diese müssen sich dann aber in der Parameterliste unterscheiden;

Erzeugen von Objekten

Ein Objekt einer Klasse kann man in java mit Hilfe des Operators new erzeugen. So erzeugt die Zeile
				new meineKlasse();
ein Objekt der Klasse meineKlasse. Um auf das erzeugte Objekt zugreifen zu können, brauchen wir eine Referenz (Verweis) der auf dieses Objekt verweist. Die folgende Anweisung erzeugt eine Referenz der Klasse meineKlasse:
				meineKlasse name;
Bem.: Einem Verweis können nur Objekte verträglicher Klassen zugewiesen werden.
Die folgenden Zeilen bewirken nun dass ein Verweis der Klasse meineKlasse angelegt wird und Objekt der Klasse meineKlasse erzeugt und dem Verweis name zugeordnet wird:
				meineKlasse name;
name = new meineKlasse();
Bem.: in Java ist folgende kurze Schreibweise erlaubt
				meineKlasse name = new meineKlasse();
Oft werden die Referenzen synonym mit den Objekten verwendet auf die diese verwiesen. In den meisten Fällen ist diese Unterscheidung auch nicht notwendig, wir werden aber auch Fälle sehen in denen diese Unterscheidung zwingend notwendig ist.
Bem.: Es können mehrere Referenzen auf dasselbe Objekt verweisen: z.B.:
				meineKlasse name2 = name;
Folgende Grafik veranschaulicht den Unterschied zwischen dem Objekt und der Referenz:



Bem.: beim Erzeugen von Objekten wird der entsprechende Konstruktor aufgerufen;

Zugriff auf Eigenschaften der Objekte

Klassen werden durch Ihre Eigenschaften (Variablen und Methoden) definiert und somit auch Objekte dieser Klassen. Auf diese Eigenschaften kann von "außen" zugegriffen werden. Um auf die internen Eigenschaften einer Klasse oder eines Objektes zuzugreifen wird der Operator "." benutzt:
				double y = name.f(2);
In diesem Fall rufen wir die Objekt-Methode "f" auf, und bekommen somit den Wert der Funktion an der Stelle 2;
Bem.: Mit "." können wir nur auf die Eigenschaften der Objekte zugreifen, die "freigegeben" sind, in unseren Beispiel können wir  nicht auf die Variablen a,b und c direkt zugreifen, d.h.
				double u = name.a 
wird nicht funktionieren. Hier also einige Schlüsselwörter, die Rechte für den Zugriff auf die internen Eigenschaften einer Klasse festlegen:
  • public - legt fest, dass auf die Eigenschaft von jeder Klasse bzw. jedem Objekt aus zugegriffen werden kann;
  • protected - legt fest, dass auf die Eigenschaft nur aus den Klassen (Objekten) zugegriffen werden kann, die von dieser Klasse abgeleitet wurden;
  • private - legt fest, dass auf die Eigenschaft nicht von außen zugegriffen werden kann;
Bem.: unter Eigenschaften werden an dieser Stelle Methoden und Variablen verstanden;
Bem.: wenn kein Schlüsselwort explizit verwendet wird, so ist die Variable automatisch public;

Vererbung in Java

Die Klassen werden in Java durch ihre Eigenschaften (Methoden und Variablen) definiert. Diese Eigenschaften kann eine Klasse an andere Klassen vererben. Das geschieht mit Hilfe des Schlüsselwortes extends. Bertachten wir nun als Beispiel die folgende Klasse:
  1. public class lineareFunktion extends quadratischeFunktion

  2. {

  3. /** Erstellt eine neue Instanz der Klasse quadratischeFunktion */

  4. public lineareFunktion(double a, double b)

  5. {

  6. super(a, b, 0);

  7. }//end constructor lineareFunction

  8.  

  9. }//end class lineareFunktion

man sagt die Klasse lineareFunktion "erbt", oder "wird abgeleitet" von der Klasse quadratischeFunktion. Auf diese Weise besitzt die Klasse lineareFunktion alle Eigenschaften der Klasse quadratischeFunktion, insbesondere also auch die Variablen a,b,c und die Methode public double f(double x).

Bem.: super(...) ruft den entsprechenden Konstruktor der Elternklasse auf;

Schnittstellen (Interface)

Schnittstellen (Interface) sind Klassen, die jedoch nur Signaturen von Methoden enthalten aber keine Implementation.
Ein Beispiel für ein Interface:
  1. public interface function

  2. {

  3. double f(double x);

  4. }//end inteface function

In der Zeile 3. wird die Signatur der Methode f(double x) festgelegt, die genaue Implementation fehlt jedoch. Von einem Interface können keine Objekte erstellt werden, ein Interface gibt an welche Methoden eine Klasse haben muss, die dieses Interface implementiert. Wenn eine Klasse ein Interface implementieren soll, so wird es mit dem Schlüsselwort implements festgelegt
public class quadratischeFunktion implements function
{
...
}
In diesem Beispiel muss also die Klasse quadratischeFunktion eine Methode public double f(double x) enthalten.
Um die Notwendigkeit von Schnittstellen zu verdeutlichen betrachten wir folgendes Beispiel: angenommen wir wollen eine Methode implementieren, die die Ableitung einer beliebigen eindimensionalen Funktion an einer bestimmten Stelle approximiert. Die Funktion wird dabei als ein Objekt irgendeiner Klasse übergeben. Wie die Funktion genau implementiert ist, und welche Eigenschaften diese Klasse sonst noch hat interessiert uns an dieser Stelle nicht: für uns ist wichtig, dass diese Klasse eine Methode public double f(double x) besitzt, die uns den Wert der Funktion an der Stelle x liefert. Somit schreiben wir also eine Methode, die als Parameter Objekte einer Klasse akzeptiert, die die Schnittstelle function implementiert

  1. private static double derivative(function f, double x)

  2. {

  3. double h = 1e-4;


  4. double delta =(x+h)*h;


  5. double derivative = (f.f(x+delta) - f.f(x))/delta;


  6. return derivative;

  7. }//end derivative

Somit bleiben dem Benutzer dieser Methode alle Freiheiten zur Implementation seiner Funktion und der Benennung der Klasse :)

Fehlerbehandlung in Java

Jedes gute Programm braucht eine gute Fehlerbehandlung. Java verfügt zu diesem Zweck über das Konzept der Exception (Ausnahmen). Jedes Mal, wenn zur Ausführungszeit eines Java-Programms ein Fehler auftritt wird eine Exception erzeugt ("geworfen") die dann später an der richtigen Stelle "abgefangen" und behandelt werden kann. Genau genommen wird in der Regel ein Objekt der Klasse Exception, oder einer abgeleiteten Klasse erzeugt und nach oben gereicht. Dieses Objekt enthält die Informationen über den Fehler. Taucht in einer Methode eine Ausnahme auf, so kann diese entweder abgefangen, oder weitergeleitet werden. Wird eine Ausnahme bis nach ganz oben gereicht, so wird die Ausführung des Programms unterbrochen und eine entsprechende Fehlermeldung wird ausgegeben.
Zum werfen, weiterleiten und abfangen von Ausnahmen steht in java Konstrukte throw, throws und try-catch zu Verfügung die jetzt näher erläutert werden.

Ausnahmen weiterleiten

Soll eine Methode die Ausnahmen weiterleiten, so kann es wie im folgenden Beispiel festgelegt werden:
	public static void meineMethode() throws exceptionKlasse1, exceptionKlasse2, ... ,exceptionKlasseN
{
...
}
Bei der Deklaration der Methode wird mit throws festgelegt welche Klassen von Ausnahmen weitergeleitet werden sollen: es werden alle Ausnahmen weitergeleitet, die zu den Klassen in der throws Liste, oder zu abgeleiteten Klassen gehören.
Bem.: sollen alle möglichen Ausnahmen weitergeleitet werden, so genügt in der Regel nur die Angabe der Klasse Exceptions;

Ausnahmen abfangen

Manchmal ist es nicht erwünscht, dass das Programm bei einem Fehler, wie etwa falsche Eingabe, abstürzt. Damit das nicht passiert, müssen die entsprechenden Exceptions abgefangen werden. Dazu steht in Java das try ... catch Konstrukt zur Verfügung, das folgendermaßen verwendet wird:
	try
{
Anweisung 1;
Anweisung 2;
.
.
.
}
catch(ExceptionTyp1 e)
{
ExceptionAnweisungen1;
}
.
.
.
catch(ExceptionTypN e)
{
ExceptionAnweisungenN;
}
Bei normaler Programmausführung werden die Anweisungen innerhalb des try Blocks ausgeführt, entsteht dabei ein Fehler, so wird eine Exception geworfen, die dann von einem der catch Blöcke abgefangen werden kann. Die Ausführung der "normalen" Anweisungen wird dabei unterbrochen und in einen "passenden" catch Block gesprungen. Ein catch(KlassentypName e) Block kann nur die Ausnahmen abfangen, die vom Typ KlassentypName oder von einem abgeleiteten Klassentyp sind.
Bem.: in der Regel gehören alle Ausnahmen zu den von der Klasse Exception abgeleiteten Klassen, sodass
	try
{
...
}
catch(Exception e)
{
...
}
alle Ausnahmen abfängt;

Ausnahmen erzeugen

Sehr oft ist es bequemer das Misslingen einer Operation durch das Werfen einer Ausnahme anzuzeigen, anstatt die Rückgabewerte zu kontrollieren. Betrachte dazu das folgende Beispiel:


  1. public static double divide(double x, double y) throws Exception

  2.  

  3. return x/y;

  4.  

  5. }//end divide

Wird nun zum Beispiel die Methode wie folgt aufgerufen z = divide(1,0) so wird als Rückgabewert Infinity zurückgegeben, was zu falschen Ergebnissen führen kann wenn man weiter rechnet. Wir betrachten die Division durch 0 als Fehler und wollen gegebenenfalls eine entsprechende Ausnahme an die aufrufende Methode werfen:


  1. public static double divide(double x, double y) throws Exception

  2. {

  3. if(y == 0)

  4. throw new Exception("Division durch NULL.");

  5.  

  6. return x/y;

  7. }//end divide

Bem.: throws Exception gibt an, dass Ausnahmen der Klasse Exception nicht in der Methode behandelt werden, sondern weitergeleitet werden sollen;
Bem.: die Zeile throw new Exception("Division durch NULL."); bewirkt dass eine Ausnahme der Klasse Exception mit der entsprechender Meldung erzeugt wird;
Heinrich Mellmann, 04.05.2006
mellmann@mathematik.hu-berlin.de