6  Objektorientierte Programmierung

In diesem Kapitel werden die Grundlagen der Objektorientierten Programmierung beschrieben und ein Einstieg in Java geliefert. Fortgeschrittene LeserInnen, die schon Erfahrung in der Java-Programmierung haben und durch die Lektüre dieses Buches lediglich den Umgang mit leJOS und dem EV3 erlernen wollen, können wie folgt vorgehen:

Sowohl der EV3 als auch der verwendete Computer sind an dieser Stelle vollständig eingerichtet und es können die EV3-Beispielprogramme aus Kapitel 7, Kapitel 8, Kapitel 10 sowie aus Kapitel 11 übertragen und ausgeführt werden. Das Nachvollziehen dieser Beispielprogramme und die Beschreibung der verfügbaren leJOS-Klassen in Kapitel 10 liefern eine gute Grundlage um mit der Programmierung eigener Roboter zu beginnen.

pict
Das im Internet kostenlos einsehbare Buch Galileo Openbook »Java ist auch eine Insel« kann als allgemeine Java-Grundlektüre empfohlen werden. Auch wenn sich »Java ist auch eine Insel« nicht konkret mit leJOS und dem EV3 beschäftigt, werden hier wichtige Grundlagen für die Programmierung mit Java anschaulich und leicht verständlich beschrieben. [17]

6.1  Allgemeines zur Objektorientierung

Mit dem Begriff »Objektorientierung« wird eine Art der Modellbildung in der Softwareentwicklung beschrieben. Unter dieser ist, sehr vereinfacht ausgedrückt, die Abstraktion der realen Welt und der Transfer dieses abstrahierten Modells auf eine Maschine zu verstehen. Es ist dennoch weiterhin möglich, prozedural (Aufteilung von Aufgaben und Teilproblemen in Prozeduren) zu programmieren, wie es zum Beispiel in C gemacht wird. Das Konzept der Objektorientierung (kurz: OO) bietet viele Vorteile, die die Entwicklung von Software von Grund auf verändert und erleichtert. Dabei wird versucht auf menschliche Strukturierungs- und Klassifizierungsmethoden zurückzugreifen.

Der Objektorientierung liegt eine Aufteilung der zu beschreibenden Welt in Objekte mit ihren Eigenschaften und Operationen zugrunde. Realisiert wird diese Aufteilung durch das Konzept der Klasse, bei dem Objekte aufgrund ähnlicher Eigenschaften zusammengefasst und nach außen gekapselt werden. Die Struktur eines Objekts wird durch die Attribute (auch Eigenschaften) seiner Klassendefinition festgelegt. Das Verhalten des Objekts wird von den Methoden der Klasse bestimmt.

6.1.1  Vorteile der Objektorientierung

Ein Vorteil des auf der Objektorientierung beruhenden Programmierparadigmas, der Objektorientierten Programmierung (kurz: OOP), stellt die Möglichkeit dar, Programmteile wiederzuverwenden. Konsistent entwickelte Programmteile können in verschiedenen Konfigurationen und verschiedenen logischen Zusammenhängen genutzt werden. Ein weiterer Vorteil ist, dass das zu erzeugende Programm in übersichtliche Programmstücke geteilt werden kann. Deren Erstellung und Wartung lassen sich dadurch besser und transparenter strukturieren.

Die unterschiedlichen Programmteile sind somit unabhängig voneinander und können einzeln und von verschiedenen Personen entwickelt werden. Um von den beschriebenen Vorteilen zu profitieren, werden die vier folgenden Konzepte genutzt:

Diese vier Konzepte werden in Kapitel 7 beschrieben.

6.2  Grundsätzliche Strukturen der OOP

Bevor auf diese Konzepte eingegangen werden kann, werden die grundsätzlichen Strukturen der objektorientierten Programmierung vorgestellt.

6.2.1  Klassen

Wie eingangs erwähnt, lehnt sich das Konzept der OOP stark an Strukturierungs- und Klassifizierungsmethoden aus der alltäglichen (menschlichen) Betrachtungsweise unserer realen Welt an. Das folgenden Beispiel soll dies verdeutlichen:

Der Begriff PKW stellt eine Klassifizierung von motorisierten Vehikeln dar, die (meist vierrädrig) zur Personenbeförderung von A nach B dienen. Der Begriff legt dabei aber weder die Anzahl der Türen, noch Farbe, Form, Hubraum etc. des PKWs fest.

Ein ähnliches, auf die Thematik dieses Buches bezogenes Beispiel bietet der Begriff Roboter:

Als Roboter wird im herkömmlichen Sinn eine technische Apparatur bezeichnet, die dazu konzipiert wurde, Aufgaben des Menschen zu übernehmen, um diesen zu entlasten. Dabei können viele verschiedene Arten beispielsweise im Aussehen unterschieden werden: humanoider Roboter, Industrieroboter, mobiler Roboter etc. Auch weitere Eigenschaften wie z.B. Einsatzgebiet, Kosten, Aufgabe oder die Achsenanzahl sind nicht definiert.

pict
Eine interessante Übersicht mit Links zu Robotikanwendungen sortiert nach Einsatzgebieten liefert: https://robots.zeef.com/roberta.roboter0
Diese Art der Strukturierung greift die objektorientierte Programmierung durch die Verwendung von Klassen und Objekten auf. Auf die Beispiele angewandt, werden die Klassen PKW und Roboter angelegt, um festzulegen welche Attribute ein später erzeugtes Objekt der Klasse besitzen soll. Das Objekt stellt dann mit konkreten Attributwerten etwas tatsächlich Existierendes dar, z.B. den gelben VW Käfer von Herrn Müller oder den Industrieroboter der Automobilfirma Ford.

