Programmieren in Java: Einführung


Kapitel 3: Variablen


Inhaltsverzeichnis

Dieses Buch ist unter einer Creative Commons-Lizenz lizensiert.


3.1 Datentöpfe

Informationen gehören in Töpfe

Um Informationen in Programmen verarbeiten zu können, müssen Computer-Programme grundsätzlich in der Lage sein, Informationen zu speichern. Um beispielsweise eine Zahl, die der Anwender eingibt, im Programm speichern zu können, muss es eine Art Topf geben, in den Sie diese Zahl hineinlegen können. Wird später vom Programm eine Berechnung durchgeführt, in der auf die Zahl zugegriffen werden muss, die der Anwender eingegeben hat, wird die gespeicherte Zahl einfach aus dem Topf herausgeholt. Diese Töpfe heißen in den Programmiersprachen Variablen. Eine Variable ist also ein Topf, der eine ganz bestimmte Information speichern kann.

In jeden Topf - also in jede Variable - passt jeweils nur eine Information hinein. Wollen Sie eine Zahl speichern, benötigen Sie einen Topf für diese Zahl. Wollen Sie eine zweite Zahl in Ihrem Programm speichern, benötigen Sie einen zweiten Topf. Legen Sie die zweite Zahl nämlich in den Topf hinein, in den Sie bereits die erste Zahl hineingelegt haben, geht die erste Zahl verloren. Wollen Sie also in Ihrem Programm Informationen gleichzeitig speichern, benötigen Sie für jede zu speichernde Information einen Topf, also eine Variable.

Wo Sie Variablen herbekommen, wie Sie Informationen in Variablen speichern und was Sie sonst noch im Umgang mit Variablen in Java berücksichtigen müssen, erfahren Sie in diesem Kapitel.


3.2 Primitive Datentypen

Zahlentopf, Buchstabentopf, Wahrheitswertetopf ...

Für jede zu speichernde Information benötigen Sie einen Topf - das ist aber noch nicht alles. Abhängig von der Art der Information, die Sie speichern möchten, brauchen Sie jeweils einen anderen Topf. Das heißt, wollen Sie eine Zahl speichern, brauchen Sie einen Topf, der auch Zahlen speichern kann. Wollen Sie hingegen einen Buchstaben speichern, brauchen Sie einen Topf, der Buchstaben speichern kann. Wollen Sie eine Buchstabenkette - also ein Wort oder einen Satz speichern - brauchen Sie wiederum einen anderen Topf, der Buchstabenketten speichern kann. Sie benötigen also jeweils die richtige Art von Topf, um die entsprechende Information speichern zu können. Man spricht davon, dass Variablen jeweils den richtigen Datentyp benötigen. Der Datentyp beschreibt, welche Art von Information in einer Variablen gespeichert werden kann.

Java bietet wie auch andere Programmiersprachen eine Handvoll primitiver Datentypen an. Eine andere Bezeichnung lautet intrinsische Datentypen. Es handelt sich um Datentypen, die in die Programmiersprache Java direkt eingebaut sind und die Sie ohne besondere Vorkehrungen verwenden können. Es ist zum Beispiel nicht notwendig, primitive Datentypen über die import-Anweisung in irgendeiner Form in Ihr Programm einzubinden, um sie gegenüber dem Java-Compiler bekannt zu machen. Der Java-Compiler kennt von Haus aus alle primitiven Datentypen. Genau das zeichnet diese Datentypen ja aus.

boolean b; 
char c; 
byte y; 
short s; 
int i; 
long l; 
float f; 
double d; 

Die in Java existierenden primitiven Datentypen sind boolean, char, byte, short, int, long, float und double. Jede oben definierte Variable besitzt einen anderen Datentyp, kann also eine andere Art von Information speichern.

Um Variablen in Java anzulegen, geben Sie zuerst einen Datentyp an und dahinter durch ein Leerzeichen getrennt einen Variablennamen. Variablendefinitionen werden in Java am Zeilenende zusätzlich mit einem Semikolon abgeschlossen. Die obigen Code-Zeilen legen also die Variablen b, c, y, s, i, l, f und d an. Jede dieser Variablen hat einen anderen Datentyp. Ihnen gemeinsam ist jedoch, dass jede dieser Variablen auf einem primitiven Datentyp basiert.

Während bei der Definition der Variablen der Datentyp angegeben werden muss, erfolgen Zugriffe auf Variablen nach der Definition nur mehr über den Variablennamen. Der Datentyp wird also für eine Variable genau einmal angegeben, und zwar immer nur bei der Definition. Achten Sie in den folgenden Beispielen darauf, dass Zugriffe auf Variablen ausschließlich über den Variablennamen stattfinden.

boolean b; 

b = true; 
b = false; 

Variablen vom Datentyp boolean können entweder den Wahrheitswert true oder den Wahrheitswert false speichern. Der Datentyp boolean eignet sich also für Variablen, die genau einen von zwei Zuständen speichern sollen. Ein typisches Beispiel ist also ein Lichtschalter, der entweder an oder aus ist. Den Zustand des Lichtschalters könnte man in Java in einer Variablen vom Typ boolean speichern, wobei true bedeutet, dass das Licht brennt, und false, dass das Licht aus ist.

Man spricht bei einem Speichervorgang, der mit Hilfe des Operators = erfolgt, von einer Zuweisung. Das, was rechts vom Operator = steht, wird dem, was links steht, zugewiesen. Die Zuweisung findet immer von rechts nach links statt. Obige Zeilen weisen also einmal den Wahrheitswert true, einmal den Wahrheitswert false der Variablen b zu.

Der Zuweisungsoperator = findet nicht nur bei Variablen vom Typ boolean Anwendung, sondern existiert für Variablen jeden Datentyps.

char c; 

c = 'A'; 

Der Datentyp char wird benötigt, um Buchstaben in Variablen speichern zu können. Genaugenommen können nicht nur Buchstaben, sondern beliebige Zeichen in Variablen vom Datentyp char gespeichert werden. Das zu speichernde Zeichen muss hierbei immer in einfache Anführungszeichen eingeschlossen werden. Pro Variable vom Datentyp char kann nur ein Zeichen gespeichert werden.

byte y; 
short s; 
int i; 
long l; 

