| » Tutorial / Java Grundlagen / Klassen |
|
Klassen bilden das wichtigste Sprachelement von Java. Aus diesem Grund stellt dieses Kapitel eines der wichtigsten dieses Tutorials dar. Bisher haben wir bei unseren kleinen Beispielprogrammen stets Klassen verwendet, deren genaue Syntax uns bisher unklar war. In diesem Kapitel werden wir alle Dateils der Programmierung eigener Klassen klären. |
|
» Definieren einer Klasse » Instanziierung von Objekten » Konstruktoren » Modifikatoren » Datenelemente » Methoden » Destruktoren » innere Klassen » anonyme Klassen » This » Arrays von Klassen » Instanzbestimmung |
| » Definieren einer Klasse | nach oben « |
|
Zunächst soll geklärt werden, wie man eigene Klassen definiert und was dabei zu beachten ist. Die Definition einer Klasse wird durch das Class - Schlüsselwort eingeleitet, dem ein entsprechender Modifikator vorangestellt ist. Letztlich wird die Klasse noch benannt und im folgenden Block können dann alle benötigten Anweisungen für die Datenelemente und Methoden stehen. |
|
| Syntax | |
[public] class Name
{
// Anweisungen
}
|
|
|
Das Public - Schlüsselwort markiert die Klasse als öffentlich, so dass sie in allen anderen Programmteilen verwendet werden kann. Es darf nur eine öffentliche Klasse pro Datei geben und sowohl der Name der Klasse als auch der Datei müssen identisch sein. Im folgenden Beispiel definieren wir eine einfache Klasse, die jedoch noch keine Elemente beinhaltet. Wir werden diese Klasse später weiter ausbauen. |
|
| Quellcode | |
public class MyClass
{
}
|
|
| » Instanziierung von Objekten | nach oben « |
|
Bei der Instanziierung werden nun feste Objekte von einer Klasse erzeugt und auf dem Speicher angelegt. Jedes neu erzeugte Objekt beinhaltet dabei alle Methoden und Elemente der Klasse, von der das Objekt abstammt. Mehrere Objekte der selben Klasse werden des weiteren getrennt behandelt und haben folglich eigenständige Datenelemente und Methoden. Es besteht also keine direkte Verbindung zwischen den Objekten. Die Ausnahme bilden hier die statischen Klassenelemente, diese sind aber auch nicht auf Objekte bezogen, als vielmehr auf den globalen Klassenzugriff. Syntaktisch erfolgt die Instalziierung eines Objekts durch die Angabe der zu verwendenden Klasse, wobei natürlich ein Bezeichner für das Objekt verwendet werden muss. Bei diesem handelt es sich allerdings nur um eine Referenz auf die eigentliche Speicherstelle. Da alle Klassen in Java dynamisch auf dem Speicher konstruirt werden, muss dem Bezeichner der New - Operator folgen, der das Objekt auf dem Speicher anlegt. |
|
| Syntax | |
Klasse Name;
Name = new Klasse();
|
|
|
Praktisch erzeugen wir nun eine Objekt unserer vorhin definierten Klasse. Beachten Sie, dass unsere zu instanziierende Klasse keine öffentliche Klasse ist, da es davon nur eine geben darf. |
|
| Quellcode | |
class MyClass
{
}
public class MyTest
{
public static void main(String[] args)
{
MyClass object;
object = new MyClass();
}
}
|
|
|
Beide Anweisungen lassen sich bei einer direkten Instanziierung auch zusammenfassen. |
|
| Syntax | |
Klasse Name = new Klasse();
|
|
|
Des weiteren lassen sich an Konstruktoren sogenannte Parameter übergeben. Dabei handelt es sich um Werte von Variablen oder Objekten, mit denen das Objekt initialisiert werden soll. Eben dieser Aufruf wird durch die beiden Klammern nach dem Klassennamen eingeleitet. Wie Konstruktoren nun verwendet werden, wollen wir uns im nächsten Teil ansehen. |
|
| » Konstruktoren | nach oben « |
|
Konstruktoren definieren sich in ihrer Anwendung als spezielle Methoden. Ihre Aufgabe ist es, ein Objekt zu konstruiren und gegebenenfalls durch spezielle Parameter zu initialisieren. Da es sich hierbei um ein recht weites Feld handelt, wollen wir es weiter untergliedern. |
|
|
» Konstruktion eines Objekts » Definition eines Konstruktors » Aufruf » Parameterübergabe » Standardkonstruktor » Konstruktoren überladen » statische Konstruktoren » Abfolge der Konstruktion |
|
|
» Konstruktion eines Objekts Der eigentliche Vorgang läuft stets im Hintergrund ab, ohne das der Programmierer eingreifen muss. Java übernimmt in der Regel selbständig die Konstruktion auf dem Speicher, ohne dabei die Details der Implementierung offenbaren zu müssen. Man selbst kann sich hierbei der Konstruktoren bedienen, die eine Beeinflussung der Wertezuweisung erlauben. » Definition eines Konstruktors Ein Konstruktor trägt zunächst einmal immer den Namen der eigenen Klasse, woran man ihn stets identifizieren kann. Des weiteren hat er keinen Rückgabetyp und ist in seiner Definition optional. Man ist also nicht verpflichtet, einen eigenen Konstruktor zu definieren. Syntaktisch beginnt die Definition über den Klassennamen, dem ein spezifischer Modifikator vorangestellt sein muss. Warum, das werden wir im nächsten Punkt sehen. Die folgenden Klammern beinhalten die Parameterliste, die ebefalls optional ist. Letztlich folgt der Anweisungsblock, der entsprechende Zuweisungen vornimmt. |
|
| Syntax | |
[Modifikator] Konstruktor()
{
// Anweisungen
}
|
|
|
Im folgenden Beispiel definieren wir einen einfachen und noch parameterlosen Konstruktor. Dieser wird unserer internen Variable einen Wert zuweisen, damit diese gültig belegt ist. |
|
| Quellcode | |
class MyClass
{
public MyClass()
{
i = 10;
}
private int i;
}
|
|
|
Als Modifikator haben wir Public gewählt. Was aber wäre, wenn man diesen durch den Private - Modifikator ersetzen würde? Obwohl es erst Thema des nächsten Punktes ist, so wollen wir diesen Zusammenhang bereits jetzt erläutern. Alle als öffentlich deklarierten Elemente einer Klasse sind von außen frei zugänglich. Alle privaten Elemente hingegen nicht. Auf sie kann nur über die eigenen öffentlichen Methoden zugegriffen werden. Deklarieren wir also unseren Konstruktor als Private, so kann von dieser Variante keine Klasse erstellt werden. So lassen sich also bestimmte Konstruktionsmöglichkeiten von vornherein ausschließen. » Aufruf Nachdem wir unseren ersten Konstruktor definiert haben, wollen wir uns ansehen, wie dieser aufgerufen wird. Dazu noch zwei wichtige Anmerkungen. Der Konstruktor ist die Methode, welche bei der Konstruktion als erste aufgerufen wird. Des weiteren hängt die syntaktische Verwendung von der verwendeten Parameteranzahl ab, dazu allerdings mehr im nächsten Punkt. Syntaktisch wird der Konstruktor lediglich durch die runden Klammern bei der Instanziierung gekennzeichnet. Sie sind es auch, die den zu verwendenden Konstruktur spezifizieren. |
|
| Syntax | |
Klasse Objekt = new Klasse();
|
|
|
Im folgenden Beispiel rufen wir den Konstruktor mit leerer Parameterliste auf. Java ist selbständig dazu in der Lage, den passenden Konstruktor zu ermitteln. Vorausgesetzt, es sind mehrere Konstruktoren definiert. Mehr dazu später. |
|
| Quellcode | |
class MyClass
{
public MyClass()
{
i = 10;
}
private int i;
}
public class MyTest
{
public static void main(String[] args)
{
MyClass object = new MyClass();
}
}
|
|
|
» Parameterübergabe An benuterdefinierte Konstruktoren können beliebig viele Parameter übergeben werden. Um dies zu erreichen, muss die Definition des Konstrutkors in der Klasse dafür vorbereitet werden. Folglich wollen wir uns nun die Parameterliste ansehen. Die eben noch leeren Klammern werden wir nun füllen. Um dem Konstruktor mitzuteilen, dass er Parameter übernehmen soll, definieren wir für jeden neuen Parameter den Typ und einen Bezeichner. |
|
| Syntax | |
[Modifikator] Konstruktor(Typ Parameter)
{
// Anweisungen
}
|
|
|
Es können beliebig viele Parameter übergeben werden. Diese müssen lediglich durch ein Komma voneinander getrennt werden. Im folgenden Beispiel definieren wir einen Konstruktor mit zwei Parametern, um eine interne Variable zu initialisieren. |
|
| Quellcode | |
class MyClass
{
public MyClass(int a,int b)
{
i = a*b;
}
private int i;
}
public class MyTest
{
public static void main(String[] args)
{
MyClass object = new MyClass(2,5);
}
}
|
|
|
Der Aufruf erfolgt über den Konstruktor. Dabei werden diesem zwei Werte übergeben. Es ist auch möglich, Objekte oder andere Variablen an den Konstruktor zu übermitteln, anstatt konkreter Werte. Die übergebenen Parameter müssen dabei mit den formal im Konstruktor deklarierten Typen übereinstimmen. Auch die Reihenfolge, in der die verschiedenen Variablen übergenen werden, muss beachtet werden. Differieren die Parametertypen, so führt Java in speziellen Fällen eine Standardkonvertierung durch. » Standardkonstruktor Wie hatten am Anfang bereits einmal darauf hingewiesen, dass Java einen Standardkonstruktor zur verfügung stellt. Dieser ist vollständig parameterlos und wird auch nur dann gestellt, wenn keine eigenen Vorkehrungen getroffen werden. Definiert man allerdings selbst einen Konstruktor, so wird damit automatisch der Standardkonstruktor überschrieben. Auch dann, wenn man selbst nur parametrisierte Virianten programmiert. » Konstruktoren überladen Je umfangreicher eine Klasse wird, umso mehr Konstruktionsmöglichkeiten werden in der Regel benötigt. Daher ist es möglich, wie Methoden auch, die Konstruktoren zu überladen. Dabei müssen sich diese strikt in ihrer Parameterliste unterscheiden. Dies kann einmal durch die Verwendung von unterschiedlichen Datentypen oder einer unterschiedlichen Anzahl von verwendeten Parametern erreicht werden. Ein Beispiel soll dies verdeutlichen. Folglich definieren wir drei Konstruktoren, die sich entweder in ihren Parametertypen oder der Anzahl unterscheiden. Der Aufruf der Konstruktoren bestimmt dann den zu evrwendenden Konstruktor. |
|
| Quellcode | |
class MyTest
{
// Konstruktor 1
public MyTest(int a)
{
i = a;
j = true;
}
// Konstruktor 2
public MyTest(int a,boolean b)
{
i = a;
j = b;
}
// Konstruktor 3
public MyTest(boolean b)
{
i = 3;
j = b;
}
public int i;
public boolean j;
}
public class MyClass
{
public static void main(String[] args)
{
MyTest object1 = new MyTest(1); // Konstruktor 1
MyTest object2 = new MyTest(2,true); // Konstruktor 2
MyTest object3 = new MyTest(false); // Konstruktor 3
System.out.println(object1.i +" "+ object1.j);
System.out.println(object2.i +" "+ object2.j);
System.out.println(object3.i +" "+ object3.j);
}
}
|
|
| Ausgabe | |
1 true
2 true
3 false
|
|
|
» statische Konstruktoren In Java gibt es einen sogenannten statischen Konstruktor. Dieser kann verwendet werden, um statische Variablen mit einem Wert zu initialisieren. Der Konstruktor kann dabei ausschließlich statische Variablen mit einem Startwert vorbelegen, da dieser als erster Konstruktor ausgeführt wird, sobald die Klasse geladen ist. Zu diesem Zeitpunkt gibt es noch keine konkreten Instanzen, auf die der Konstruktor Zugriff hätte. Syntaktisch setzt sich ein solcher Konstruktor nur aus dem Static - Schlüsselwort und einem Anweisungsblock zusammen. Es können auch mehrere statische Konstruktoren pro Klasse existieren. Diese werden dann in der Reihenfolge ihrer Deklaration ausgeführt. Eine Parameterübergabe ist des weiteres ausgeschlossen, da der Konstruktor bereits zur Laufzeit ausgeführt wird. |
|
| Syntax | |
static
{
// Anweisungen
}
|
|
|
Das folgende Beispiel initialisiert alle statischen Variablen einer Klasse durch einen statischen Konstruktor. |
|
| Quellcode | |
class MyClass
{
public static int i;
public static short s;
static
{
i = 10;
s = 20;
}
}
|
|
|
» Abfolge der Konstruktion Die Konstruktion eines Objekts mit allen Initialisiergsarbeiten folgt einer festen Schrittfolge, welche sich wiefolgt zusammensetzt.
|
|
| » Modifikatoren | nach oben « | |||||||||||||||||||||||||
|
Im Laufe unseres Tutorials haben wir bereits des öfteren sogenannte Modifikatoren verwendet, die Elemente einer Klasse in ihrem Charakter verändern können. Java kennt die folgenden Modifikatoren. Bevor wir uns der genauen Bedeutung der einzelnen Modifikatoren widmen wollen, soll noch gesagt werden, dass die Verwendung sich sowohl auf die Sichtbarkeit, Lebensdauer und Veränderbarkeit eines Elements der Klasse auswirkt. Dazu nun mehr in den einzelnen Punkten. » public / protected / private / package scoped Diese Modifikatoren bilden eine gemeinsame Gruppe. Sie können bei allen Elementen einer Klasse auftreten, wobei der letzte Modifikator selbst nie angegeben wird. Er gilt nur als fiktiv, wenn überhaupt kein Modifikator dem Elemtent vorangestellt wurde. Öffentliche Elemente (Public) sind im Rahmen ihrer Lebensdauer überall sichtbar. Alle externen Klassen und Pakete können auf diese Elemente frei zugreifen. Das Schlüsselwort ist auch für die Definition einer Klasse von Bedeutung, denn in diesem Fall markiert es die Klasse als einzige öffentliche ihrer Datei. Somit ist diese Klasse auch in anderen Paketen sichtbar. Private Elemente (Private) sind nur in der eigenen Klasse sichtbar. Von außen besteht keine Möglichkeit des Zugriffs. Geschützte Elemente (Protected) bilden praktisch ein Bindeglied zwischen den beiden vorhergehenden Modifikatoren und sind meist nur im Rahmen von Vererbungshierarchien relevant. Alle derart deklarierte Elemente sind sowohl in der eigenen, als auch in der abgeleiteten Klasse sichtbar. Ein Zugriff besteht auch für Methoden anderer Klassen, die sich jedoch im selben Paket befinden. Andere Pakete hingegen sind ausgeschlossen. Der letzte Modifikator (Package Scoped) gilt immer dann als gesetzt, wenn selbst keine Angaben gemacht wurden. Die hier deklarierte Standardsichtbarkeit macht alle Elemente einer Klasse nur im selben Paket sichtbar. Die hier besprochenen Modifikatoren gelten sowohl für Datenelemente als auch für Methoden und Konstruktoren. Abschließend wollen wie die eben gemachten Angaben über die Zugriffsmöglichkeiten in einer einfachen Tabelle manifestieren. Diese gibt Auskunft über Modifikatoren und Sichtbarkeit. Hier können Sie also ermitteln, wann und wo ein Klassenelement sichtbar ist.
» static Statische Elemente einer Klasse sind nicht an die Existenz eines Objekts gebunden, sondern existieren den gesamten Zeitraum über, in dem die Klasse geladen wurde. Folglich lassen sich derartige Elemente auch nur über die Referenzierung des Klassennamens ansprechen. Als statisch können sowohl Datenelemente als auch Methoden deklariert werden. » final Als Final deklarierte Elemente gelten als konstant und können nachträglich nicht mehr verändert werden. Konstante Methoden einer Klasse dürfen in Ableitungen nicht überlagert werden und als konstant deklarierte Klassen können nicht Teil einer Vererbungshierarchie sein, da fortan keine Klassen mehr von dieser abgeleitet werden dürfen. » transient Alle als transient markierte Elemente einer Klasse markieren diese als nicht persistent. Sie werden beim Vorgang der Serialisierung und Deserialisierung ignoriert. Mehr dazu im entsprechenden Kapitel. » volatile Diese Elemente signalisieren, dass das aktuelle Element asynchron ist, sprich von außerhalb des aktuellen Prozesses verändert werden kann. Daher wird beim Zugriff dieses Element stets neu ausgelesen, anstatt aus Puffern oder dem Prozessorregister neu referenziert zu werden. Auch wenn die Verwendung eher ungebräuchlich ist, so kann dennoch eine Datenintegrität bei Multithreading - Anwendungen sichergestellt werden. Nach diesem theoretischen Abriss der zur verfügung stehenden Modifikatoren werden sich die folgenden Punkte noch einmal individuell auf eventuell vorhandene Sonderfälle spezialisieren. |
||||||||||||||||||||||||||
| » Datenlemente | nach oben « | ||||||||||||||||||||
|
Datenelemente einer Klasse können alle Typen von Variablen sowie weitere Klassen sein. Sie werden in Verbindung mit den entsprechenden Modifikatoren im Rumpf der Klasse definiert. Dabei wollen wir uns folgende Punkte ansehen. |
|||||||||||||||||||||
|
» Definition von Datenelementen » Initialisierung » Modifikatoren » externer Zugriff |
|||||||||||||||||||||
|
» Definition von Datenelementen Die Definition von Datenelementen unterscheidet sich nicht von der bisher kennengelernten Art und Weise. Ein Bezeichner wird über einen Datentyp spezifiziert und kann später mit einem Wert initialisiert werden. |
|||||||||||||||||||||
| Syntax | |||||||||||||||||||||
Typ Name;
|
|||||||||||||||||||||
|
» Initialisierung Alle Datenelemente einer Klasse können auf drei verschiedenen Wegen mit Werten initialisiert werden.
|
|||||||||||||||||||||
| Quellcode | |||||||||||||||||||||
class MyClass
{
public MyClass(int x)
{
a = x;
}
public int a;
public int b = 2;
public int c;
}
public class MyTest
{
public static void main(String[] args)
{
MyClass object = new MyClass(1);
System.out.print(object.a);
System.out.print(object.b);
System.out.print(object.c);
}
}
|
|||||||||||||||||||||
| Ausgabe | |||||||||||||||||||||
120
|
|||||||||||||||||||||
|
» Modifikatoren Im vorhergehenden Punkt hatten wir bereits die möglichen Modifikatoren besprochen, die eine Variable zusätzlich verändern können. Aus diesem Grund finden Sie hier noch einmal eine kurze Zusammenstellung samt Erläuterung.
» externer Zugriff Wie gestaltet sich nun der Zugriff auf ein Element einer Klasse durch einen externen Aufrufer? Zunächst einmal stellt hierbei die Sichtbarkeit den entscheidenden Faktor dar, da nicht auf alle Elemente einfach zugegriffen werden kann. Auf geschützte und private Elemente kann von außen garnicht zugegriffen werden. Gleiches gilt für alle Elemente mit Standardsichtbarkeit. Statische Elemente sind nicht an eine konkrete Instanz gebunden, sind also nur über den Klassennamen erreichbar. Auch hier spielen wieder unsere Modifikatoren eine entscheidende Rolle. Sofern ein Zugriff gestattet ist, stellt sich der syntaktische Abruf recht einfach dar. Der Objektname wird durch den Punktoperator vom gewünschten Element getrennt notiert. |
|||||||||||||||||||||
| Syntax | |||||||||||||||||||||
Objekt.Element;
|
|||||||||||||||||||||
|
Das folgende Beispiel demonstriert das an einer einfachen öffentlichen Variable. |
|||||||||||||||||||||
| Quellcode | |||||||||||||||||||||
class MyClass
{
public int i = 10;
}
public class MyTest
{
public static void main(String[] args)
{
MyClass object = new MyClass();
System.out.print(object.i);
}
}
|
|||||||||||||||||||||
| Ausgabe | |||||||||||||||||||||
10
|
|||||||||||||||||||||
|
Statsische Variablen hingegen werden immer über ihren Klassennamen referenziert, da sie an keine konkrete Instanz gebunden sind. |
|||||||||||||||||||||
| Syntax | |||||||||||||||||||||
Klasse.Element;
|
|||||||||||||||||||||
|
Das folgende Beispiel demonstriert das an einer einfachen öffentlichen Variable. |
|||||||||||||||||||||
| Quellcode | |||||||||||||||||||||
class MyClass
{
public static int i = 10;
}
public class MyTest
{
public static void main(String[] args)
{
System.out.print(MyClass.i);
}
}
|
|||||||||||||||||||||
| Ausgabe | |||||||||||||||||||||
10
|
|||||||||||||||||||||
| » Methoden | nach oben « | ||||||||||||||||||||||
|
Methoden stellen den Dreh- und Angelpunkt in der Interaktion unter den Objekten dar. Sie bilden die Schnittstelle zwischen interner Implementierung und dem Programmierer, der das Objekt verwenden möchte. An dieser Stelle angelangt, wollen wir uns ansehen, wie man eigene Methoden programmiert und verwendet. |
|||||||||||||||||||||||
|
» Definition einer Methode » Aufruf » Parameter » Rückgabewert » Überladen von Methoden » Modifikatoren » statische Methoden » Signatur einer Methode » Klassifikation |
|||||||||||||||||||||||
|
» Definition einer Methode Methoden bilden das Verhalten eines Objekts nach, indem sie bestimmte Aufgaben erfüllen. Sie werden für jede neue Instanz miterzeugt und haben so Zugriff auf alle Elemente ihrer Klasse. Syntaktisch setzt sich die Definition einer Methode aus einem Rückgabetyp, dem Namen der Methode, einer Parameterliste sowie dem Methodenrumpf mit den Anweisungen zusammen. Auch hier kommen wieder die Modifikatoren ins Spiel. |
|||||||||||||||||||||||
| Syntax | |||||||||||||||||||||||
[Modifikator] Typ Methode()
{
// Anweisungen
}
|
|||||||||||||||||||||||
|
Das folgende Beispiel demonstriert die Definition einer Methode, welche eine einfache Meldung auf dem Bildschirm ausgeben kann. |
|||||||||||||||||||||||
| Quellcode | |||||||||||||||||||||||
class MyClass
{
public void MyMethod()
{
System.out.print("ProgrammersBase.NET");
}
}
|
|||||||||||||||||||||||
|
» Aufruf Der Aufruf einer Methode gestaltet sich fast äquivalent zum Aufruf einer Klassenvaraible. Ein möglicher Aufruf hängt natürlich wieder von der Sichtbarkeit ab. Ansonsten ist noch zu beachten, dass beim Aufruf die Parameterliste mit angegeben werden muss, auch wenn die Klammern letztlich leer sind. |
|||||||||||||||||||||||
| Syntax | |||||||||||||||||||||||
Objekt.Methode();
|
|||||||||||||||||||||||
|
Unser jetziges Beispiel verwendet die eben von uns programmierte Methode und gibt über sie eine Meldung auf dem Bildschirm aus. |
|||||||||||||||||||||||
| Quellcode | |||||||||||||||||||||||
class MyClass
{
public void MyMethod()
{
System.out.print("ProgrammersBase.NET");
}
}
public class MyTest
{
public static void main(String[] args)
{
MyClass object = new MyClass();
object.MyMethod();
}
}
|
|||||||||||||||||||||||
| Ausgabe | |||||||||||||||||||||||
ProgrammersBase.NET
|
|||||||||||||||||||||||
|
» Parameter Wir haben die Parameterübergabe bereits bei den Konstruktoren kennengelernt. Das Verfahren ist bei den Methoden äquivalent. Dennoch wollen wir die Parameterübergabe hier noch etwas detaillierter betrachten. Die Methodendefinition wird für eine Parameterübergabe in der Parameterliste vorbereitet. Diese kann mehrere Parameter beinhalten, welche durch je ein Komma voneinander getrennt werden. Jeder formale Parameter wird dabei durch seinen Typ und einen Bezeichner gekennzeichnet. |
|||||||||||||||||||||||
| Syntax | |||||||||||||||||||||||
[Modifikator] Typ Methode(Typ Parameter)
{
// Anweisungen
}
|
|||||||||||||||||||||||
|
Unser Beispiel wird eine an die Methode übergebene Variable auf dem Bildschirm ausgeben. |
|||||||||||||||||||||||
| Quellcode | |||||||||||||||||||||||
class MyClass
{
public void MyMethod(int i)
{
System.out.print(i);
}
}
public class MyTest
{
public static void main(String[] args)
{
MyClass object = new MyClass();
object.MyMethod(10);
}
}
|
|||||||||||||||||||||||
| Ausgabe | |||||||||||||||||||||||
10
|
|||||||||||||||||||||||
|
In diesem Beispiel haben wir einfache Variablen übergeben. Nun wollen wir uns den Fall ansehen, bei dem ein Array an eine methode übermittelt werden soll. Hierbei muss die Markeirung des Datentyps einfach um zwei rechteckige Klammern ergänzt werden. |
|||||||||||||||||||||||
| Syntax | |||||||||||||||||||||||
[Modifikator] Typ Methode(Typ[] Parameter)
{
// Anweisungen
}
|
|||||||||||||||||||||||
|
Unser Beispiel wird ein an die Methode übergebenes Array auf dem Bildschirm ausgeben. Beachten Sie die Art der Ausgabe in Form einer einfachen Schleife, die durch alle Elemente des Arrays iteriert. |
|||||||||||||||||||||||
| Quellcode | |||||||||||||||||||||||
class MyClass
{
public void MyMethod(int[] a)
{
for(int i = 0;i<a.length;i++)
System.out.print(a[i]);
}
}
public class MyTest
{
public static void main(String[] args)
{
int[] array = new int[10];
for(int i = 0;i<array.length;i++)
array[i] = i;
MyClass object = new MyClass();
object.MyMethod(array);
}
}
|
|||||||||||||||||||||||
| Ausgabe | |||||||||||||||||||||||
0123456789
|
|||||||||||||||||||||||
|
Die Demonstration beinhaltet gleich zwei Schleifen. Einmal wird das Array mit den Werten null bis neun belegt und einmal werden alle Elemente der Reihenfolge nach ausgegeben. Bei der Übergabe von Parametern an Methoden und Konstruktoren unterscheidet man zwei Verfahren, je nachdem, ob es sich um gewöhnliche Variablen oder Objekte handelt. Wie wir kennengelernt haben, so handelt es sich bei Objektvariablen lediglich um Referenzen auf die entsprechenden Speicherstellen. Daher werden diese anders behandelt. Folgende zwei Verfahren gibt es.
Alle elementaren Variablen werden stets als Kopie übergeben. Das heißt, dass selbst eine Manipulation dieser Variable für das Original keine Rolle spielt. Anders bei Objekten. Diese werden als Referenz übergeben und beziehen sich daher immer auf das Originalobjekt. Wird das übergebene Objekt manipuliert, so ändert sich auch der Zustand des Originals. Das folgende Beispiel definiert ein einfaches Array mit zehn Elementen. Dieses wird an eine Methode übergeben. Diese Übergabe erfolgt per Referenz, da alle Arrays in Java als Objekte implementiert wurden. Daher wirkt sich Initialisierung der Elemente innerhalb des Methodenrumpfes unmittelbar auf das Original aus, wie bei der Rückkehr zur Hauptfunktion sehr schön zu beobachten ist. |
|||||||||||||||||||||||
| Quellcode | |||||||||||||||||||||||
class MyClass
{
public void MyMethod(int[] a)
{
for(int i = 0;i<a.length;i++)
a[i] = i;
}
}
public class MyTest
{
public static void main(String[] args)
{
int[] array = new int[10];
MyClass object = new MyClass();
object.MyMethod(array);
for(int i = 0;i<array.length;i++)
System.out.print(array[i]);
}
}
|
|||||||||||||||||||||||
| Ausgabe | |||||||||||||||||||||||
0123456789
|
|||||||||||||||||||||||
|
» Rückgabewert Jede Methode kann einen Rückgabewert haben. Dabei handelt es sich um eine Möglichkeit, Ergebnisse an den aufrufenden Prozess zu liefern. Die Methode muss allerdings keinen Wert liefern. Um einen Rückgabetyp zu spezifizieren, wird dieser dem Methodennamen vorangestellt. Möglich sind alle elementaren und benutzerdefinierte Datentypen. Falls kein Rückgabewert existieren soll, so wird dem Methodennamen das Void - Schlüsselwort vorangestellt. Die folgende Liste enthält die möglichen Angaben.
Sofern eine Methode einen Rückgabetyp liefern soll, so muss dies über die Return - Anweisung in der Methode geschehen. Liefert die Methode keinen Typ, so ist diese Anweisung optional. |
|||||||||||||||||||||||
| Syntax | |||||||||||||||||||||||
[Modifikator] Typ Methode(Typ[] Parameter)
{
// Anweisungen
return Wert;
}
|
|||||||||||||||||||||||
|
Unser Beispiel ruft eine Methode auf, welche einen Zahlenwert zurückliefert. Dieser wird dann auf dem Bildschirm ausgegeben. |
|||||||||||||||||||||||
| Quellcode | |||||||||||||||||||||||
class MyClass
{
public int MyMethod()
{
return 10;
}
}
public class MyTest
{
public static void main(String[] args)
{
int i;
MyTest object = new MyTest();
i = object.MyMethod();
System.out.print(i);
}
}
|
|||||||||||||||||||||||
| Ausgabe | |||||||||||||||||||||||
10
|
|||||||||||||||||||||||
|
Selbstverständlich spielt es keine Rolle, ob direkt ein Wert in der Return - Anweisung steht oder ob eine Variable angegeben wird. Wichtig ist nur die Typkompatibilität, da Java nur bedingt eine Standardkonvertierung vornimmt. Auch ist es gestattet, mehrere Return - Anweisungen in einer Methode unterzubringen. Sobald allerdings die erste erreicht ist, bricht auch die Methode ab. Dieses Verhalten kann aber durch Bedingungen kontrolliert werden. Handelt es sich beim Rückgabetyp um eine Klasse, so wird wiederum eine Referenz auf das Originalobjekt kopiert und alle Zuweisungen wirken sich unmittelbar auf das Originalobjekt aus. » Überladen von Methoden Auch das Überladen von Methoden entspricht dem der Konstruktoren. Werden mehrere Methoden mit dem selben Namen definiert, so müssen diese sich eindeutig in ihrer Parameterliste unterscheiden. Gültig sind entweder verschiedene Datentypen der Parameter oder eine unterschiedliche Anzahl von Parametern. |
|||||||||||||||||||||||
| Quellcode | |||||||||||||||||||||||
class MyClass
{
public void MyMethod()
{
System.out.println("ProgrammersBase.NET");
}
public void MyMethod(String s)
{
System.out.println(s);
}
}
public class MyTest
{
public static void main(String[] args)
{
String string = "ProgrammersBase.DE";
MyTest object = new MyTest();
object.MyMethod();
object.MyMethod(string);
}
}
|
|||||||||||||||||||||||
| Ausgabe | |||||||||||||||||||||||
ProgrammersBase.NET
ProgrammersBase.DE
|
|||||||||||||||||||||||
|
» Modifikatoren Auch Methoden lassen sich durch spezielle Modifikatoren beeinflussen. Es kommen des weiteren noch zwei wichtige Typen hinzu, die wir bisher noch nicht erwähnt hatten. Die Modifikatoren für Methoden werden dem entsprechenden Methodennamen mit Rückgabetyp vorangestellt.
An sich verhalten sich alle Modifikatoren zur Methode identisch wie bei den Datenelementen. Statische Methoden sind, wie die entsprechenden Variablen, nicht an eine konkrete Instanz gebunden, sondern werden ebenfalls über den Klassennamen angesprochen. Konstante Methoden verhindern, dass sie in einer Subklasse überschrieben werden können. Dies ist aber erst Thema des nächsten Kapitels, wenn es um die Vererbung und ihre Folgen geht. Diesem Punkt sollen sich auch die abstrakten Methoden anschließen. Letztlich sind noch die nativen und synchronisierten Methoden anzusprechen. Native Methoden sind nicht in Java geschrieben, sondern in verwandten Sprachen wie C++. Diese sollen dennoch in Java verwendet werden. Die synchronisierten Methoden wurden wiederum für das Multithreading von Java entwickelt. Dies ist Thema unserer Java Specials. » statische Methoden Statische Methoden sind vollkommen unabhängig von einem konkreten Objekt. Sie werden nur einmal pro Klasse angelegt und können somit einen globalen Gültigkeitsbereich erlangen. Der Zugriff kann, wie bei den entsprechenden statischen Datenelementen, nur über die Referenzierung der Klasse erfolgen. Syntaktisch wird einer entsprechenden lediglich das Static - Schlüsselwort mit vorangestellt. Der Rest der Definition bleibt wie bei den normalen Methoden identisch. |
|||||||||||||||||||||||
| Syntax | |||||||||||||||||||||||
[Modifikator] static Typ Methode()
{
// Anweisungen
}
|
|||||||||||||||||||||||
|
Natürlich können an die Methode auch Parameter übergeben werden. Der Zugriff erfolgt dann wieder über den Klassennamen. |
|||||||||||||||||||||||
Klasse.Methode();
|
|||||||||||||||||||||||
|
Im folgenden Beispiel definieren wir eine statische Methode, die es uns erlaubt, Textmeldungen auf dem Bildschirm auszugeben. |
|||||||||||||||||||||||
| Quellcode | |||||||||||||||||||||||
class MyClass
{
public static void MyMethod(String s)
{
System.out.println(s);
}
}
public class MyTest
{
public static void main(String[] args)
{
String string = "ProgrammersBase.NET";
MyClass.MyMethod(string);
}
}
|
|||||||||||||||||||||||
| Ausgabe | |||||||||||||||||||||||
ProgrammersBase.NET
|
|||||||||||||||||||||||
|
» Signatur einer Methode Eine Methode setzt sich aus zwei Teilen zusammen. Aus der Signatur und dem Methodenrumpf, welcher die Anweisungen enthält. Die Signatur beinhaltet folgende Angaben.
Dazu ein kleines Beispiel. Die folgende Notation zeigt eine Methode mit mehreren Parametern. Die Signatur setzt sich der Reihenfolge nach aus dem Rückgabetyp, Methodenname und Parametern zusammen. |
|||||||||||||||||||||||
| Methode | |||||||||||||||||||||||
public static void MyMethod(int i,String s); | |||||||||||||||||||||||
| Signatur | |||||||||||||||||||||||
void / MyMethod / int / String
|
|||||||||||||||||||||||
|
» Klassifikation Unser letzter Punkt zum Thema Methoden soll sich mit der Klassifikation beschäftigen. Alle Methoden einer Klasse lassen sich in eine von zwei Kategorien einteilen.
|
|||||||||||||||||||||||
| » Destruktoren | nach oben « |
|
Sie stellen das Gegenstück zu den Konstruktoren dar. Allerdings übernimmt Java selbständig die gesamte Speicherverwaltung in Form des im Hintergrund laufenden Garbage Collectors, weshalb den Destruktoren in Java eine viel geringere Rolle zukommt. Dennoch wollen wir diese kurz ansprechen. Der Destruktor wird immer dann ausgeführt, wenn eine Objektreferenz aus dem Speicher entfernt wird und folglich das Objekt abgebaut werden muss. Der Programmierer kann wichtige Bereinigungsaufgaben an eine spezielle Methode übertragen, die den Standarddestruktor überschreibt. Das Überschreiben hat keine schwerwiegende Folgen, da die Destruktormethode ohnehin leer ist. Syntaktisch ist die Methode exakt vorgeschrieben und muss lediglich in der entsprechenden Klasse nachgebildet werden. |
|
| Syntax | |
protected void finalize()
{
// Anweisungen
}
|
|
|
An dieser Stelle möchten wir Ihnen einen Ansatz für die sinnvolle Nutzung der Destruktormethode liefern. Im folgenden Beispiel zählen wir im Konstruktor jeweils eine Variable hoch, wenn wir eine neue Instanz erzeugen. Der Destruktor wird diese herunterzählen, wenn ein Objekt aus dem Speicher entfernt wird. So haben wir immer die aktuelle Anzahl von den momentan existierenden Instanzen zur Hand. |
|
| Quellcode | |
class MyClass
{
public static int i = 0;
public MyClass() { i++; } // Konstruktor
protected void finalize() { i--; } // Destruktor
public static void MyMethod()
{
System.out.println(i);
}
}
public class MyTest
{
public static void main(String[] args)
{
MyClass object = new MyClass(); // Instanziierung
MyClass.MyMethod();
object = null; // Löschen des Objekts
System.gc(); // Aufruf des Garbage Collectors
MyClass.MyMethod();
}
}
|
|
| Ausgabe | |
1
0
|
|
|
Noch einige Worte zur Erklärung. Zunächst instaniziieren wir ein neues Objekt und geben den aktuellen Status aus. Danach löschen wir das Objekt durch eine Nullzuweisung an die Referenzvariable. Damit merkt der Garbage Collector, dass das Objekt gelöscht werden kann. Dennoch rufen wir diesen danach explizit noch einmal auf. Das hängt damit zusammen, dass die Speicherverwaltung als niedriger Prozess im Hintergrund mitläuft und nicht regelmäßig alle Objekte gleich abbaut. Es ist also eine Frage der Zeit, die wir allerdings durch den expliziten Aufruf lösen. |
|
| » innere Klassen | nach oben « |
|
Definiert man eine Klasse innerhalb des Rumpfes einer anderen Klasse oder sogar Methode, so bezeichnet man diese als innere Klasse. Sie können mit exakt den gleichen Möglichkeiten wie normale Klassen auch bestückt werden. Dabei unterliegen diese Klassen bestimmten Regeln. Innere Klassen sind nur innerhalb ihrer umgebenden Klasse sichtbar und können auch nur dort instanziiert werden. Des weiteren haben sie vollen Zugriff auf die Elemente ihrer umgebenden Klasse und umgekehrt. Der folgende Auszug zeigt die Definition einer inneren Klasse. Wir werden erst im nächsten Punkt detaillierter auf innere Klassen eingehen, da dieses Konzept lediglich de Grundlage für die anonymen Klassen darstellt, welchen eine weit wichtigere Rolle zukommt. |
|
| Quellcode | |
class Outer
{
class Inner
{
}
}
|
|
| » anonyme Klassen | nach oben « |
|
Anonyme Klassen werden in Java sehr oft eingesetzt und vorallem in den zahlreichen Bibliotheken kommt ihnen eine Schlüsselrolle zu. Anonyme Klassen haben selbst keinen Namen, sondern werden in ein und derselben Anweisung definiert und instanziiert. Es handelt sich also um eine Art Einwegklasse. In der Regel handelt es sich um sehr kleine Klassen, die dadurch an Flexibilität gewinnen, weil sie dort eingesetzt werden können, wo sie gebraucht werden. Syntaktisch werden sie durch die Verbindung des New - Operators mit der unmittelbar folgenden Klassendefinition realisiert. Hier ein kurzer syntaktischer Bauplan. |
|
| Syntax | |
new Klasse()
{
// Definition
}
|
|
|
Bevor eine anonyme Klasse überhaupt eingesetzt werden kann, muss diese mindestens einmal deklariert worden sein, da der Compiler ansonsten den Typ der zu verwendenden Klasse nicht ermitteln kann. Das Prinzip der anonymen Klassen basiert darauf, die bestehende Funktionalität einer Klasse an einem lokalen Punkt im Quellcode zu ändern, indem entweder die Variablen oder Methoden der Ausgangsklasse überschrieben und redefiniert werden. Das folgende Beispiel definiert eine Ausgangsklasse, die eine Nachricht liefern soll. Im lokalen Quellcode wird diese Klasse redefiniert, indem eine anonyme Klasse samt neuem Rumpf instanziiert und programmiert wird. Beachten Sie die Änderungen in der Ausgabe mit Blick auf die Originalklasse. Die Redefinition kann an nahezu jedem beliebigen Punkt im Quellcode erfolgen. In diesem Fall geschieht es in der Parameterliste einer statischen Funktion, die sich der Elemente der Nachrichtenklasse bedient. |
|
| Quellcode | |
class MyMessage
{
public String getMessage()
{
return "ProgrammersBase.DE";
}
}
public class MyTest
{
public static void main(String[] args)
{
print(new MyMessage()
{
public String getMessage()
{
return "ProgrammersBase.NET";
}
}
);
}
public static void print(MyMessage m)
{
System.out.print(m.getMessage());
}
}
|
|
| Ausgabe | |
ProgrammersBase.NET
|
|
| » This | nach oben « |
|
Bei This handelt es sich um eine interne Referenzvariable, die immer auf das eigene Objekt verweist. Dieser Zeiger wird automatisch für jedes Objekt generiert, damit die internen Elemente angesprochen werden können. Der This - Zeiger ist nur für nichtstatische Elemente verfügbar, da er an eine konkrete Instanz einer Klasse gebunden ist. Des weiteren besteht die Möglichkeit, über die explizite Angabe des This - Befehls in Verbindung mit dem Punktoperator, die lokalen Elemente einer Klasse auch explizit anzusprechen. |
|
| Syntax | |
this.Element;
|
|
|
Manchmal kann die explizite Angabe zu einer besseren Übersichtlichkeit führen, da die explizite Angabe von This auf Elemente der aktuellen Klasse hinweist. Auch bei Namensgleichheiten verschiedener Elementen einer Klasse hilft die This - Referenzierung weiter. Wird erneut ein Bezeichner in einem Unterblock in der Klasse verwendet, so verweist This immer auf die äußersten Elemente der Klasse. Als Beispiel soll uns hier die Initialisierung von Variablen über den Konstruktor helfen. |
|
| Quellcode | |
class MyClass
{
public MyClass(int i,String s)
{
this.i = i;
this.s = s;
}
private int i;
private String s;
}
|
|
| » Arrays von Klassen | nach oben « |
|
An diesem Punkt angelangt, wollen wir uns noch kurz die Verwaltung von Objekten in Arrays anschauen. Zunächst einmal gestaltet sich bereits die Erzeugung und Initialisierung eines Objektarrays anders als bisher. Erzeugt man ein Array von Objekten, so kann man nicht in der gleichen Anweisung einen speziellen Konstruktor aufrufen. Daher muss bei der Instanziierung ein weiterer Schritt unternommen werden. Als erstes wird ein Array beliebiger Größe angelegt. Die Instanziierung muss dann allerdings für jedes Element einzeln erfolgen, sofern die Objekte im Array unterschiedlich initialisiert werden sollen. Dazu die folgende Demonstration. |
|
| Quellcode | |
class MyClass
{
public MyClass(int i)
{
this.i = i;
}
private int i;
}
public class MyTest
{
public static void main(String[] args)
{
// Schritt 1 - Erzeugung des Arrays
MyClass[] array = new MyClass[2];
// Schritt 2 - Instanziierung der Elemente
array[0] = new MyClass(1);
array[1] = new MyClass(2);
}
}
|
|
|
In diesem Beispiel müssen wir alle Elemente des Arrays einzeln instanziieren, da sie verschiedene Werte erhalten soll. Handelt es sich um sehr große Arrays, so stößt die eben gezeigte Variante sehr schnell an ihre Grenzen. In diesem Fall empfiehlt es sich, die Instanziierung in eine Schleife zu verpacken, die dann für alle Elemente der Klasse den gleichen Konstruktor verwendet. |
|
| Quellcode | |
class MyClass
{
public MyClass(int i)
{
this.i = i;
}
private int i;
}
public class MyTest
{
public static void main(String[] args)
{
// Schritt 1 - Erzeugung des Arrays
MyClass[] array = new MyClass[20];
// Schritt 2 - Instanziierung der Elemente
for(int i=0;i<array.length;i++)
array[i] = new MyClass(0);
}
}
|
|
|
Abschließend wollen wir nun alle Elemente eines Objektarrays wieder ausgeben. Auch hier gilt es, eine Hürde zu nehmen. Das Objekt ist nun in ein Array (ebenfalls ein Objekt) verschachtelt. Daher müssen die korrekten Ebenen zur Ausgabe erst wieder angegeben werden. Den Anfang macht der Name des Arrays, gefolgt vom Index des gewünschten Objekts. Erst jetzt kann auf das auszugebende Element zugegriffen werden, da es sich wiederum im Objekt selbst befindet. |
|
| Quellcode | |
class MyClass
{
public MyClass(int i)
{
this.i = i;
}
public int i;
}
public class MyTest
{
public static void main(String[] args)
{
// Schritt 1 - Erzeugung des Arrays
MyClass[] array = new MyClass[20];
// Schritt 2 - Instanziierung der Elemente
for(int i=0;i<array.length;i++)
array[i] = new MyClass(0);
// Schritt 3 - Ausgabe aller Elemente
for(int i=0;i<array.length;i++)
System.out.print(array[i].i);
}
}
|
|
| Ausgabe | |
0000000000
|
|
| » Instanzbestimmung | nach oben « | |||||||||||||||
|
Unser letztes Thema in diesem Kapitel zeigt Ihnen, wie man ermitteln kann, ob ein Objekt vom Typ einer bestimmten Klasse ist. Dieser Sprachaspekt wird im folgenden Kapitel ebenfalls noch einmal aufgegriffen und detaillierter besprochen, da er vorallem für Klassenhierarchien sehr interessant ist. Hierbei handelt es sich des weiteren um einen binären Operator, der als Argument einmal das zu prüfende Objekt und einmal auf den zu prüfenden Typen übernimmt. Wenn das Objekt tatsächlich vom Typ her übereinstimmt, so liefert der Operator True.
Syntaktisch wird der Operator wie folgt verwendet. |
||||||||||||||||
| Syntax | ||||||||||||||||
Objekt instanceof Klasse;
|
||||||||||||||||
|
Das folgende Beispiel erzeugt eine neue Klasse und ermittelt, ob diese vom angegebenen Typ ist. Trifft diese Bedingung zu, so wird der entsprechende boolesche Wert ausgegeben. |
||||||||||||||||
| Quellcode | ||||||||||||||||
class MyClass
{
}
public class MyTest
{
public static void main(String[] args)
{
MyClass object = new MyClass();
System.out.print(object instanceof MyClass);
}
}
|
||||||||||||||||
| Ausgabe | ||||||||||||||||
true
|
||||||||||||||||
| « Kapitel | Kapitelübersicht | nach oben | Kapitel » |