Folgendes Beispiel zeigt, wie in Java die Klasse ExampleRobot definiert wird.


Programm 6.1: ExampleRobot-Klasse
 
1public class ExampleRobot { 
2    //variables: 
3    public String name; 
4    public double price; 
5    public int yearOfPurchase; 
6    public String appearance; 
7 
8    //methods: 
9    public int age(int currentYear) { 
10        return currentYear - yearOfPurchase; 
11    } 
12}

Durch das Schlüsselwort class wird die Klassendefinition begonnen. In den geschweiften Klammern können beliebig viele Variablen und Methodendefinitionen erfolgen. Die Variablen (auch Attribute einer Klasse genannt) speichern die Eigenschaften der Klasse – hier Bezeichnung (name) , Kosten (price), Kaufjahr (yearOfPurchase) und Aussehen (appearance). Die Methoden definieren die Fähigkeiten bzw. das Verhalten von Objekten. Im Beispiel kann über die Methode age(currentYear) das Alter des entsprechenden Roboters zurückgegeben werden. Methoden werden innerhalb von Klassen angelegt und können auf die Klassen-Attribute zugreifen. Der sogenannte Modifier public und weitere werden unter dem Punkt Kapselung in Kapitel 7 erklärt.

pict
Da Software häufig von internationalen Teams entwickelt wird, bezeichnet man Variablen sowie Methoden und verfasst Quelltextkommentare auf Englisch. Wir schließen uns dieser Praxis an, im Text werden aber dennoch die deutschen Übersetzungen mit angegeben.

6.2.2  Objekte

Ein Objekt (oder auch die Instanz1) ist eine konkrete Ausprägung einer Klasse. Wird ein Objekt von einer Klasse erzeugt, so erhält es alle Attribute und Methoden, die in dieser Klasse enthalten sind. Des Weiteren haben alle Objekte drei wichtige Eigenschaften:

Die aktuellen Werte der Variablen eines Objekts bilden seinen Zustand. Dieser kann sich im Verlauf eines Programms durchaus verändern. Das Verhalten eines Objekts, wird durch seine Methoden gebildet. Sie definieren unter anderem, wie das Objekt seinen oder den Zustand anderer Objekte verändern kann. Darüber hinaus besitzt jedes Objekt intern eine eigene, unveränderliche Identität. Auch wenn zwei Objekte einer Klasse den gleichen Zustand haben und gleiches Verhalten zeigen, sind es dennoch unterschiedliche Objekte mit unterschiedlichen Identitäten.

6.2.3  Erzeugen eines Objekts

Für das Erzeugen eines Objekts muss eine Variable vom Typ der Klasse (hier ExampleRobot) deklariert werden. Mit dem =-Operator wird dieser Variable dann ein neu erzeugtes Objekt zugewiesen. Die Erzeugung des Objekts erfolgt mit Hilfe des new-Operators, gefolgt vom Klassennamen mit zwei runden Klammern (also ExampleRobot()). Dieser Ausdruck wird Default-Konstruktor genannt und in Abschnitt 6.3 beschrieben.


Programm 6.2: Erzeugen des Objekts ”myEV3”
 
1public class ExampleRobot { 
2    public String name; 
3    public double price; 
4    public int yearOfPurchase; 
5    public String appearance; 
6 
7    public int age(int currentYear) { 
8        return currentYear - yearOfPurchase; 
9    } 
10 
11    public static void main(String[] args) { 
12        ExampleRobot myEV3; 
13        myEV3 = new ExampleRobot(); 
14    } 
15}

Die Erzeugung des Objekts erfolgt hier in der sogenannten main-Methode. Diese stellt das Hauptprogramm in einer Klassendatei dar und wird ebenfalls in Abschnitt 6.3 beschrieben.

pict
Die Deklaration einer Variable und das Zuweisen eines Objekts kann auch kombiniert in einer Zeile erfolgen:
ExampleRobot myEV3 = new ExampleRobot();
Im letzten Beispielprogramm, gibt es ein Objekt der Klasse ExampleRobot. Dieser konkrete Roboter (myEV3) hat die allgemeinen Attribute der Klasse: Bezeichnung (name), Kosten (price), Kaufjahr (yearOfPurchase) und Aussehen (appearance). Aber er besitzt noch keine Ausprägungen, also einen undefinierten Zustand. Im Beispielprogramm 6.3 wird nun folgender Zustand hergestellt:

Dies geschieht durch Verwendung des .-Operators, mit dessen Hilfe auf Variablen und Methoden eines Objekts zugegriffen werden kann.


Programm 6.3: Initialisieren der Objektvariablen
 
1public class ExampleRobot { 
2    public String name; 
3    public double price; 
4    public int yearOfPurchase; 
5    public String appearance; 
6 
7    public int age(int currentYear) { 
8        return currentYear - yearOfPurchase; 
9    } 
10 
11    public static void main(String[] args) { 
12        ExampleRobot myEV3; 
13        myEV3 = new ExampleRobot(); 
14 
15        myEV3.name = "LEGO EV3"; 
16        myEV3.price = 319.95; 
17        myEV3.yearOfPurchase = 2013; 
18        myEV3.appearance = "Roberta"; 
19    } 
20}

6.3  Generelle Programmstrukturen