y = -128; 
y = 127; 
s = -32768; 
s = 32767; 
i = -2147483648; 
i = 2147483647; 
l = -9223372036854775808L; 
l = 9223372036854775807L; 

Variablen vom Typ byte, short, int oder long ermöglichen das Speichern von Zahlen. Die Datentypen unterscheiden sich insofern, als dass sie unterschiedliche Bandbreiten besitzen. Während in Variablen vom Typ byte nur Zahlen von -128 bis +127 gespeichert werden können, können Variablen vom Typ long Zahlen von -9223372036854775808 bis +9223372036854775807 speichern. Je größer die Bandbreite, umso mehr Speicherplatz verschlingt der Datentyp.

Falls Sie sich ein wenig mehr mit Speichergrößen auskennen, ist folgende Information hilfreich für Sie: Der Datentyp byte benötigt 1 Byte, short 2 Bytes, int 4 Bytes und long 8 Bytes. Diese Speichergrößen gelten für die Datentypen auf jedem beliebigen physikalischen Rechner - egal, ob mit 32-Bit- oder 64-Bit-Prozessor. Der Datentyp boolean belegt übrigens 1 Byte, während der Datentyp char im Gegensatz zu anderen Programmiersprachen 2 Bytes belegt. Grund ist, dass Java nicht mit dem ASCII-Zeichensatz oder einem der ISO-8859-Zeichensätze arbeitet, sondern komplett auf Unicode basiert.

Beachten Sie, dass hinter den Zahlen, die der Variablen l vom Typ long zugewiesen werden, ein L angegeben ist. Diese Angabe ist notwendig, da der Compiler bei Zahlen grundsätzlich davon ausgeht, dass es sich um Integer-Werte handelt - also um Werte vom Typ int. Das L direkt hinter einer Zahl macht dem Compiler deutlich, dass es sich bei dieser vorangehenden Zahl um einen long-Wert handelt.

float f; 
double d; 

f = -3.40282347e38F; 
f = 3.40282347e38F; 
d = -1.79769313486231570e308; 
d = 1.79769313486231570e308; 

Mit den Datentypen float und double ist das Speichern von Kommazahlen möglich. float speichert Kommazahlen mit einfacher Genauigkeit (4 Bytes), double mit doppelter Genauigkeit (8 Bytes). Für Sie als Programmierer sind wiederum die Bandbreiten der beiden Datentypen am interessantesten, deren Grenzwerte im obigen Beispiel-Code angegeben sind.

Beachten Sie, dass Sie hinter Kommazahlen vom Typ float ein F angeben müssen. Der Compiler geht grundsätzlich bei Kommazahlen davon aus, dass es sich um double-Werte handelt.

Nochmal zur Verdeutlichung: Um Kommazahlen in Variablen zu speichern, muss die Variable vom Typ float oder double sein. Es ist nicht möglich, Kommazahlen in Variablen vom Typ byte, short, int oder long zu speichern. Diese Datentypen ermöglichen ausschließlich das Speichern von Ganzzahlen.

Beachten Sie obige Schreibweise der Kommazahlen. Hinter dem Buchstaben e kann ein Exponent für die Kommazahl angegeben werden.


3.3 Klassen und Objekte

Häuslebauer

Bevor Sie verstehen können, was Referenzvariablen sind, müssen Sie den Unterschied zwischen Klassen und Objekten kennen. Denn während Variablen, die auf primitiven Datentypen basieren, noch recht einfach zu verstehen sind, sind Referenzvariablen in das Gefüge von Klassen und Objekten eingebettet. Die Frage lautet also: Was sind Klassen und Objekte und wie hängen Klassen und Objekte zusammen?

Stellen Sie sich vor, Sie bauen ein Haus. Der erste Schritt besteht darin, dass Sie einen detaillierten Plan erstellen, der genau beschreibt, wie das Haus später aussehen soll - also zum Beispiel einen Grundriss. In diesen Plan müssen alle wesentlichen Beschreibungen zum Haus hinein.

Ist der Plan fertig, kann es los gehen: Auf einem von Ihnen gekauften Grundstück wird ein Haus errichtet. Dieses Haus sieht nach der Fertigstellung natürlich genauso aus wie im Plan beschrieben.

Nachdem Sie noch etwas Kleingeld übrig haben, beschließen Sie, ein zweites Haus zu bauen. Auch für das zweite Haus wird genau der gleiche Plan verwendet wie für das erste Haus. Es sieht dementsprechend dem ersten Haus ähnlich, steht aber natürlich auf einem anderen Grundstück, vielleicht in einer anderen Strasse oder sogar in einer anderen Stadt.

In der objektorientierten Terminologie entspricht der Plan der Klasse und die gebauten Häuser den Objekten. Während es mehrere Häuser geben kann, die mit Hilfe des gleichen Plans erbaut wurden, kann es mehrere Objekte vom Typ einer Klasse geben. So wie der Plan lediglich eine Beschreibung eines Hauses ist, ist die Klasse lediglich eine Beschreibung eines Objekts. Weder der Plan alleine noch die Klasse machen jedoch das Haus bzw. das Objekt lebendig. Es handelt sich hierbei lediglich um Beschreibungen. So wie Sie erst in das fertiggebaute Haus einziehen können, können Sie in Ihrem Programm auch nur mit Objekten arbeiten. Sie benötigen zum Erstellen eines Objekts eine Klasse, die das Objekt beschreibt - so wie Sie zum Bauen eines Hauses auch erstmal einen Plan benötigen.

Andere Analogien sprechen von Art oder Gattung. Wenn mehrere Objekte vom Typ einer Klasse sind, gehören sie derselben Gattung an, sind sie derselben Art. So wie es die Fahrzeugklasse 318 von BMW gibt, gibt es mehrere Fahrzeuge von dieser Fahrzeugklasse - eine Klasse, mehrere Objekte.

Nachdem Ihnen in Java standardmäßig eine Klassenhierarchie zur Verfügung steht, können Sie häufig basierend auf den in der Klassenhierarchie bereitgestellten Klassen Objekte erstellen, ohne die entsprechenden Klassen selber entwickeln zu müssen - diese Arbeitserleichterung ist auch genau Sinn und Zweck der Klassenhierarchie.