Jedes Programm benötigt eine grundlegende Struktur. Genau wie in anderen Programmiersprachen ist diese Struktur fest vorgegeben und muss unbedingt beachtet werden. Dieses Kapitel soll einen kurzen Überblick über die verschiedenen Elemente geben, die in einer Klasse vorkommen müssen.

6.3.1  Dateistruktur

Die Programmstruktur eines leJOS-Programms gestaltet sich recht einfach. Es sollte darauf geachtet werden, einige Vorgaben und Konventionen einzuhalten. Es ist üblich in jeder Datei eine öffentliche Klasse (die mit Schlüsselwort public deklariert wird) zu erstellen. In der Datei können sich dann beliebig viele weitere Helferklassen befinden, die aber jeweils nicht öffentlich sind. Der Dateiname entspricht dem Klassennamen der öffentlichen Klasse. Die Klasse ExampleRobot wird also auch in der Datei ExampleRobot.java abgespeichert.

6.3.2  Paketimport

Jedes Java-Programm nutzt bestimmte Bibliotheken. Darin sind die Befehle beschrieben, die bei der Programmierung eingesetzt werden sollen. Bei der Programmierung mit leJOS müssen die Klassen für Sensoren und Motoren etc. aus den jeweiligen Packages eingebunden werden, damit der Programmcode von Java verstanden und richtig umgesetzt werden kann. Möchte man die Klasse KLASSE aus dem Package PACKAGE einbinden, so funktioniert dies nach folgendem Grundprinzip:

import PACKAGE.KLASSE;

Soll beispielsweise die Klasse »EV3ColorSensor« aus dem Package »lejos.hardware.sensor« zur Ansteuerung des EV3-Farbsensors verwendet werden, so lautet der Befehl dafür:

import lejos.hardware.sensor.EV3ColorSensor;

Möchte man direkt mehrere Klassen aus einem Package einbinden, zum Beispiel die leJOS-Klassen zur Ansteuerung aller Sensoren, wird der *- Operator verwendet:

import lejos.hardware.sensor.*;

Eclipse unterstützt die EntwicklerInnen beim Einbinden der Klassen durch einen Hinweis, falls Klassen verwendet werden sollen, die noch nicht eingebunden sind. Durch einen einfachen Linksklick auf den entsprechenden Hinweis fügt Eclipse die benötige import-Zeile automatisch zum Programmcode hinzu.

6.3.3  Klassendefinition

Nach den Paketimporten wird die eigentliche Klasse definiert. Mit den Schlüsselwörtern public class <Klassenname> wird eine neue öffentliche Klasse angelegt. In unserem Beispiel ist dies:

public class ExampleRobot

Danach beginnt der Klassenrumpf, der von geschweiften Klammern umschlossen wird (siehe zum Beispiel Programm 6.3).

6.3.4  Die main-Methode

Ist das Grundgerüst geschaffen, bedarf es nun noch der Hauptfunktion die den Namen main trägt. Auch diese Methode ist vom Typ public und da sie eine Klassenmethode ist, bekommt sie noch das Schlüsselwort static. In unserem Beispiel ist dies:

public static void main(String[] args)

Der main-Funktion muss immer ein Stringarray (also eine Feldvariable vom Typ String) übergeben werden. Übergeben wird das Feld args[], in dem auf dem ersten Platz args[0] der Klassenname selber steht. Auf den folgenden Plätzen finden sich die Startparameter. In der Hauptfunktion main kann nun das gesamte Programm untergebracht, andere Objekte erstellt oder Threads (siehe Abschnitt 8.3) gestartet werden. Ist die main- Funktion abgearbeitet, endet automatisch auch die Programmausführung auf dem EV3. Ist eine Klasse jedoch nur dazu da, Objekte von ihrem Typ zu instanziieren, verfügt sie nicht über eine main-Methode. Stattdessen existiert dort ein sogenannter Konstruktor.

6.3.5  Konstruktor

Der Konstruktor wird bei jeder Instanziierung eines Objekts aufgerufen. Er kann als eine Art spezielle Methode ohne Rückgabewert bezeichnet werden. Diese Methode ist fest mit der Klasse verbunden und trägt ihren Namen. Mit der Ausführung des Konstruktors können beispielsweise Initialwerte gesetzt oder Zählvariablen erhöht werden. Aufgerufen wird der Konstruktor, wenn ein Objekt mit dem Schlüsselwort new erzeugt wird (siehe Programm 6.4). Damit wird der Konstruktor für jedes Objekt genau einmal aufgerufen. Ein nachträglicher oder erneuter Aufruf ist nicht möglich. Neben Konstruktoren gibt es in Java auch Destruktoren, die vor dem Zerstören eines Objekts aufgerufen werden. Da Java über eine automatische Speicherverwaltung verfügt, wird den Destruktoren in diesem Buch keine weitere Bedeutung beigemessen.

Im nächsten Beispielprogramm werden die Variablen des Roboters myEV3 nicht in der main-Methode, sondern in einem eigens dafür erstellten Konstruktor initialisiert.


Programm 6.4: ExampleRobot mit Konstruktor
 
1/∗∗ 
2 Example Robotclass. 
3 Object is being created in mainmethod 
4 and variables are initialized with a specific constructor. 
5/ 
6public class ExampleRobot { 
7    public String name; 
8    public double price; 
9    public int yearOfPurchase; 
10    public String appearance; 
11 
12    public int age(int currentYear) { 
13        return currentYear - yearOfPurchase; 
14    } 
15 
16    ExampleRobot(String name, double price, int yearOfPurchase, String appearance) { 
17        this.name = name; 
18        this.price = price; 
19        this.yearOfPurchase = yearOfPurchase; 
20        this.appearance = appearance; 
21    } 
22 
23    public static void main(String[] args) { 
24        ExampleRobot myEV3; 
25        myEV3 = new ExampleRobot("LEGO EV3", 319.95, 2013, "Roberta"); 
26    } 
27}

Beim Erstellen des Objekts mit dem new-Operator werden dem Konstruktor die Variablenwerte als Parameter, getrennt durch Kommata, übergeben. Bei der Konstruktordefinition kann dann angegeben werden, was mit den so übergebenen Werten geschehen soll. In unserem Fall werden Sie den Variablen des Objekts zugewiesen. Damit es dabei nicht zu Verwechslungen zwischen Parameternamen und Variablennamen kommt, werden letztere durch den sogenannten this- Pointer gekennzeichnet. Dieser verweist immer auf das Objekt, zu dem die Methode (oder der Konstruktor) gehört.

6.3.6  Default-Konstruktor

Wird beim Anlegen einer Klasse kein expliziter Konstruktor angegeben, erzeugt der Compiler automatisch einen Default-Konstruktor, der ohne die Angabe von Parametern aufgerufen wird.

6.3.7  Java Codekonventionen

Die Java Codekonventionen stellen Regeln in Bezug auf die Form von Java Quelltext dar. Es ist ratsam sich an diese Regeln zu halten, da man sich so als Entwickler schneller in fremden Code einfinden kann und die Lesbarkeit erhöht wird. Die von Java herausgegebene Dokumentation zu den Codekonventionen kann unter http://www.oracle.com/technetwork/java/codeconvtoc-136057.html angesehen werden. Im folgenden werden die wichtigsten Konventionen vorgestellt:

6.3.8  Kommentare und Javadoc

In Java gibt es, wie in den meisten Programmiersprachen, mehrere Möglichkeiten seinen Quellcode mit Kommentaren zu versehen. Die Einfachste ist der sogenannte Zeilenkommentar. Dabei wird an beliebiger Stelle im Quelltext durch Einfügen von // ein Kommentar eingeleitet. Alle Zeichen die danach in dieser Zeile stehen werden nicht vom Compiler übersetzt. Der Kommentar endet mit der Zeile.

Die zweite Möglichkeit ist die Verwendung von sogenannten Blockkommentaren. Diese beginnen mit /* an beliebiger Stelle im Quelltext und enden mit */. Alle Zeichen dazwischen werden vom Compiler ignoriert. Blockkommentare können sich auch über mehrere Zeilen erstrecken.

pict
Kommentare können auch dazu verwendet werden um Programmteile zu Testzwecken auszukommentieren und damit nicht mit zu compilieren. Im Gegensatz zum Löschen der Programmzeilen können sie dann einfach wieder einkommentiert werden, falls sie wieder verwendet werden sollen.
Das Kommentieren von Quelltexten ist ein oft vernachlässigter, aber wichtiger Bestandteil der Softwareentwicklung, da Code in der Regel häufiger gelesen als geschrieben wird. Vor allem wenn Programmkomponenten anderen Entwicklern zur Verfügung gestellt werden sollen, müssen diese gut dokumentiert sein. Das Erstellen einer externen Dokumentation zum Quelltext wurde früher manuell gemacht. Dies war sehr aufwendig und fehleranfällig, da alle Änderungen im Quellcode auch in der Dokumentation umgesetzt werden mussten.

Heutzutage werden solche externen Dokumentationen automatisch, meistens im HTML-Format, erstellt. Bei Java übernimmt diese Aufgabe das Programm »Javadoc«. Dazu werden vor jeder Klasse, jedem Interface, jedem Konstruktor, jeder Methode und jedem Attribut spezielle Dokumentationskommentare verwendet. Diese beschreiben das entsprechende Element und werden von Javadoc in die Dokumentation umgewandelt. Dokumentationskommentare sind eine Sonderform der Blockkommentare. Sie beginnen mit /** und enden mit */. Zeilen dazwischen beginnen meist mit einem *, was den Kommentar optisch aufwertet aber keine weitere Funktion erfüllt.

Der erste Satz jedes Dokumentationskommentars wird in der Zusammenfassung der Methoden und Attribute angezeigt. Dieser erste Satz sollte nach Möglichkeit die Funktionalität schon komplett beschreiben. Der gesamte Dokumentationskommentar wird dann in der Detailansicht aufgeführt. Es sollte bei der Beschreibung der Methoden eher angegeben werden, was die Methode macht, als wie sie es macht. Ebenso sollten Parameter und Rückgabewerte angegeben werden. Dazu werden sogenannte »Tags« benutzt, mit denen eine Vielzahl von Informationen innerhalb eines Dokumentationskommentars an »Javadoc« übergeben werden kann. Eine Übersicht der wichtigsten Tags liefert Tabelle 6.1.

Tag

Beschreibung

Verwendung

@param

Beschreibung eines Parameters

@param Parametername Beschreibung

@return

Beschreibung des Rückgabewerts

@return Beschreibung

@see

Verweis auf ein anderes Packet/Methode/Attribut

@see Methodenname

@exception/ @throws

Angabe der Exception, die die Methode werfen kann

@exception Exceptionname

@author

Angabe des Autors

@author Autorenname

@deprecated

Gibt an, dass die Methode veraltet ist und nicht mehr verwendet werden sollte

@deprecated Beschreibung

Tabelle 6.1.: Javadoc Tags