Abhängig von der jeweils verwendeten Klasse besitzen Objekte unterschiedliche Eigenschaften und Fähigkeiten. Je nachdem, was Sie für Eigenschaften und Fähigkeiten benötigen, müssen Sie daher beim Erstellen eines Objekts auf die richtige Klasse zugreifen. Die Entwicklungsarbeit besteht für Sie als Java-Programmierer also zum Teil darin herauszufinden, welche Klasse Sie für eine bestimmte Funktionalität benötigen.

Steht eine Klasse nicht zur Verfügung, müssen Sie sie unter Umständen selber programmieren. Sie können Objekte nicht ohne eine Klasse erstellen. Voraussetzung für das Anlegen von Objekten sind Klassen. Das Erstellen von Objekten besteht also immer aus zwei Schritten: Zuerst benötigen Sie eine Klasse (die Sie entweder einer Klassenhierarchie entnehmen oder selber programmieren), dann erstellen Sie ein Objekt vom Typ dieser Klasse. Mit dem erstellten Objekt können Sie dann in Ihrem Programm arbeiten.


3.4 Referenzvariablen

Variablen als IDs für Objekte

Mit den primitiven Datentypen können Sie bereits Variablen anlegen, die Wahrheitswerte, einzelne Buchstaben, Ganz- und Kommazahlen speichern können - allzuviel können Sie damit aber noch nicht anfangen. Sie können noch nicht einmal Wörter oder Sätze speichern. Was nun?

Als Java-Programmierer werden Sie hin und wieder Variablen vom primitiven Datentyp int anlegen. Dieser Datentyp wird normalerweise verwendet, wenn Sie Zahlen in Ihrem Programm speichern müssen. Ansonsten kommen Sie für gewöhnlich nie in Kontakt mit primitiven Datentypen. Denn die Klassenhierarchie von Java stellt Hilfsmittel zur Verfügung, mit denen sich Informationen sehr viel besser speichern lassen als mit primitiven Datentypen.

Eine Klasse wird genauso verwendet wie ein Datentyp. Sie können also Klassen unter anderem als Datentyp betrachten. Genauso wie Sie Variablen von primitiven Datentypen anlegen können, können Sie Variablen vom Typ einer Klasse anlegen. Man spricht in diesem Fall von Referenzvariablen. Referenzvariablen sind also Variablen vom Typ einer Klasse. Ob diese Klasse nun Bestandteil der offiziellen Klassenhierarchie ist, aus der Klassenhierarchie eines Drittherstellers stammt oder es sich um eine von Ihnen selbst entwickelte Klasse handelt, spielt keine Rolle.

java.lang.String Text; 

Die offizielle Java-Klassenhierarchie enthält eine ganze Menge an Klassen. Sie können nun beliebig Referenzvariablen anlegen, die als Datentyp die eine oder andere Klasse verwenden. Jede Klasse dient natürlich einem bestimmten Zweck. So gibt es beispielsweise die Klasse java.lang.String, die sich hervorragend dafür eignet, Zeichenketten zu speichern. In einer Referenzvariablen vom Datentyp java.lang.String können Sie also mehrere Buchstaben, ganze Wörter oder Sätze ablegen.

Klassen sind jedoch nicht nur einfach neue Datentypen, in denen sich eine andere Art von Information speichern lässt als mit primitiven Datentypen. Klassen bieten andere Vorteile, auf die wir noch im Laufe des Buchs zu sprechen kommen.

Auch beim Anlegen von Referenzvariablen gilt: Zuerst Datentyp, dann Variablennamen angeben. Im obigen Beispiel wird eine Variable namens Text vom Typ java.lang.String angelegt. String ist eine Klasse aus der Klassenhierarchie von Java und ist im Paket java.lang gespeichert. Die Klasse java.lang.String benötigen Sie eigentlich in jedem Java-Programm, weil Sie fast immer Zeichenketten in Form von Wörtern oder Sätzen speichern müssen.

Während Variablen von primitiven Datentypen zur Definition nur Datentyp und Variablennamen benötigen, muss bei Referenzvariablen noch angegeben werden, auf was sie eigentlich referenzieren. Zu diesem Zweck muss bei der Definition gleichzeitig mit Hilfe des Schlüsselwortes new ein Objekt der entsprechenden Klasse angelegt werden, das dann der Referenzvariablen zugewiesen wird. Das Erzeugen eines Objekts erfolgt in Java immer auf die gleiche Art und Weise: Hinter dem Schlüsselwort new wird die Klasse angegeben, hinter der wiederum zwei runde Klammern nicht vergessen werden dürfen.

java.lang.String Text = new java.lang.String(); 

Betrachten wir den Vorgang genauer. Das Verständnis von Referenzvariablen ist entscheidend für die Programmierung in Java. Zuerst wird eine Variable namens Text definiert. Diese Variable ist vom Typ java.lang.String. Es handelt sich hierbei nicht um einen primitiven Datentyp, sondern um eine Klasse, die Bestandteil der offiziellen Klassenhierarchie in Java ist. Man spricht auch von einem Referenztyp. Ist die Referenzvariable definiert, wird von der Klasse java.lang.String ein Objekt erzeugt. Dies erfolgt mit Hilfe des Schlüsselwortes new, hinter dem die Klasse mit runden Klammern angegeben wird, von der ein Objekt erzeugt werden soll. Im letzten Schritt erhält nun die Variable Text über den Operator = eine Referenz - also eine Art Verweis - auf das neu erstellte Objekt vom Typ java.lang.String. Über die Variable Text kann also nun auf ein Objekt vom Typ java.lang.String zugegriffen werden, da die Variable Text eine Referenz auf ein derartiges Objekt ist.

Was heißt Referenz genau? Warum ist es falsch zu sagen, die Variable Text ist ein Objekt vom Typ java.lang.String? Warum muss man stattdessen sagen, die Variable Text ist eine Referenz auf ein Objekt vom Typ java.lang.String?

Stellen Sie sich das Erzeugen von Objekten wie folgt vor: Durch Angabe des Schlüsselwortes new und des entsprechenden Klassennamens wird irgendwo im Java-System ein neues Objekt erzeugt. Um nun mit diesem Objekt arbeiten zu können, das irgendwo im Java-System erzeugt wurde, benötigen Sie eine Art ID, um auch auf Ihr neues Objekt zugreifen zu können. Diese ID ist die Referenzvariable. Diese Variable enthält alle Informationen, um auf das neu erstellte Objekt irgendwo im Java-System zugreifen zu können und mit diesem arbeiten zu können.

Nun kommt der Trick an den Referenzvariablen: Wenn Sie eine zweite Variable definieren, die vom gleichen Typ ist wie die erste Referenzvariable, dann müssen Sie dieser nicht unbedingt wieder ein neues Objekt zuweisen, das Sie mit new erstellen. Sie können der neuen Variablen die vorherige Referenzvariable zuweisen.

java.lang.String Text = new java.lang.String(); 
java.lang.String Text2 = Text; 

Was ist passiert? Nachdem Referenzvariablen nur Informationen enthalten, um irgendwo im Java-System ein Objekt zu identifizieren, enthält nun die Variable Text2 exakt die gleichen Informationen wie die Variable Text. Mit beiden Variablen ist es nun möglich, auf ein und dasselbe Objekt irgendwo im Java-System zuzugreifen. Das heißt, sie haben zwei Referenzvariablen, aber nur ein Objekt. Referenzvariablen sind keine Objekte, sondern nur Referenzen auf Objekte - das ist ganz wichtig für die Java-Programmierung.

Wenn Sie stattdessen der zweiten Referenzvariablen ein neues Objekt zuweisen wollen, das identisch zu dem Objekt ist, auf das die erste Referenzvariable verweist, können Sie eine Funktion clone() aufrufen. Diese Funktion ist in der Klasse java.lang.Object definiert, die wie Sie sich erinnern die oberste Klasse in der offiziellen Klassenhierarchie in Java ist. Obwohl durch Vererbung diese Funktion daher jeder anderen Klasse aus der offiziellen Klassenhierarchie zur Verfügung stehen müsste, kann clone() für zahlreiche Klassen nicht aufgerufen werden - so auch nicht für java.lang.String. Grund sind Zugriffsattribute, die die Vererbung beeinflussen. Für das folgende Beispiel wird daher auf eine Klasse java.util.Vector zugegriffen, die einen Aufruf der Funktion clone() zuläßt.

java.util.Vector vector = new java.util.Vector(); 
java.util.Vector vector2 = (java.util.Vector)vector.clone(); 

Während Sie also mit dem Zuweisungsoperator = lediglich Informationen zur Referenzierung eines Objekts von einer Variablen an die nächste weitergeben, können Sie mit der Funktion clone() ein neues identisches Objekt erzeugen und der Variablen die Referenz auf das neue Objekt zuweisen. vector2 verweist also auf ein Objekt, das eine vollständige Kopie des Objekts ist, auf das vector verweist.

Beachten Sie, dass Sie bei einem Aufruf der Funktion clone() in Klammern die Klasse des Objekts angeben müssen, das mit clone() kopiert wird. Der Grund ist, dass clone() immer Objekte vom Typ java.lang.Object zurückgibt, Sie jedoch ein Objekt vom Typ java.util.Vector erwarten. Indem Sie die Klasse java.util.Vector in Klammern vor den Aufruf von clone() stellen, wird der Datentyp java.lang.Object in java.util.Vector umgewandelt. Man bezeichnet dies als Casting.

Informationen zu Rückgabewerten von Funktionen erhalten Sie im Kapitel 4, Funktionen.


3.5 Konstruktoren

Funktionsname gleich Klassenname

Der Konstruktor ist eine Funktion, die genauso heißt wie die Klasse, in der die Funktion definiert ist. Diese Funktion hat eine besondere Bedeutung: Wann immer ein Objekt von einer Klasse erzeugt wird, wird automatisch der Konstruktor der Klasse aufgerufen. Der Konstruktor eignet sich hervorragend dafür, Objekte zu initialisieren. Der Konstruktor einer Klasse ist also vergleichbar mit der Funktion init() von Applets. Um einen Konstruktor aufzurufen, wird der Klassenname einfach um die runden Klammern erweitert.

java.lang.String Text = new java.lang.String(); 

Für die Klasse java.lang.String heißt der Konstruktor dementsprechend java.lang.String(). Wie Sie sehen, rufen Sie bereits die ganze Zeit den Konstruktor einer Klasse auf, wenn Sie ein Objekt mit dem Schlüsselwort new erzeugen.

Abhängig von der Beschreibung des Objekts - also abhängig von der Klasse - stehen unterschiedliche Konstruktoren zur Verfügung. Es gibt Konstruktoren, die keinen Parameter übergeben bekommen und bei denen daher nichts zwischen den beiden runden Klammern steht. Die Klasse java.lang.String bietet aber beispielsweise einen Konstruktor an, mit dem es möglich ist, das Objekt mit einer bestimmten Zeichenkette zu initialisieren. Wie Sie wissen können in Objekten vom Typ java.lang.String Zeichenketten gespeichert werden.

java.lang.String Text = new java.lang.String("Hallo, Welt!"); 

Nun übergeben Sie dem Konstruktor als Parameter die Zeichenkette "Hallo, Welt!". Zeichenketten werden in Java immer in doppelten Anführungszeichen angegeben. Erinnern Sie sich: Zahlen werden direkt als Zahlen angegeben, Wahrheitswerte als true oder false, einzelne Zeichen wie Buchstaben werden in einfache Anführungszeichen gesetzt und Zeichenketten wie Sie nun wissen in doppelte Anführungszeichen.

Um herauszufinden, ob eine Klasse spezielle Konstruktoren anbietet, denen Parameter übergeben werden können, um ein Objekt zu initialisieren, hilft nur der Blick in die entsprechende Dokumentation. Die meisten Klassen ermöglichen jedoch die Übergabe von Parametern an Konstruktoren.

import java.applet.*; 
import java.awt.*; 

public class MyApplet extends Applet 
{ 
  String Text = new String("Hallo, Welt!"); 

  public void paint(Graphics g) 
  { 
    g.drawString(Text, 0, 10); 
  } 
} 