In Eclipse kann eine Javadoc-Dokumentation über File Export und Auswahl von »Javadoc« in der Kategorie »Java« angelegt werden. Um die Verwendung einer solchen Dokumentation zu verdeutlichen, wurde Beispielprogramm 6.4 vollständig mit Javadoc-Kommentaren versehen:


Programm 6.5: ExampleRobot mit Konstruktor (Javadoc)
 
1/∗∗ 
2 Example Robotclass. 
3 Object is being created in mainmethod 
4 and variables are initialized with a non defaultconstructor. 
5/ 
6public class ExampleRobot { 
7    /∗∗ 
8      Name of the robot 
9     / 
10    public String name; 
11    /∗∗ 
12      Price of the robot 
13     / 
14    public double price; 
15    /∗∗ 
16      Year the robot was bought in 
17     / 
18    public int yearOfPurchase; 
19    /∗∗ 
20      Appearance of the robot 
21     / 
22    public String appearance; 
23    /∗∗ 
24      Returns the age of the robot. 
25      @param currentYear The current year 
26      @return The age of the robot 
27     / 
28    public int age(int currentYear) { 
29        return currentYear - yearOfPurchase; 
30    } 
31    /∗∗ 
32      Constructor, that initializes the instancevariables with given parameters 
33      @param name Name of the robot 
34      @param price Price of the robot 
35      @param yearOfPurchase Year the robot was bought in 
36      @param appearance Appearance of the robot 
37     / 
38    ExampleRobot(String name, double price, int yearOfPurchase, String appearance) { 
39        this.name = name; 
40        this.price = price; 
41        this.yearOfPurchase = yearOfPurchase; 
42        this.appearance = appearance; 
43    } 
44    /∗∗ 
45      mainmethod. Object is created here. 
46      @param args standard parameter for mainmethod 
47     / 
48    public static void main(String[] args) { 
49        ExampleRobot myEV3; 
50        myEV3 = new ExampleRobot("LEGO EV3", 319.95, 2013, "Roberta"); 
51    } 
52}
pict
Die resultierende Javadoc-Dokumentation kann unter www.roberta-home.de/javaband-ev3/examplerobot-doc.html angesehen werden.

6.4  Variablen und Methoden

6.4.1  Variablen

Die grundsätzliche Funktionsweise von Variablen unterscheidet sich nicht von der anderer Programmiersprachen. Eine Übersicht über die Datentypen, mit denen Variablen belegt werden können und die leJOS bereitstellt, gibt Tabelle 6.2. Im Folgenden soll der Unterschied zwischen

deutlich werden.

Abhängig von der Art, wie eine Variable deklariert wird, ist sie entweder eine Instanz- (instance-), eine Klassen- (class-) oder eine lokale Variable (local variable). Eine Instanzvariable ist, wie der Name andeutet, eine Variable, die nur dem gemeinsam mit ihr erzeugten Objekt zur Verfügung steht. Eine Klassenvariable hingegen steht allen Objekten einer Klasse zur Verfügung. Werden also, wie in dem Beispiel beschrieben, einige Objekte von einer Klasse erzeugt, so erhalten sie die Eigenschaften aus dieser Klasse. Obwohl es in der Klasse nur ein Attribut appearence (Aussehen) gibt, kann jedes Objekt (also jeder konkrete Roboter) diesen Wert individuell beschreiben und verändern, ohne damit die anderen Objekte zu beeinflussen.

Soll aber zum Beispiel erfasst werden, wie viele Objekte der Klasse ExampleRobot erzeugt wurden, so benötigt die Klasse einen Zähler, auf den alle Objekte zugreifen können. Dieser Zähler ist dann eine Klassenvariable: Der Inhalt der Variable ist für alle Instanzen gleich. Eine solche Variable wird bei Deklaration durch das Schlüsselwort static gekennzeichnet.

Lokale Variablen werden innerhalb einer Methode definiert und existieren auch nur dort. Das Beispielprogramm 6.6 zeigt die verschiedenen Variablendeklarationen und die Verwendung einer Klassenvariablen.


Programm 6.6: Variablendeklaration
 
1/∗∗ 
2 Example Robotclass. Illustrates the definition of different variabletypes. 
3/ 
4public class ExampleRobot { 
5    /∗∗ 
6     Classvariable for counting the total number of created objects. 
7    / 
8    private static int robotCount = 0; 
9 
10    //declaration of instance variables 
11    public String name; 
12    public double price; 
13    public int yearOfPurchase; 
14    public String appearance; 
15 
16    /∗∗ 
17     Constructor that increases the classvariable robotCount by 1 upon objectcreation. 
18    / 
19    ExampleRobot() { 
20        robotCount = robotCount + 1; 
21    } 
22 
23    public int age(int currentYear) { 
24        //declaration of a local variable 
25        int time = currentYear - yearOfPurchase; 
26        return time; 
27    } 
28}

6.4.2  Methoden

Methoden geben einer Klasse ihre Fähigkeiten und ihr Verhalten. Konkrete Aufgaben wie Berechnungen können übersichtlich in einzelne Methoden gefasst werden. Eine Methode hat immer einen Rückgabewert und optional auch Eingabeparameter, mit denen einer Methode Werte übergeben werden können.

Methoden sind entweder objekt- oder klassenbezogen. Klassenmethoden werden durch das Schlüsselwort static deklariert. Diese Klassenmethoden können innerhalb oder außerhalb der Klasse aufgerufen werden. Durch den sogenannten Modifier werden die Zugriffsregeln auf die Methode bestimmt.