Endlich kommen wir zurück zur Praxis und können nun ein Objekt vom Typ java.lang.String in ein Beispielprogramm einbauen. Es handelt sich hierbei um ein Applet, das ähnlich wie das Beispielprogramm aus dem ersten Kapitel die Meldung Hallo, Welt! ausgibt. Dies geschieht in diesem Fall über eine Funktion namens paint(). Die Funktion paint() wird automatisch vom Browser aufgerufen, wenn dieser das Applet auffordert, sich neu zu zeichen, sprich die Oberfläche des Applets zu aktualiseren. Unter anderem wird paint() bei einem Programmstart aufgerufen, nachdem init() und start() ausgeführt wurden. Auch paint() ist also ein typischer Event-Handler, der bei Eintreten bestimmter Ereignisse für Java-Applets automatisch aufgerufen wird.

Es gibt jedoch eine Besonderheit bei paint(). Dieser Funktion wird als Parameter eine Referenz auf ein Objekt vom Typ java.awt.Graphics übergeben. Es handelt sich bei java.awt.Graphics um eine Klasse, die ganz allgemein grafische Oberflächen beschreibt. Diese Klasse ist Bestandteil der Standard-Hierarchie in Java und liegt im Paket java.awt. Die Abkürzung AWT steht für Abstract Window Toolkit. Dieses Paket fasst eine ganze Menge an Klassen zusammen, um grafische Benutzeroberflächen zu erstellen und zu bearbeiten. Wie mit Klassen aus AWT gearbeitet wird, erfahren Sie im Buch Programmieren in Java: Aufbau. Wie die Übergabe von Referenzen an Funktionen per Parameter im Detail funktioniert, erfahren Sie im Kapitel 4, Funktionen.

Wichtig ist, dass paint() als Parameter eine Referenz auf ein Objekt vom Typ java.awt.Graphics übergeben bekommt und dieses Objekt die Oberfläche des Java-Applets repräsentiert. Was immer die Funktion paint() nun mit der übergebenen Referenz anstellt - sie bearbeitet die Oberfläche des Applets durch das Objekt, auf das die Referenz zeigt. Im obigen Beispiel heißt die Referenzvariable g. Über die Referenzvariable g wird innerhalb von paint() auf das Objekt zugegriffen, auf das g verweist. Dies geschieht über den Zugriffsoperator ., der einfach hinter der Referenzvariablen angegeben wird. Auf diese Weise wird über die Referenzvariable g auf das Objekt zugegriffen, auf das g verweist.

Dort angekommen wird eine Funktion namens drawString() aufgerufen. Der Aufruf von drawString() gibt einen Text ins Applet aus. In diesem Fall übergeben wir dieser Funktion nicht direkt eine Zeichenkette, sondern die Referenzvariable Text. Diese Variable enthält eine Referenz auf ein Objekt vom Typ String, das mit dem Text "Hallo, Welt!" initialisiert wurde. Außerdem bekommt drawString() zusätzlich zum Text zwei Zahlen übergeben, die die Koordinatenposition ausgehend von der linken oberen Ecke im Applet beschreiben, an der der Text ausgegeben werden soll.

Fällt Ihnen übrigens etwas auf? Obwohl wir nur den Typ String angegeben haben, fehlt ein import java.lang.String. Der Grund ist, dass alle Klassen aus dem Paket java.lang automatisch in Java-Programmen bekannt sind. In jedem Java-Programm gibt es also sozusagen eine unsichtbare Zeile import java.lang.*. Nachdem im Paket java.lang sehr viele nützliche Klassen liegen wie unter anderem die enorm wichtige Klasse String, wurde diese Design-Entscheidung von den Entwicklern der Programmiersprache Java so getroffen.

import java.applet.*; 
import java.awt.*; 

public class MyApplet extends Applet 
{ 
  String Text = "Hallo, Welt!"; 

  public void paint(Graphics g) 
  { 
    g.drawString(Text, 0, 10); 
  } 
} 

Im Zusammenhang mit Referenzvariablen vom Typ String gibt es einen Trick: Sie können bei der Definition die Angabe von new String weglassen und stattdessen direkt in Anführungszeichen die Zeichenkette der Referenzvariablen zuweisen, mit der das Objekt initialisiert werden soll. Der Compiler weiß automatisch, dass in diesem Fall noch ein Objekt angelegt werden muss und macht das auch für Sie. Weil Variablen vom Typ java.lang.String so häufig in Programmen benötigt werden, kommt diese Vereinfachung dem Programmierer sehr entgegen. Sie können also bei der Definition so tun, als handle es sich bei java.lang.String um einen primitiven Datentyp, der nicht die zusätzlich Angabe des Schlüsselwortes new erfordert.

Diese Vereinfachung führt auch ab und zu zur Verwirrung. Obwohl die Angabe von new wegfällt, handelt es sich dennoch immer noch um eine Referenzvariable - also um einen Verweis auf ein Objekt irgendwo im Java-System. Die abgekürzte Schreibweise ist also tatsächlich nicht mehr als eine Abkürzung, die auch nur bei Referenzvariablen vom Typ java.lang.String funktioniert.


3.6 Arrays

Datenfelder

Arrays sind auch Variablen, wenn in vielfacher Hinsicht etwas merkwürdig. Erst einmal sind Arrays auch Objekte. Das heißt also, sie müssen wie bei Objekten üblich Arrays mit new erstellen und in einer Referenzvariable speichern. Der Datentyp der Referenzvariablen setzt sich jedoch aus einem beliebigen primitiven Datentyp oder einer beliebigen Klasse und eckigen Klammern [] zusammen. Die Syntax zum Erstellen von Arrays ist etwas gewöhnungsbedürftig. Jedenfalls gibt es keine Klassen, die Sie verwenden müssen, um Arrays zu erstellen. Sie kombinieren stattdessen einen primitiven Datentypen oder eine Klasse mit den eckigen Klammern [].

Wozu brauchen Sie überhaupt Arrays? Stellen Sie sich vor, Sie müssen in Ihrem Programm 100 Zahlen speichern. Das heißt, sie brauchen 100 Variablen. Es wäre etwas mühselig, 100 Variablen anzulegen und auf sie zuzugreifen. Arrays bieten hier einen Ausweg an. Anstelle 100 Variablen anzulegen erstellen Sie ein Datenfeld, das Platz für 100 Zahlen bereithält - ein Array.