Der Modifier private erlaubt nur den Zugriff von der eigenen Klasse. Durch den Modifier public können beliebige andere Klassen auf die Methode zugreifen. Eine vertiefende Beschreibung der Modifier und ihrer Verwendung befindet sich unter dem Punkt Kapselung in Kapitel Kapitel 7.

Methoden können mit Übergabeparametern angelegt werden. Diese Parameter werden dann beim Aufruf direkt mit angegeben und stehen in Klammern nach dem Methodennamen. Der Rückgabewert der Methode wird mit return an den Aufruf zurückgegeben. Ein Beispiel für einen solchen Übergabeparameter haben wir bereits in der Klasse ExampleRobot gesehen. Der Methode age wird das aktuelle Jahr (int currentYear) als Integer übergeben und so mit Hilfe des Kaufjahres (yearOfPurchase) das Alter des Roboters berechnet. Dieser Wert wird dann mittels return zurückgegeben.

Das nachfolgende Beispielprogramm zeigt einige Methoden mit verschiedenen Modifiern, Übergabeparametern und Rückgabewerten. Zur anschaulichen Darstellung werden die Rückgabewerte auf dem Display des EV3 ausgegeben. Dabei werden vorab Methoden der Klassen LCD und Button verwendet, die eigentlich erst in Kapitel 10 ab Seite 233 beschrieben werden.

LCD.drawString(...)
gibt einen String, der als erster Parameter übergeben wird, auf dem Display aus. Die beiden anderen Parameter geben die Stelle auf dem Display an, an der der Anfang des Strings erscheinen soll (Spalte, Zeile).
Button.ENTER.waitForPress()
wartet mit der Programmausführung solange, bis die ENTER-Taste (mittlere Taste auf dem EV3-Stein) gedrückt wurde. Diese Methode wird verwendet, damit die Ausgabe auf dem Display für einige Zeit bestehen bleibt. Bei Programmende kehrt der EV3 nämlich zum Hauptmenü zurück und die Ausgabe des Programms verschwindet.


Programm 6.7: MethodTester
 
1import lejos.hardware.Button; 
2import lejos.hardware.lcd.LCD; 
3 
4/∗∗ 
5 MethodTesterclass. 
6 Implements different methods. 
7/ 
8public class MethodTester { 
9    /∗∗ 
10     1. method: Returns a string 
11    / 
12    private String returnString() { 
13        return "Example string"; 
14    } 
15    /∗∗ 
16     2. method: squares the given value 
17    / 
18    public static int square(int value) { 
19        return value*value; 
20    } 
21    /∗∗ 
22     3. method: implements "value" power "exponent" 
23    / 
24    public double power(double value, double exponent) { 
25        return Math.pow(value,exponent); 
26    } 
27    /∗∗ 
28     4. method: implements abc 
29    / 
30    public int product(int a, int b, int c) { 
31        return a*b*c; 
32    } 
33 
34    public static void main (String args[]) { 
35        MethodTester methods = new MethodTester(); 
36 
37        LCD.drawString("1. "+methods.returnString(),0,0); 
38        LCD.drawString("2. "+MethodTester.square(15),0,1); 
39        LCD.drawString("3. "+methods.power(3.5,7),0,2); 
40        LCD.drawString("4. "+methods.product(3,4,5),0,3); 
41 
42        Button.ENTER.waitForPress(); 
43    } 
44}

Das Programm 6.7 »MethodTester.java« führt zu folgender Ausgabe auf dem EV3-Display:

pict

Abbildung 6.1.: Ausgabe des MethodTester-Programms

6.5  Ausdrücke, Anweisungen und Blöcke

6.5.1  Ausdrücke

Ausdrücke liefern bei ihrer Auswertung ein Ergebnis zurück. Sie bestehen aus Variablen, Operatoren und Methodenaufrufen. Einfache Beispiele geben die arithmetischen Operatoren (»+«, »«, »«, »«), die zusammen mit zwei Operanden einen Ausdruck bilden. Der Datentyp des Ergebnisses hängt vom Typ der verwendeten Operanden ab.

Der »+«-Operator hat in Java neben der arithmetischen Verknüpfung zweier Zahlen noch eine weitere sehr praktische Funktion: Das Zusammenfügen von zwei Strings. Der Ausdruck “Ich bin ein “ + “String“ liefert damit den String “Ich bin ein String“ zurück. Dabei konvertiert der »+«- Operator auch Zahlenwerte aus Variablen zu Strings. Der Ausdruck “Der Wert der Variable intValue lautet: “ + intValue liefert einen String zurück, der den aktuellen Wert der Variable intValue angibt.

Auch die logischen Operatoren »==« (gleich), »!=« (ungleich), »>« (größer), »<« (kleiner), »>=« (größer-gleich) und »<=« (kleiner-gleich) bilden mit zwei Operanden einen Ausdruck. Bei Auswertung liefert dieser einen booleschen Wert (true oder false) zurück, der den Wahrheitsgehalt des Ausdrucks angibt.

6.5.2  Anweisungen

Anweisungen bestehen aus Ausdrücken und bilden die kleinste ausführbare Einheit eines Computerprogramms. Es gibt drei verschiedene Arten von Anweisungen.

Ausdrucksanweisungen sind Ausdrücke die lediglich durch Abschluss mit einem Semikolon zu einer Anweisung werden. Dazu gehören beispielsweise Funktionsaufrufe oder Wertzuweisungen. Bei einer Wertzuweisung mit dem »=«-Operator wird der Ausdruck intValue = 3 durch Abschluss mit einem Semikolon zu einer Anweisung. Es handelt sich dabei ohne Semikolon um einen Ausdruck, da der Wert auch zurückgeliefert wird.