int[] Zahlen = new int[100]; 

Obige Code-Zeile erstellt ein Array, das Platz für 100 Ganzzahlen, also Zahlen vom Typ int bietet. Sie gehen hierbei wie folgt vor: Der Datentyp für Arrays besteht aus dem Datentyp, den Sie in Ihrem Array speichern wollen, und den eckigen Klammern []. Nachdem im Array Ganzzahlen gespeichert werden sollen, ist als Datentyp für das Array int[] gewählt worden. Dahinter geben Sie wie gewohnt einen Variablennamen an. Im obigen Beispiel lautet der Variablenname Zahlen.

Nachdem auch Arrays Objekte sind, ist Zahlen lediglich eine Referenzvariable. Sie müssen deswegen zusätzlich angeben, auf was Zahlen eigentlich verweisen soll. Dazu erstellen Sie mit new ein neues Array. Sie geben hinter new den gleichen Datentyp an wie auch vor der Referenzvariable, und zwar inklusive der eckigen Klammern. Was Sie jedoch nun zusätzlich angeben müssen ist eine Ganzzahl, die festlegt, aus wie vielen Elementen das Array bestehen soll. Diese Ganzzahl wird zwischen die eckigen Klammern gestellt. Die Referenzvariable Zahlen verweist also nun auf ein Array, in das 100 Ganzzahlen hineinpassen.

Wie können nun Zahlen in diesem Array gespeichert werden? Es ist nicht möglich, der Referenzvariablen Zahlen direkt eine Zahl zuzuweisen. Zahlen bezieht sich auf das gesamte Array, also auf sämtliche 100 zur Verfügung stehenden Plätze. Um eine Zahl im Array zu speichern, müssen Sie jeweils angeben, auf welchen der 100 Plätze Sie zugreifen möchten. Das machen Sie wie folgt.

Zahlen[0] = 50; 
Zahlen[1] = 500; 
Zahlen[99] = 5000; 

Wenn Sie auf einzelne Plätze in einem Array zugreifen wollen, geben Sie hinter dem Namen der Referenzvariable die eckige Klammer an. Dazwischen stellen Sie den Index des Feldes, auf das Sie zugreifen möchten. Der Index ist vergleichbar mit einer Hausnummer. In jedem Array sind alle Positionen durchnummeriert, und indem Sie die Nummer eines Feldes in eckigen Klammern angeben, greifen Sie auf genau dieses zu.

Beachten Sie, dass das erste Feld in einem Array in Java immer den Index 0 besitzt. Die Nummerierung beginnt also bei 0. Daraus folgt, dass der größtmögliche gültige Index für ein Array um 1 kleiner ist als die Länge des Arrays. Hat also das Array wie im Beispiel 100 Plätze, so ist der größtmögliche erlaubte Index in eckigen Klammern 99. Die Nummerierung aller Positionen im Array läuft eben nicht von 1 bis 100, sondern von 0 bis 99.

Auch wenn der Zugriff auf Datenfelder über eckige Klammern anfangs etwas merkwürdig scheint, so handelt es sich doch um ganz normale Variablen, die einfach in einem Array zu einer Gruppe zusammengestellt werden. Anstatt die Variablen wie bisher einzeln über einen Variablennamen anzusprechen, wird in diesem Fall über den Namen des Arrays auf die gesamte Gruppe und über den Index auf eine ganz bestimmte Variable in der Gruppe zugegriffen.

Sehen Sie sich folgendes Beispiel an, in dem auch wieder die Meldung Hallo, Welt! ausgegeben wird.

import java.applet.*; 
import java.awt.*; 

public class MyApplet extends Applet 
{ 
  String[] Texte = new String[2]; 

  public void paint(Graphics g) 
  { 
    Texte[0] = "Hallo,"; 
    Texte[1] = " Welt!"; 
    g.drawString(Texte[0] + Texte[1], 0, 10); 
  } 
} 

In diesem Beispiel wird der Text "Hallo, Welt!" in zwei Teile gesplittet und in einem Array vom Typ String gespeichert. Das Array namens Texte bietet hierfür genau zwei Plätze an. Es können also zwei Zeichenketten im Array Texte gespeichert werden.

Die Zuweisung von "Hallo," und " Welt!" an das Array findet innerhalb der Funktion paint() statt. Es wird hierfür einmal auf die erste Position und einmal auf die zweite Position im Array Texte zugegriffen. Nachdem das Array Texte aus Strings besteht, kann an den verschiedenen Positionen im Array also auch ohne Probleme jeweils eine Zeichenkette gespeichert werden.

In der letzten Zeile der Funktion paint() wird wieder auf die beiden Variablen im Array Texte zugegriffen. Sie werden mit dem Plus-Zeichen verkettet, so dass aus den beiden Teilabschnitten wieder die komplette Meldung "Hallo, Welt!" wird. Diese wird wiederum an die Funktion drawString() übergeben, um sie in das Applet auszugeben.

Seit Java 1.2 gibt es eine Klasse java.util.Arrays, deren einziger Sinn es ist, das Arbeiten mit Arrays zu vereinfachen. So bietet diese Klasse unter anderem Funktionen zur Sortierung von Arrays an.

import java.applet.*; 
import java.awt.*; 
import java.util.*; 

public class MyApplet extends Applet 
{ 
  String[] Texte = new String[2]; 

  public void paint(Graphics g) 
  { 
    Texte[0] = "b"; 
    Texte[1] = "a"; 
    Arrays.sort(Texte); 
    g.drawString(Texte[0] + Texte[1], 0, 10); 
  } 
} 

Im Beispiel wird wieder ein Array zur Speicherung zweier Zeichenketten erstellt. Der Datentyp des Arrays lautet also String[]. An der ersten Position im Array wird die Zeichenkette "b", an der zweiten Position die Zeichenkette "a" gespeichert. Dennoch wird in der letzten Zeile der Funktion paint() nicht die Meldung "ba" ins Applet geschrieben, sondern "ab". Dies liegt daran, dass auf eine Sortierfunktion der Klasse java.util.Arrays zugegriffen wird. Diese Sortierfunktion heißt sort() und wird einfach durch den Zugriffsoperator . von der Klasse java.util.Arrays getrennt aufgerufen. In Klammern wird das Array übergeben, das sortiert werden soll. Ein Array vom Typ String wird standardmäßig alphabetisch sortiert. Deswegen wechseln die Zeichenketten im Array Texte ihre Positionen.


3.7 Gültigkeitsbereiche

Lebensdauer und Sichtbarkeit

Jede Variable besitzt einen ganz bestimmten Gültigkeitsbereich. Damit ist gemeint, dass jede Variable zeitlich und räumlich genau definiert ist. Jede Variable hat eine ganz bestimmte Lebensdauer und existiert nur in einem bestimmten Programmbereich. Der Gültigkeitsbereich einer Variablen ergibt sich daraus, an welcher Stelle die Variable im Programm definiert ist.

import java.applet.*; 
import java.awt.*; 

public class MyApplet extends Applet 
{ 
  String Text; 

  public void init() 
  { 
    Text = "Hallo, Welt!"; 
  } 

  public void paint(Graphics g) 
  { 
    g.drawString(Text, 0, 10); 
  } 
} 

Betrachten Sie obiges Beispielprogramm: Es wird eine Referenzvariable Text direkt in der Klasse MyApplet definiert. Derartig definierte Variablen - egal, ob von primitiven Datentypen oder vom Datentyp einer Klasse - heißen Instanzvariablen. Instanzvariablen sind Variablen, die zu einem Objekt gehören. Wird also ein Objekt erstellt, bekommt dieses Objekt auch seine eigenen Instanzvariablen. Wird ein zweites Objekt von der gleichen Klasse erstellt, bekommt dieses zweite Objekt ebenfalls seine eigenen Instanzvariablen. Wenn also das eine Objekt den Wert einer seiner Instanzvariablen ändert, wird nicht der Wert der entsprechenden Instanzvariablen des zweiten Objekts geändert.

Die Bezeichnung Instanzvariable kommt daher, dass man bei Objekten auch von Instanzen spricht. Objekt und Instanz sind also zwei austauschbare Begriffe.

Auf Instanzvariablen haben alle Funktionen in einer Klasse Zugriff. Wie Sie im Beispiel sehen wird innerhalb der Funktion init() auf die Variable Text zugegriffen, um ihr eine Zeichenkette zuzuweisen, und in der Funktion paint() noch einmal, um den Inhalt der Variablen Text in die Applet-Oberfläche auszugeben.

import java.applet.*; 
import java.awt.*; 

public class MyApplet extends Applet 
{ 
  static String Text = "Hallo, Welt!"; 

  public void paint(Graphics g) 
  { 
    g.drawString(Text, 0, 10); 
  } 
} 

Mit dem Schlüsselwort static machen Sie aus einer Instanzvariablen eine Klassenvariable. Eine Klassenvariable gehört zu einer Klasse, nicht zu einem Objekt. Daraus resultiert folgendes: Zum einen kann eine Klassenvariable auch dann im Programm verwendet werden, wenn es überhaupt kein Objekt von der Klasse gibt. Zum anderen sind Änderungen des Wertes einer Klassenvariablen durch ein Objekt gleichzeitig in allen anderen Objekten dieser Klasse sichtbar. Im Gegensatz zu Instanzvariablen bekommt nicht jedes Objekt seine eigene persönliche Variable, sondern alle Objekte teilen sich die Klassenvariable und greifen auf ein und dieselbe Variable zu.

Auch für Klassenvariablen gilt: Alle Funktionen in einer Klasse haben freien Zugriff.

import java.applet.*; 

public class MyApplet extends Applet 
{ 
  public void paint(Graphics g) 
  { 
    String Text = "Hallo, Welt!"; 
    g.drawString(Text, 0, 10); 
  } 
} 

Die Variablendefinition ist nun von der Klasse in die Funktion paint() gerutscht. Es handelt sich nun um eine lokale Variable. Lokale Variablen sind Variablen, die innerhalb einer Funktion definiert sind. Derartige Variablen sind nur in der Funktion selber sichtbar. Das heißt, während auf eine Instanz- und Klassenvariable jede Funktion einer Klasse zugreifen kann, kann mit einer lokalen Variablen nur innerhalb der Funktion selber gearbeitet werden. Der Gültigkeitsbereich ist also stark eingeschränkt im Gegensatz zu Instanz- oder Klassenvariablen.

Welchen Sinn haben Gültigkeitsbereiche? Programmfehler äußern sich darin, dass Variablen falsche Werte enthalten und am Ende einer Berechnung beispielsweise ein falsches Ergebnis speichern. Um nun herauszufinden, wo das Programm falsch gerechnet hat, müssen also Programmbereiche überprüft werden, die Zugriff auf besagte Variable haben und sie eventuell geändert und damit auf einen falschen Wert gesetzt haben. Je kleiner der Gültigkeitsbereich von Variablen ist, umso weniger Code hat Zugriff auf die Variable, und umso weniger Code kann daran schuld sein, dass in der Variablen ein falsches Ergebnis gespeichert ist. Das bedeutet, die Suche nach fehlerhaftem Code ist umso einfacher, je kleiner der Gültigkeitsbereich von Variablen ist. Daher lautet die Regel: Der Gültigkeitsbereich von Variablen ist grundsätzlich so weit wie möglich einzuschränken.

Gültigkeitsbereiche können Sie übrigens ganz leicht erkennen: Die beiden geschweiften Klammern, zwischen denen die Variablendefinition steht, grenzen den Gültigkeitsbereich ein. Ob die geschweiften Klammern hierbei eine Klasse, eine Funktion oder andere Strukturen in Java definieren, spielt keine Rolle.

Außerhalb von geschweiften Klammern - also beispielsweise vor dem Schlüsselwort class - dürfen in Java keine Variablen definiert werden. Die aus den Programmiersprachen C und C++ bekannten globalen Variablen gibt es in Java nicht.


3.8 Eigenschaften

Instanz- und Klassenvariablen

Ein in der Terminologie der Objektorientierung wichtiger Begriff ist die Eigenschaft. Eine Eigenschaft ist ein Merkmal einer Klasse, also Teil einer Objektbeschreibung. Wenn Sie Klassen erstellen, besteht dieser Vorgang unter anderem darin, dass Sie Eigenschaften festlegen.