Weiter gibt es Kontrollflussanweisungen, die in Abschnitt 6.6 beschrieben werden und Deklarationsanweisungen. Bei diesen handelt es sich um jede Art von Variablendeklarationen, also zum Beispiel: int intValue;. Dabei wird dem Programm die Existenz einer Variable bekannt gemacht, bevor mit ihr gearbeitet wird. Deklaration und Zuweisung können auch in einer Anweisung kombiniert werden: int intValue = 0;.

6.5.3  Blöcke

Ein Block ist eine Gruppierung von mehreren Anweisung mit Hilfe geschweifter Klammern. Der Beginn eines Blocks wird durch »{«, das Ende eines Blocks durch »}« gekennzeichnet. Ein Block kann überall da verwendet werden, wo auch einzelne Anweisungen erlaubt sind. Ein Beispiel findet sich in der Syntax der if- else-Anweisung auf Seite 132.

6.6  Kontrollstrukturen

Kontrollstrukturen sind Anweisungen, die den Programmfluss steuern, also die Reihenfolge beeinflussen, in der Anweisungen ausgeführt werden. Eine Kontrollstruktur ist entweder eine Verzweigung (if-else und switch) oder eine Schleife (while, do-while und for).

6.6.1  if-else-Anweisung

Die if-Struktur wird verwendet, um Programmverzweigung aufgrund einer Bedingung zu realisieren. Nur wenn die Bedingung zutrifft, werden die Anweisungen nach dem Schlüsselwort if abgearbeitet, ansonsten werden die Anweisungen nach else abgearbeitet. Das folgende Flussdiagramm in Abbildung 6.2 erläutert die allgemeine Programmverzweigung der if-Struktur.

pict

Abbildung 6.2.: Flussdiagramm der if-Struktur

Die Java-Syntax zur Implementierung einer if-Abfrage lautet:

if (condition) { 
    statement; 
    
} 
else { 
    statement; 
    
}

Mehrere if bzw. if-else-Anweisungen können beliebig verschachtelt werden. Hierzu wird eine zusätzliche if-Anweisung in den Anweisungsblock von else geschrieben. Es ist natürlich auch möglich, nur die if-Anweisung (ohne den else-Teil) zu nutzen.

6.6.2  switch-Anweisung

Im Gegensatz zur Zweifachverzweigung der einfachen if-else-Struktur, können mit dem switch-statement Mehrfachverzweigungen im Programmfluss erstellt werden.

pict

Abbildung 6.3.: Flussdiagramm des switch-case-statements

Dazu wird zunächst der Wert einer Variablen oder eines Ausdrucks evaluiert und je nach Ergebnis unterschiedliche Anweisungen ausgeführt. Die Syntax des switch-statements lautet wie folgt:

switch (expression){ 
    case result1: 
        statement; 
        
    case result2: 
        statement; 
        
    case resultN: 
        statement; 
        
    default: 
        statement; 
        
}

Der zu evaluierende Ausdruck (meist eine Variable) wird in Klammern hinter dem Schlüsselwort switch angegeben. Im darauffolgenden Switch-Block können mittels case für beliebig viele Ergebnisse dieses Ausdrucks unterschiedliche Anweisungen angegeben werden. Ist ein Ergebnis im Switch-Block nicht zu finden, werden die Anweisungen hinter default ausgeführt.

Wie in Diagramm 6.3 zu erkennen ist, werden – falls nicht anders behandelt – für ein bestimmtes Ergebnis nicht nur die dafür angegebenen Anweisungen ausgeführt, sondern auch alle darauf folgenden Anweisungen anderer Ergebnisse. Das liegt daran, dass mit case und default lediglich Sprungziele im Switch-Block für den Programmfluss definiert werden. Nach einem Sprung zur dem Ergebnis entsprechenden Stelle wird der Switch-Block dann regulär Zeile für Zeile abgearbeitet.

In der Regel sollen die Anweisungen anderer Ergebnisse nicht mit ausgeführt werden. Daher muss der Switch-Block nach jeder Sequenz von Anweisungen für ein Ergebnis explizit mittels break verlassen werden.

Dieses Verhalten des switch-Statements ist durchaus gewünscht und kein Fehler. So ist es nämlich möglich, durch Weglassen des break, für mehrere Ergebnisse die gleichen Anweisungen auszuführen. Man spricht dann auch vom sogenannten »fall through«.

6.6.3  while-Schleife

Die while-Schleife wird verwendet, um Wiederholungen von bestimmten Anweisungen auszuführen, solange (engl. while) eine Bedingung erfüllt ist (siehe Abbildung 6.4). Die while-Schleife wird auch kopfgesteuerte Schleife genannt. Es wird zunächst die Bedingung im Schleifenkopf geprüft, ist diese erfüllt (true), werden die Anweisungen im Schleifenrumpf ausgeführt. Dies wiederholt sich, bis die Bedingung nach einer Ausführung des Schleifenrumpfs nicht mehr erfüllt ist.

pict

Abbildung 6.4.: Flussdiagramm der while-Schleife

Die Java-Syntax zur Implementierung einer solchen while-Schleife lautet wie folgt:

while (condition) { 
    statement; 
    
}

6.6.4  do-while-Schleife