Technisch ist eine Eigenschaft nichts anderes als eine Instanz- oder Klassenvariable.

public class Mensch 
{ 
  int Schuhgroesse; 
} 

Im obigen Beispiel sehen Sie eine Klasse namens Mensch. Diese Klasse enthält als einziges Merkmal eine Variable namens Schuhgroesse vom Typ int. Nachdem eine Klasse ja lediglich ein Bauplan, die Beschreibung eines Objekts ist, besitzt ein Objekt basierend auf der Klasse Mensch wie in der Klasse beschrieben eine Variable Schuhgroesse. In der Objektorientierung wird diese Variable als Eigenschaft bezeichnet.

Wenn Sie Baupläne erstellen, also Klassen entwickeln, müssen Sie sich unter anderem überlegen, welche Eigenschaften Sie später in den Objekten benötigen. Wenn Sie also einen Menschen für Ihr Programm beschreiben müssen und eine entsprechende Klasse entwickeln, geben Sie die Eigenschaften an, die für Ihren Menschen in Ihrem Programm relevant sind. Eigenschaften werden hierbei einfach als Variablen zwischen den geschweiften Klammern einer Klasse angegeben - also nicht in Funktionen. Letztere bezeichnet man nicht als Eigenschaften, sondern als ganz normale lokale Variablen.

Ob eine Variable in einer Klasse mit dem Schlüsselwort static definiert wird oder nicht, ist für die Bezeichnung Eigenschaft unerheblich. Sowohl Instanz- als auch Klassenvariablen werden Eigenschaften genannt.


3.9 Aufgaben

Übung macht den Meister

Sie können die Lösungen zu allen Aufgaben in diesem Buch als ZIP-Datei erwerben.

  1. Erstellen Sie eine Java-Application und legen Sie jeweils eine Variable vom Typ boolean, int und String an. Setzen Sie jede Variable auf einen beliebigen Wert. Geben Sie den Inhalt jeder Variablen auf die Standardausgabe aus.

  2. Erstellen Sie eine Java-Application und legen Sie Variablen an, in denen Sie die Informationen false, "Hallo!", 5000 und 16.9 speichern. Geben Sie den Inhalt jeder Variablen auf die Standardausgabe aus.

  3. Erstellen Sie ein Java-Applet und definieren Sie ein Array vom Typ String, in dem Sie drei beliebige Vornamen speichern. Geben Sie mit Hilfe der Funktion paint() die drei Namen im Array in die Applet-Oberfläche aus.

  4. Erstellen Sie ein Java-Applet und definieren Sie eine Eigenschaft vom Typ String. Setzen Sie diese Eigenschaft in der Funktion start() auf einen beliebigen Wert. Geben Sie den Wert der Eigenschaft in der Funktion paint() in die Applet-Oberfläche aus.

  5. Erstellen Sie eine Java-Application und definieren Sie ein Array vom Typ int, in dem Sie fünf beliebige Zahlen unsortiert speichern. Sortieren Sie das Array mit Hilfe der Funktion sort() der Klasse java.util.Arrays und geben Sie die erste und letzte Zahl im Array nach der Sortierung auf die Standardausgabe aus.

  6. Erstellen Sie ein Java-Applet und definieren Sie ein Array vom Typ char. Speichern Sie im Array vom Typ char das Wort "Hallo". Geben Sie mit Hilfe der Funktion paint() den Inhalt des Arrays in die Oberfläche des Applets aus. Verwenden Sie hierzu die Funktion drawChars(), die Sie wie die im Kapitel mehrfach verwendete Funktion drawString() für die Referenzvariable vom Typ java.awt.Graphics aufrufen. Übergeben Sie der Funktion drawChars() in den runden Klammern als ersten Parameter das Array vom Typ char, als zweiten Parameter den Wert 0, als dritten Parameter die Länge Ihres Arrays (Anzahl der Zeichen) und als vierten und fünften Parameter die Koordinatenposition, an der der Inhalt des Arrays in der Applet-Oberfläche ausgegeben werden soll.

  7. Erstellen Sie eine Klasse, um ein Flugzeug zu beschreiben. Flugzeuge besitzen eine bestimmte Anzahl an Turbinen, ein bestimmte Menge an Sitzplätzen und Bordpersonal. Flugzeuge gehören einer bestimmten Fluggesellschaft und besitzen eine maximale Fluggeschwindigkeit. Berücksichtigen Sie diese Informationen beim Erstellen der Klasse Flugzeug und kompilieren Sie Ihren Code anschließend, um ihn auf Fehlerfreiheit zu überprüfen.

  8. Erstellen Sie eine Klasse, um eine Wohnung zu beschreiben. Wohnungen besitzen eine bestimmte Anzahl an Räumen. Es gibt Wohnungen mit Balkon und Wohnungen ohne Balkon. Wohnungen liegen in einem bestimmten Stockwerk. Sie gehören einem Eigentümer und liegen in einer bestimmten Stadt. Berücksichtigen Sie diese Informationen beim Erstellen der Klasse Wohnung und kompilieren Sie Ihren Code anschließend, um ihn auf Fehlerfreiheit zu überprüfen.

  9. Erstellen Sie eine Java-Application und definieren Sie ein Array vom Typ byte, in dem Sie die drei Zahlen 1, 10 und 100 speichern. Weisen Sie die Referenzvariable des Arrays einer zweiten Referenzvariable zu und speichern Sie, indem Sie ausschließlich über die zweite Referenzvariable zugreifen, im Array die Zahlen -1, -10 und -100. Geben Sie anschließend den Inhalt des Arrays auf die Standardausgabe aus, indem Sie über beide Referenzvariablen auf die Zahlen im Array zugreifen.

  10. Ändern Sie Ihre Lösung zur Aufgabe 9 dahingehend, dass Sie der zweiten Referenzvariable nicht mehr die erste Referenzvariable zuweisen, sondern ihr eine Kopie des Arrays übergeben, auf das die erste Referenzvariable verweist. Führen Sie die Java-Application erneut aus und vergleichen Sie die Ausgabe mit den Werten, die Ihre Lösung zur Aufgabe 9 ausgegeben hat.