Soll der Anweisungsblock mindestens einmal ausgeführt werden, empfiehlt sich die Verwendung der do-while-Schleife. Bei ihr wird im Programmverlauf zuerst die Anweisung ausgeführt und danach geprüft, ob die Bedingung noch gültig ist. Daher wird die do-while-Schleife als fußgesteuerte Schleife bezeichnet.

pict

Abbildung 6.5.: Flussdiagramm der do-while-Schleife

Die Java-Syntax zur Implementierung einer do-while-Schleife lautet::

do { 
    statement; 
    
} while (condition)

6.6.5  for-Schleife

Braucht man in einer wiederholenden Struktur einen Schleifenzähler, empfiehlt es sich, statt der while-Schleife eine for-Schleife zu verwenden.

pict

Abbildung 6.6.: Flussdiagramm der for-while-Schleife

Die Initialisierung (engl. initialization) wird ausgeführt bevor die for-Schleife das erste Mal durchlaufen wird. An dieser Stelle wird die Zählvariable erstellt. Danach folgt die Bedingung (engl. condition), die vor jedem Durchlauf der Schleife geprüft wird. Liefert sie true wird der nächste Durchlauf gestartet. Nach jedem Durchlauf wird der sogenannte Fortschaltausdruck (for statement) ausgeführt. Dieser verändert die Zählvariable auf gewünschte Art und Weise, kann aber theoretisch jede beliebige Anweisung enthalten.

Die for-Struktur sieht wie folgt aus:

for(initialization; condition; statement){ 
    statement; 
       
}

Eine mögliche for-Schleife, im Laufe derer die Zählvariable von 0 bis 8 erhöht wird, sieht dann beispielsweise so aus:

for(int i = 0; i <= 8; i++){ 
    statement; 
       
}

Die Integer-Variable i wird zu Beginn mit dem Wert 0 initialisiert. Die Bedingung i<=8 bedeutet, dass die Anweisungen solange wiederholt werden, wie i kleiner oder gleich 8 ist. Der Ausdruck i++ (gleichbedeutend mit i=i+1) erhöht den Wert der Zählvariable nach jeder Ausführung des for-Blocks um 1. Alternativ können so auch Schleifen konstruiert werden, deren Zählvariablen kleiner werden.

6.6.6  Schleifensteuerung

Manchmal ist es nötig eine Schleife vorzeitig zu beenden oder eine Schleifeniteration an einer bestimmten Stelle abzubrechen und mit der nächsten Iteration zu beginnen. Dazu stehen dem Programmierer drei Befehle zur Verfügung:

break beendet die Schleife komplett. Die Programmausführung wird nach dem Schleifenrumpf, also ausserhalb der Schleife, fortgesetzt. Meist wird dieser Befehl innerhalb einer Schleife in Verbindung mit if-Abfragen eingesetzt, um eine oder mehrere Abbruchbedingungen für die Schleife zu erstellen.
continue beendet die aktuelle Schleifeniteration und setzt die Programmausführung im Schleifenkopf (bzw. Schleifenfuß bei do-while-Schleifen) fort. Bei while- und do-while-Schleifen wird mit dem Testen der Schleifenbedingung fortgefahren. Bei for-Schleifen wird zuerst der Fortschaltausdruck ausgewertet und dann die Schleifenbedingung getestet. Das Programm springt daraufhin bei allen drei Schleifen (falls die Schleifenbedingungen wahr sind) wieder in den Schleifenrumpf.
return haben wir bereits kennengelernt. Schleifen können damit verlassen werden, wenn sie sich innerhalb von Methoden befinden, die nicht void sind, also einen Rückgabewert liefern müssen. Es wird der hinter return angegebene Wert an den Methodenaufruf zurückgeliefert, die Methode dadurch beendet und damit auch die Schleife innerhalb der Methode abgebrochen.
pict
Zu beachten ist, dass sich break und continue nur auf den Block beziehen in dem sie aufgerufen werden. Bei verschachtelten Schleifenstrukturen laufen die äußeren Schleifen unverändert weiter, wenn in der Innersten ein break oder continue aufgerufen wird.

6.7  Datentypen

leJOS unterstützt alle Datentypen, die auch von Java unterstützt werden. In Tabelle 6.2 sind die wichtigsten von Java unterstützten Datentypen aufgeführt. Aus Performancegründen sind die primitiven Datentypen (char, byte, short, int, long) nicht in echten Klassen definiert, sondern in sogenannten Wrapper-Klassen. Wrapper-Klassen sind Klassen, die den entsprechenden Datentyp in ein Objekt »einpacken«. Hinzu kommt, dass es nicht, wie in anderen Programmiersprachen, unsigned-Typen gibt. Positive und negative Wertebereiche werden implizit deklariert.

Art

Name/ Klasse

Datentyp

Größe

Wertebereich

Binär

Boolean

boolean

1 Bit

true oder false

Ganzzahl

Byte

byte

8 Bit

27 bis 27 1

Ganzzahl

Short

short

16 Bit

215 bis 215 1

Ganzzahl

Integer

int

32 Bit

231 bis 231 1

Ganzzahl

Long

long

64 Bit

263 bis 263 1

Fließkommazahl

Float

float

32 Bit

±1,4E-45±3,4E+38

Fließkommazahl

Double

double

64 Bit

±4,9E-324±1,7E+308

Zeichen

Char

char

16 Bit

Zeichen

String

String

max. 231 1 Zeichen

Tabelle 6.2.: Wertebereiche von Datentypen

1 Instanz und Objekt werden in der Literatur oftmals synonym verwendet, wir schließen uns diesem Gebrauch innerhalb dieses Bandes ebenfalls an.