Inhalt
Allgemeine Java-Kontrollstrukturen
Hier wollen wir uns einmal kurz um die "Basics" kümmern und vergleichen, wie werden die einzelnen
Elemente von Java in Delphi (Object Pascal) dargestellt.
Wir gehen dazu kurz die meisten Themen des Grundkurses durch und nehmen dazu immer Bezug auf die einzelnen
Steps zum vertiefen. Algorithmen und vertiefendere Themen der OOP werden dabei allerdings nur ansatzweise
angegangen.
Um sowohl den Java- als auch Delphi-Quellcode etwas kompakt zu halten, habe ich mich dazu entschlossen das
Ganze jeweils in ein einziges Projekt bzw. bei Java eben in eine einzige Datei zu packen.
Um das trotzdem etwas übersichtlich zu halten, packe ich alles thematisch in Methoden, die auch die
Überschrift hier wiederspiegelt. Alles zum Thema Variablen ist somit in der Methode variablen().
Um das ganze noch etwas lesbarer zu machen, was da überhaupt steht, kommentiere ich im Original-Source
die entsprechenden Teile.
Wie erwähnt, bleiben wir bei Java rudimentär bei einer Konsolen-Anwendung. Um allerdings nun bereits den
Mehrwert von Delphi kennenzulernen, werden wir hier die VCL nutzen und eine neue VCL-Anwendung erstellen.
Dann bitte folgende Schritte durchführen um den Source-Code wie folgt auch mitmachen zu können:
- Rechts in der Palette suchen wir unter Standard ein Button und setzen ihn ins Formular oben rein.
- Links im Objektinspektor können wir den Button unter Caption mit einem Titel besetzen, wie z.B. Ausführen... (da wir ja den Beispiel-Code ausführen wollen).
- Da wir ja nicht sofort eine Konsole haben, mit der wir bei Java ja unser Standard-Output haben, brauchen wir auch hier eine Komponente, die diese Infos darstellen kann. Dazu nutzen wir ein Memo. Hier auch wieder rechts in der Palette das Memo suchen und möglichst maximal in das Formular einfügen. Das heißt entweder wieder per Drag & Drop oder Doppelklick das Memo einfügen und anschließend an den Ecken entsprechend größer ziehen.
-
Links im Objektinspektor müssen wir nun noch zwei Punkte anpassen:
- Zum einen der Inhalt: Einmal ein Doppelklick auf den Inhalt bei Lines (dort steht gerade "(Strings)").
- Zum anderen könnte es sein, dass irgendwann mehr dargestellt wird, so dass wir Scrollen können müssen. Die Eigenschaft heißt ScrollBars. Hier einmal ssBoth setzen.
-
Und nun zum Schluss, brauchen wir ja auch eine Stelle, in der wir den Source eingeben können.
Dazu klicken wir einfach doppelt auf den Button. Delphi erzeugt uns sofort die Ereignismethode
Button1Click und führt uns in die Unit-Ansicht.
Tipp: F11 und F12 kannst du sehr oft nutzen, da es dich immer wieder zwischen dem Formular und dem dazugehörigen Quellcode wechselt. Bei F11 wird der Fokus zusätzlich auf den Objektinspektor gelegt. - Um nun weitermachen zu können fehlt noch eine entscheidende Info zu Methoden in Delphi. Generell kann nur verwendet werden, was in irgendeiner Weise über seinem Aufruf deklariert ist. Das heißt, die Button1Click-Prozedur ist immer unten, wenn du zum Beispiel eine procedure variablen; erstellen möchtest.
Um den letzten Punkt etwas einfacher zu fassen, hier kurz ein Quellcode-Auszug dazu als Beispiel:
// Dies ist ein einzeiliger Kommentar! { ... und Achtung: dies ist ein mehrzeiliger Kommentar ! } (* ... aber dies ist auch ein mehrzeiliger Kommentar! *) // und los geht's mit dem Beispiel: // ... implementation procedure variablen; var // hier deklarieren wir die Variablen begin // hier können die Variablen genutzt werden... // auf das Memo kann wie folgt zugegriffen werden: Form1.Memo1.Lines.Add('Hallo Memo'); end; // Weitere neue prozeduren dann z.B. hier procedure TForm1.Button1Click(Sender: TObject); begin // Hier wird die Logik implementiert: variablen(); // ... weitere Methoden-Aufrufe dann hier end; end. |
Unter GitHub befindet sich der komplette Sourcecode, sowohl als Java- wie auch als
Delphi-Source.
Variablen
Variablen sind ja schon ziemlich das Elementarste was in Programmen genutzt wird, um Daten zu speicher, zu nutzen
oder mit ihnen irgendwas zu machen, wie bspw. Berechnungen. Daher schauen wir uns einmal die Rudimentärsten
Datentypen an und prüfen, welche Bereiche diese erfüllen. Im Original-Source habe ich in Java die Namen der
Delphi-Datentypen gleich reingesetzt um da auch etwas übersichtlich die Datentypen zu zeigen.
Umgekehrt ist es entsprechend im Delphi-Source. Der hier dargestellte Sourcecode ist mit reduzierten Kommentaren
dargestellt. Die Vollversion dazu steht unter Github ;)
// Nur zur Vollständigkeit: Dies ist ein einzeiliger Kommentar ;) /* ... und dies ein mehrzeiliger Kommentar */ private static void variablen() { byte b = 42; System.out.println("Min Byte = " + Byte.MIN_VALUE); // -128 System.out.println("Max Byte = " + Byte.MAX_VALUE); // 127 short s = 420; System.out.println("Min Short = " + Short.MIN_VALUE); // -32768 System.out.println("Max Short = " + Short.MAX_VALUE); // 32767 int i = 42000; System.out.println("Min Short = " + Integer.MIN_VALUE); // -2147483648 System.out.println("Max Short = " + Integer.MAX_VALUE); // 2147483647 long l = 4200000000L; System.out.println("Min Short = " + Long.MIN_VALUE); // -9223372036854775808 System.out.println("Max Short = " + Long.MAX_VALUE); // 9223372036854775807 // Deklaration und Initialisierung eines Strings: String str = "Hallo Welt!"; System.out.println("str = " + str); // str = Hallo Welt! } |
Wie oben schon erwähnt, sind manche Namen in Delphi etwas anders. Bei Delphi werden Variablen,
die nur in der Methode genutzt werden sollen, alle
direkt oben im Methoden-Kopf (Über begin) deklariert und zwar nach dem Schema
"Variablenname Doppelpunkt Datentyp". Hier können somit alle "lokalen" Variablen deklariert werden und
alles was man braucht ist das Schlüsselwort var. Es genügt das Schlüsselwort var nur einmal direkt unter
die Methodendeklaration zu schreiben um den Bereich zu definieren, in dem man dann beliebig viele Variablen
deklarieren kann. Zudem läuft in Delphi die Zuweisung etwas anders:
Das wird durch := anstatt dem einfachen = durchgeführt. Das einfache = ist in Delphi der Vergleich, wie
== in Java.
procedure variablen; var b : Byte; s : SmallInt; i : Integer; long : Int64; str : String; begin b := 42; Form1.Memo1.Lines.Add('Min Byte = ' + Byte.MinValue.ToString); Form1.Memo1.Lines.Add('Max Byte = ' + Byte.MaxValue.ToString); s := 420; Form1.Memo1.Lines.Add('Min SmallInt = ' + SmallInt.MinValue.ToString); Form1.Memo1.Lines.Add('Max SmallInt = ' + SmallInt.MaxValue.ToString); i := 42000; Form1.Memo1.Lines.Add('Min Integer = ' + Integer.MinValue.ToString); Form1.Memo1.Lines.Add('Max Integer = ' + Integer.MaxValue.ToString); long := 4200000000; Form1.Memo1.Lines.Add('Min Int64 = ' + Int64.MinValue.ToString); Form1.Memo1.Lines.Add('Max Int64 = ' + Int64.MaxValue.ToString); str := 'Hallo Welt'; Form1.Memo1.Lines.Add('str = ' + str); end; |
Variablen sind ausführlicher im
Step 04 dargestellt
(neuer Tab/Fenster).
Fragen kannst du gerne via Mail, oder auch Instagram oder Twitter stellen. Letzteres bietet auch anderen die
Möglichkeit zu antworten, da ich nicht stetig in die Apps reinschaue und so vielleicht auch mal ein, zwei
Tage deine Frage nicht lese.
if-Statement
Einfache Entscheidungen können in Java mit dem if eingeleitet werden. Hier ein kurzer Auszug:
private static void ifStatements() { int i = 3; if (i == 3) { System.out.println("i ist tatsächlich 3!"); System.out.println("Mehrzeilige Anweisung im if braucht klammern"); } else if (i > 10) // einfache Anweisung kann ohne Klammern auskommen. System.out.println("i ist größer als 10!"); else System.out.println("i kleiner oder gleich 10, aber nicht 3!"); } |
Das if-Statement wird in Delphi, ohne Überraschung, auch mit if eingeleitet. Delphi arbeitet allerdings bei
der Untergliederung nicht mit geschweiften Klammern, sondern nutzt hier die Schlüsselworte
"begin" und "end", wie wir oben in den Beispielen auch für die Procedure bereits sehen
konnten.
procedure IfStatements(); var i: Integer; begin if i = 3 then begin Form1.Memo1.Lines.Add('i ist tatsächlich 3!'); Form1.Memo1.Lines.Add('Mehrzeilige Anweisung im if braucht begin und end'); end // man beachte hierbei: KEIN Semikolon! else if i > 10 then // auch in Delphi können begin und end wegfallen: Form1.Memo1.Lines.Add('i ist größer als 10!') // man beachte hierbei auch: KEIN Semikolon! else Form1.Memo1.Lines.Add('i kleiner oder gleich 10, aber nicht 3!'); end; |
if-Statements sind ausführlicher im
Step 09 dargestellt
(neuer Tab/Fenster).
case-Statement
Das case kann verwendet werden um bestimmte bekannte Fälle vorzuentscheiden und etwas übersichtlicher
umsetzen zu können, als mit vielen if-else-Verzeigungen.
private static void caseStatement() { int x = 13; switch (x) { case 10: System.out.println("x ist 10"); break; case 13: System.out.println("x ist 13"); break; default: System.out.println("x ist weder 10 noch 13!"); } } |
Im Vergleich zwischen Delphi und Java fällt schnell auf, dass es bei Delphi kein break benötigt, da es von
vornherein nur ein Statement oder ein Block, der mit begin...end eingegrenzt wurde, ausführt und damit
verhindert, dass versehentlich etwas ausgeführt wird, was ein fehlendes break in Java ausgeführt hätte.
Wieso hebe ich das so hervor? Nunja, am Besten wäre hier sowieso eine Verzweigung in eigene Methoden, die
dann entsprechend die Logik abbilden. Somit könnte auch ein solches Verhalten in Delphi "simuliert" werden,
indem eine Methode y(), die durch ein fehlendes break nach dem Aufruf von x() hätte aufgerufen werden
können (also gedanklich gerade in Java), aber so auch direkt in x aufgerufen werden, was den Programmfluss
viel übersichtlicher werden lässt.
procedure CaseStatement(); var x : Integer; begin x := 13; case x of 10 : begin Form1.Memo1.Lines.Add('x ist 10'); end; // Delphi kennt kein break; // Ausführung wird mit begin...end abgerenzt. 13 : Form1.Memo1.Lines.Add('x ist 13'); else Form1.Memo1.Lines.Add('x ist weder 10 noch 13'); end; end; |
case-Statements sind ausführlicher im
Step 10 dargestellt
(neuer Tab/Fenster).
for-Statement
Die Zählschleife for ist, kurz angesprochen, in Java etwas komplexer gelöst, als in Delphi. In Java können
hier in der Standard-For-Schleife zunächst die Variable deklariert und initialisiert werden, dann ein
boolscher Vergleich angegeben werden und anschließend eine Ausführung, wie z.B. das Hochzählen der
deklarierten Laufvariable. Das folgende Beispiel ist die Generierung und Ausgabe der Quadratzahlen von 1
bis 10:
private static void forStatement() { for (int i = 1; i <= 10; i++) { System.out.println(i + " im Quadrat ist " + (i*i)); } } |
In Delphi ist die For-Schleife recht übersichtlich. Sie benötigt eine im Methodenkopf deklarierte
Zählvariable, die dann vom Startwert (hier 1) bis zum Zielwert (hier 10) hochgezählt wird.
Anders als bei Java kann man die Schritte, die hochgezählt werden nicht direkt beeinflussen.
Wer das benötigt, kann eine zweite Variable deklarieren, die entsprechend die Zählvariable multipliziert.
Es folgt das gleiche Beispiel nun in Delphi:
procedure ForStatement(); var i : Integer; begin for i := 1 to 10 do Form1.Memo1.Lines.Add(Integer.ToString(i) + ' im Quadrat ist ' + Integer.ToString(i*i)); end; |
for-Statements sind ausführlicher im
Step 11 dargestellt
(neuer Tab/Fenster).
while-Statement
While-Schleifen sind kopfgesteuert, das heißt, bevor überhaupt irgendwas ausgeführt wird in dessen Rumpf
muss die im Schleifenkopf definierte Bedingung erfüllt sein.
private static void whileStatement() { int i = 107; while (i > 42) { i -= 13; System.out.println("i ist " + i); } } |
Im Vergleich mit Java hat man in Delphi genauso eine Bedingung anzugeben. Der generelle Aufbau ist bis auf
das reservierte Wort "do" im Grunde gleich.
procedure WhileStatement(); var i : Integer; begin i := 107; while i > 42 do begin i := i - 13; Form1.Memo1.Lines.Add('i ist ' + Integer.ToString(i)); end; end; |
while-Statements sind ausführlicher im
Step 12 dargestellt
(neuer Tab/Fenster).
do-while-Statement / repeat-Statement
Anders als die While-Schleife wird in der Do-While-Schleife (bzw. der repeat-Schleife) die Entscheidung,
ob noch ein weiterer Lauf durchgeführt wird, am Ende, also am Fuße des Statements durchgeführt.
Diese ist also eine Fußgesteuerte Schleife.
Die folgende Beispiel-Routine soll nochmal durchlaufen werden, so lange wie der generierte
Zufallswert einen Werte kleiner oder gleich 90 ist.
private static void doWhileStatement() { String msg = "Und los geht's!"; int x; do { System.out.println(msg); x = (int) Math.round(Math.random() * 100); System.out.println("x ist " + x); msg = "Noch eine Runde?!"; } while (x <= 90); // *** System.out.println("do...while wurde verlassen!"); } |
Nun zum Vergleich die entsprechend angepasste Routine in Delphi. Ich gehe danach nochmal genauer darauf ein.
Vergleicht mal die Position mit dem Kommentar "***".
procedure RepeatUntilStatement(); var x : Integer; msg : String; begin msg := 'Und los geht''s!'; repeat Form1.Memo1.Lines.Add(msg); x := Random(100); // zufälligen Integer-Wert zwischen 0 und 100 Form1.Memo1.Lines.Add('x ist ' + Integer.ToString(x)); msg := 'Noch eine Runde?!'; until x > 90; // *** Form1.Memo1.Lines.Add('repeat...until wurde verlassen!'); end; |
Der Kommentar "***" markiert den Boolschen Ausdruck, der auch das ende der Fußgesteuerten Schleife definiert.
Hier sieht man die deutlichen Unterschiede in der Logik. Während in Java dem Wortlaut nach
"mache...so lange(dieses wahr ist)" die Logik abhandelt, ist in Delphi entsprechend
"wiederhole...bis(dieses nicht mehr wahr ist)", wobei in Delphi eigentlich keine Klammern benötigt werden.
repeat-Statements sind ausführlicher im
Step 13 dargestellt
(neuer Tab/Fenster).
Einfache Datenstrukturen
Bevor wir in die objektorientierte Programmierung reinschauen, gehören einige Datenstrukturen zum
Allgemeinwissen und daher hier in den "Überblick". Datenstrukturen sind ein wichtiger Bestandteil um
gewisse Algorithmen gut oder noch besser implementieren zu können.
Bei einem Sortieralgorithmus, der mittels "sortieren durch Einfügen" agiert, ist es ungemein wichtig zu wissen, dass ein Array in Java anders organisiert
ist, als in Delphi. Arrays in Delphi werden lediglich in ihrer Größe verändert, wenn sie Daten hinzubekommen
oder man Daten entfernen möchte. In Java benötigt man dafür eine neue "Objektinstanz" und muss daher
alle bestehenden Werte kopieren. Nun will ich hier nicht eine Debatte loslösen, welche Sortieralgorithmen
es gibt oder gar ihr Pro und Kontra argumentieren - es geht nun los mit den Datenstrukturen.
Wir starten mit der Datenstruktur "Array", gefolgt von einer Implementierung des Listen-Objektes. Um
mittels Key-Value-Paaren, ähnlich dem Dictionary arbeiten zu können, schauen wir danach auf die
HashMap. Abgerundet wird dieser Abschnitt mit einer sog. Enumeration - der Aufzählung.
private static void datastructureBasics() { //# Array System.out.println("Array:"); // -> Deklaration int[] intArray = new int[10]; // -> Initialisierung mit 1: Arrays.fill(intArray, 1); // -> Element bei Position 5 lesen System.out.println(intArray[5]); // -> Element bei Position 8 schreiben intArray[8] = 13; // -> Element 3 entfernen int[] tmpArray = new int[9]; for (int i = 0; i < intArray.length-1; i++) { tmpArray[i] = intArray[ (i<3) ? i : i+1]; } intArray = tmpArray; // -> Alle Elemente ausgeben Arrays.stream(intArray).forEach(System.out::println); //# ArrayList System.out.println("ArrayList:"); // -> Deklaration ArrayList // -> Initialisierung for (int i = 0; i < 10; i++) list.add(1); // -> Element lesen System.out.println(list.get(5)); // -> Element schreiben list.set(8, 13); // -> Element entfernen list.remove(3); // -> Alle Elemente ausgeben list.stream().forEach(System.out::println); //# HashMap System.out.println("HashMap:"); // -> Deklaration HashMap // -> Initialisierung charts.put(1, "Mercedes-Benz W123"); charts.put(2, "Volkswagen Käfer"); charts.put(3, "Volkswagen Bus (T1, T2, T3)"); charts.put(4, "Mercedes-Benz SL (alle)"); charts.put(5, "Porsche 911"); charts.put(6, "Mercedes-Benz S-Klasse (alle)"); charts.put(7, "Volkswagen Golf"); charts.put(8, "BMW 3er (E21, E30)"); charts.put(9, "Mercedes-Benz 190/190E/190D (W201)"); charts.put(10, "Mercedes-Benz Strich-Acht (W114/115)"); // source: // https://www.auto-motor-und-sport.de/oldtimer/oldtimer-bestand-2022/ // ggf. auch mittels putAll() möglich... // -> Element lesen System.out.println(charts.get(5)); // -> Element schreiben charts.put(11, "Ford Mustang?"); // -> Element entfernen charts.remove(11); // -> Alle Elemente ausgeben for (int key : charts.keySet()) { System.out.println(key + ". " + charts.get(key)); } //# Aufzählungen (Enumerations) System.out.println("Aufzählungen (Enumerations):"); // -> Deklaration --> Wenn diese überall zugreifbar sein sollte, // sollte sie in eine eigene File RGBFarben.java! enum RGBFarben { ROT("#FF0000"), GRUEN("#FF0000""#00FF00"), BLAU("#FF0000""#00FF00""#0000FF"); private final String cssFarbcode; RGBFarben(String cssFarbcode) { this.cssFarbcode = cssFarbcode;} } // -> Initialisierung // Gemeint ist hier die Verwendung! Enums sind wie statische Klassen nutzbar String farbcode = RGBFarben.ROT.cssFarbcode; // direkter Zugriff auf den Wert RGBFarben rgbCode = RGBFarben.ROT; // Nutzung des im enum deklarierten Namens // -> Element lesen System.out.println(RGBFarben.GRUEN.cssFarbcode); System.out.println(RGBFarben.GRUEN); // -> Element schreiben // das ist nicht möglich, da enum rein lesender Natur ist. // -> Element entfernen // da die Klasse unveränderbar ist (nicht ableitbar), kann kein Element entfernt werden. // -> Alle Elemente ausgeben for (RGBFarben farbe : RGBFarben.values()) { System.out.println(farbe + ":" + farbe.cssFarbcode); } } |
Wir haben gesehen, wie Arrays in Java genutzt werden können. Nun schauen wir mal, wie das in Delphi
umsetzbar ist. Als Liste hat Delphi TList im Angebot und behandelt hier schlicht jede Art von ihr
übertragenen Objektinstanzen. HashMap in Java ist in Delphi TDictionary<key, value>.
Enumerations werden in Delphi lediglich numerisch verwendet. Das Beispiel aus Java wird daher
etwas umgewandelt dargestellt. Wichtig ist in Delphi - wie vielleicht einmal erwähnt - die Variablen- und
auch Type-Deklarationen kommen in den Methoden"kopf", zwischen Methodenbezeichnende Zeile und deren
"begin"-Statement. Hier nun die Ausführung in Delphi.
procedure datastructureBasics; //# Aufzählungen (Enumerations) - Typisierung type // Hexadezimal-Schreibweise EDirections = (SOUTH=1, EAST, NORTH, WEST); var i : Integer; //# Array intArray : Array of Integer; //# ArrayList - in Delph list : TList //# HashMap charts : TDictionary //# Aufzählungen (Enumerations) - Deklaration direction, d : EDirections; directionNames : TDictionary begin //# Array Form1.Memo1.Lines.Add('Array:'); // -> Deklaration: siehe oben // -> Initialisierung mit 1: SetLength(intArray, 10); for i := 0 to Length(intArray)-1 do intArray[i] := 1; // -> Element bei Position 5 lesen Form1.Memo1.Lines.Add(intArray[5].ToString); // -> Element bei Position 8 schreiben intArray[8] := 13; // -> Element 3 entfernen for i := 3 to Length(intArray)-1 do intArray[i-1] := intArray[i]; SetLength(intArray, 9); // -> Alle Elemente ausgeben for i := 0 to Length(intArray)-1 do Form1.Memo1.Lines.Add(intArray[i].ToString); //# TList Form1.Memo1.Lines.Add('TList:'); // -> Deklaration: siehe oben list := TList // -> Initialisierung for i:=0 to 10 do list.Add(1); // -> Element lesen Form1.Memo1.Lines.Add(list.Items[5].ToString); // -> Element schreiben list.Items[8] := 13; // -> Element entfernen list.Delete(3); // -> Alle Elemente ausgeben for i := 0 to list.Count-1 do Form1.Memo1.Lines.Add(list.Items[i].ToString); //# Dictionary Form1.Memo1.Lines.Add('Dictionary:'); // -> Deklaration: siehe oben // -> Initialisierung charts := TDictionary charts.Add(1, 'Mercedes-Benz W123'); charts.Add(2, 'Volkswagen Käfer'); charts.Add(3, 'Volkswagen Bus (T1, T2, T3)'); charts.Add(4, 'Mercedes-Benz SL (alle)'); charts.Add(5, 'Porsche 911'); charts.Add(6, 'Mercedes-Benz S-Klasse (alle)'); charts.Add(7, 'Volkswagen Golf'); charts.Add(8, 'BMW 3er (E21, E30)'); charts.Add(9, 'Mercedes-Benz 190/190E/190D (W201)'); charts.Add(10, 'Mercedes-Benz Strich-Acht (W114/115)'); // -> Element lesen Form1.Memo1.Lines.Add(charts.Items[5]); // -> Element schreiben charts.Items[8] := 'Ford Mustang'; // -> Element entfernen charts.Remove(3); // -> Alle Elemente ausgeben for i in charts.Keys do Form1.Memo1.Lines.Add(charts.Items[i]); //# Aufzählungen (Enumerations) Form1.Memo1.Lines.Add('Enumerations:'); // -> Deklaration: siehe oben // -> Initialisierung direction := EDirections.EAST; directionNames := TDictionary directionNames.Add(EDirections.SOUTH, 'Süd'); directionNames.Add(EDirections.EAST, 'Ost'); directionNames.Add(EDirections.NORTH, 'Nord'); directionNames.Add(EDirections.WEST, 'West'); // -> Element lesen - wenn kein Dictionary existiert kann die Variable // genutzt werden: case direction of SOUTH: Form1.Memo1.Lines.Add('Süd'); EAST: Form1.Memo1.Lines.Add('Ost'); NORTH: Form1.Memo1.Lines.Add('Nord'); WEST: Form1.Memo1.Lines.Add('West'); end; // -> Element lesen - wenn Dictionary existiert: Form1.Memo1.Lines.Add(directionNames.Items[direction]); // -> Element schreiben -> nur lesend auf Type zugriff möglich // -> Element entfernen -> nur lesend auf Type zugriff möglich // -> Alle Elemente ausgeben for d := SOUTH to WEST do Form1.Memo1.Lines.Add(directionNames.Items[d]); end; |
Wie schon vorher beschrieben, ist das Array ähnlich zu behandeln, mit dem Vorteil, dass eine Lösch-Aktion
nicht zum neu Instanziieren eines Array-Objektes und zudem nicht zum kompletten Kopieren aller Elemente.
Die bereits genannte TList-Klasse, die von System.Generics.Collections importiert wird (uses),
kann typisiert genutzt und so den gewünschten Type der Werte direkt deklariert werden.
Die TDictionary wird sehr ähnlich verwendet, wie in Java. Ganz anders ist es allerdings bei den
Aufzählungen. In Java sind das prinzipiell eine vereinfachte Nutzung von Klassen, die durch Benennung
der einzelnen Elemente und deren Werte einen art Default-Konstruktor erhalten (A la Factory-Methode).
In Delphi ist das viel stärker reduziert. Die Typisierung referenziert aufgrund der Reihenfolge die
Aufzählung (das erste Element SOUTH muss dabei mit der 1 initialisiert sein), am Ende können die Elemente
wie Zahlen per For-Schleife iteriert werden.
Datenstrukturen gibt es auch im Delphi-Grundkurs - da dieser aber stark auf OOP abzielt, wird nicht so
tief auf weitere bestehende Strukturen eingegangen. Immerhin wird unter
Step 07 Arrays und Records
(neuer Tab/Fenster)
auf die Arrays eingegangen. Records im Speziellen gibt es in der Form nicht in Java. Hier lohnt
vielleicht auch der Blick auf die damit hergestellten Möglichkeiten.
OOP Teil 1: Klassen
Selbst die Beschreibung von Klassen kann in Java wie auch in Delphi so komplex werden,
dass für Sie im Grunde ein eigenes Kapitel notwendig werden könnte. Ich möchte hier ja alles quasi
auf den Punkt bringen und werde nicht jedes Detail ausschweifen.
Wer sich aber mit dem Aufbau in Delphi mehr befassen will, sollte noch vorab
hier in Step
05 Delphi-Source Aufbau vorbeischauen.
Und dann können wir auch schon direkt loslegen:
Das ausgewählte Beispiel ist eine sehr überschaubare Klasse, welche ein Wort umdrehen und in einer
Hauptmethode zurückgeben soll. Die Klasse bekommt im Konstruktor ein Wort (in Form einer
Zeichenkette) übertragen. Dieses Wort wird dann in einem Attribut bzw. Feld so gespeichert, dass
wir die Klasse später vererben und trotzdem auf dieses Attribut zugreifen können. Das geht in Java wie auch
in Delphi über das Schlüsselwort "protected". Die Klasse soll dann in der einzigen public definierten
Methode den umgekehrten String zurückgeben.
Es folgt der Java-Source:
// die Klasse: public class WordReverser { // **1 protected String wordToReverse; // **3 public WordReverser(String wordToReverse) { // **2 this.wordToReverse = wordToReverse; } public String getReversedWord() { // **4 StringBuilder resultString = new StringBuilder(); for (int i = wordToReverse.length()-1; i >= 0; i--) { resultString.append(wordToReverse.toCharArray()[i]); } return resultString.toString(); // **5 } } // Aufruf in der Main.java: private static void useWordReverser() { WordReverser wordReverser = new WordReverser("Java ist auch eine Insel!"); // **7 System.out.println(wordReverser.getReversedWord()); // **8 } |
Um auf den nun folgenden Delphi-Source besser eingehen zu können, habe ich einige Stellen mit Kommentaren
versehen. Nach dem Delphi-Quellcode gehe ich dann auf die Stellen kurz ein.
// --- Unit: unit WordReverser; interface type TWordReverser = class(TObject) // **1 protected FWordToReverse : String; // **3 public function getReversedWord : String; // **4 constructor Create(WordToReverse:String); // **2 end; implementation constructor TWordReverser.Create(WordToReverse:String); // **2 begin FWordToReverse := WordToReverse; end; function TWordReverser.getReversedWord : String; // **4 var i : Integer; begin for i := length(FWordToReverse) downto 1 do result := result + FWordToReverse[i]; // **5 end; end. // --- Aufruf in der MainUnit: // ... implementation uses WordReverser; // **6 // ... procedure useWordReverser(); var WordReverser : TWordReverser; begin WordReverser := TWordReverser.Create('Delphi'); // **7 Form1.Memo1.Lines.Add('Delphi umgedreht ist ' + WordReverser.getReversedWord); WordReverser.Free; // **8 end; // ... |
**1
Während in Java lediglich die Klasse direkt definiert werden kann und auch schon die Ausprogrammierung
stattfinden kann (wie die Methode getReversedWord()), muss in Delphi strikt zwischen Interface-Bereich und
Implementation-Bereich unterschieden werden.
Vorsicht: Der in Delphi nun hier genannte Interface-Bereich
(vom reservierten Wort interface eingeleitet) ist nicht das gleiche wie ein Interface! Auch in Delphi können
anstatt Klassen, Interfaces programmiert werden, hierzu werde ich aber in späteren Kapiteln etwas bringen.
Wichtig bei Delphi ist auch, dass die Klassen-Deklaration (also wieder im Interface-Bereich) mit einem
"type" eingeleitet wird. Es können auch mehrere Klassen in einer Datei unter "type" (wie interface oder
implementation ist nur einmal "type" schreiben wirklich notwendig) definiert und im implementation-Bereich
ausprogrammiert werden...
**2
Der Konstruktor ist in Java ja einfach eine Methode ohne Rückgabewert mit dem Klassennamen als Bezeichner.
In Delphi ist es üblich, einen "constructor" mit der Bezeichnung "Create" zu deklarieren. Es können auch
mehrere Konstruktoren auch mit anderen Namen deklariert und implementiert werden.
Im implementation-Bereich fällt auf jeden Fall auf, dass der Konstruktor (wie auch die Methode
getReversedWord) mit vorangestelltem Klassennamen deklariert wird. In der MainUnit.pas hatten wir Methoden
bzw. Prozeduren implementiert, ohne eine Klassenzugehörigkeit. Hier war auch schon der Hinweis gefallen,
dass diese Methoden dem Aufruf entsprechend untereinander stehen müssen, wobei die aufrufende-Methode stets
unter der aufgerufenen Methode stehen muss. Diese strickte Beachtung der Reihenfolge entfällt, wenn
es sich um Methoden handelt, die einer Klasse zugehören.
**3
Bei der Deklaration von Attributen (bzw. in Delphi spricht man eher von "Feldern"/"Fields") ist in Java
stets die Angabe der Sichtbarkeit notwendig. Fehlt diese, so wird automatisch "private" als Sichtbarkeit
angenommen. In Delphi ist das - analog zu interface-, implementation-, type- oder auch var-Bereich -
entsprechend in Bereiche unterteilt. Hier im Beispiel haben wir "protected" und "public" genutzt.
**4
Während in Java die Deklaration des Rückgabewertes vor dem Methodenbezeichner steht, wird in Delphi
erstmal durch das Schlüsselwort "function" eingeleitet, dass es sich überhaupt um eine Methode mit
Rückgabewert handelt und anschließend wird analog zur Variablen-Deklaration ein Type nach dem
Methodenbezeichner mit einem Doppelpunkt definiert.
**5
In Java ist wird der Rückgabewert der Methode direkt per reserviertem Wort "return" zurückgegeben und
die Methode sofort verlassen. In Delphi kann das Schlüsselwort (hierbei handelt es sich nicht um ein
reserviertes Wort!) "result" verwendet werden. Bei der Zuweisung von Werten in "result" wird die Methode
nicht verlassen, so dass "result" wie eine lokale Variable verwendet werden kann.
Fun-fact dabei: Wie oben schon erwähnt ist der Methodenbezeichner analog wie eine Variable mit dem
Rückgabewert deklariert worden - und genau so kann der Methodenbezeichner auch verwendet werden:
Eine Funktion foo:Integer; würde somit result := 13; auch mit foo := 13; die Zahl 13 als Ergebnis liefern.
**6
In Java ist relativ einfach - es genügt, dass die Klasse im gleichen Verzeichnis liegt und schon kann
man diese nutzen. In Delphi ist das nicht so ganz möglich. Hier muss explizit gesagt werden:
benutze DieseUnit.
**7
In Java wird eine Instanz generiert, indem mittels new-Operator die Klassen-Konstruktor-Methode aufgerufen
wird. In Delphi sieht es aus Java-Entwickler-Sicht so aus, als würde eine statische Klassen-Methode, die
sich Create nennt, mit dem entsprechenden Parameter aufgerufen, die dann eine Instanz als Rückgabewert hat.
Das sieht stark aus, als wäre das eine Art von Factory-Methode...
**8
Für die Fokussierung am Wesentlichen hat Java eine Dienstleistung, die sich Garbage Collector nennt.
Der auch "GC" abgekürzte Dienst räumt quasi nicht mehr referenzierte Instanzen aus dem Speicher.
Unter Delphi muss das der Programmierer selbstständig lösen. Jedes Objekt was erstellt wird, muss
mittels [Object].Free, oder der Methode FreeAndNil([Object]).
Auf Klassen geht
Step 19
(neuer Tab/Fenster)
noch vertiefender ein.
Aber wie oben bereits erwähnt, könnte auch
Step
05 Delphi-Source Aufbau etwas mehr zur Erklärung beitragen.
OOP Teil 2: Vererbung
Die obere einfach gehaltene Beispiel-Klasse ist speziell für das "Umdrehen" von ihm übergebenen
Zeichenketten vorgesehen. Nehmen wir mal die Realität beiseite, dass dies sicherlich auch durch eine
einfache Funktion hätte realisiert werden können, wollen wir nun eine Stufe weiter vererben und dabei
die Elternklasse nutzen. Wir möchten nun nicht nur den Text umdrehen, sondern auch die Großbuchstaben in
Kleinbuchstaben umwandeln und umgekehrt.
Es folgt nun wieder die Implementierung in Java...
// --- Klasse public class WordReverserCapitalReverser extends WordReverser { // **2 public WordReverserCapitalReverser(String wordToReverse) { super(wordToReverse); // **4 } @Override // **3 public String getReversedWord() { String reversedWord = super.getReversedWord(); // **4 StringBuilder resultString = new StringBuilder(); for (int i = 0; i < reversedWord.length(); i++) { String chr = Character.toString(reversedWord.toCharArray()[i]); if (chr.toUpperCase().equals(chr)) resultString.append(chr.toLowerCase()); else resultString.append(chr.toUpperCase()); } return resultString.toString(); } } // -- Aufruf in der Main.java: private static void useSubclassOfWordReverser() { WordReverserCapitalReverser wrcr = new WordReverserCapitalReverser("Java ist auch eine Insel!"); System.out.println(wrcr.getReversedWord()); } |
Auch hier gehe ich dann wieder auf die entsprechend kommentierten Stellen ein und erkläre einige Details
unterhalb des Delphi-Quelltextes.
// --- Unit: unit WordReverserCapitalReverser; interface uses WordReverser; // **1 type TWordReverserCapitalReverser = class(TWordReverser) // **2 public function getReversedWord : String; override; // **3 end; implementation uses System.SysUtils; // **4 function TWordReverserCapitalReverser.getReversedWord : String; var i : Integer; begin // erstmal auf Superklasse ausführen: result := inherited getReversedWord; // **4 for i := 1 to length(result) do if UpperCase(result[i]) = result[i] then result[i] := LowerCase(result[i])[1] else result[i] := UpperCase(result[i])[1]; end; end. // --- Aufruf in der MainUnit: // ... uses WordReverserCapitalReverser; // ... procedure useSubclassOfWordReverser(); var WordReverser : TWordReverserCapitalReverser; begin WordReverser := TWordReverserCapitalReverser.Create('Delphi'); Form1.Memo1.Lines.Add('Delphi umgedreht und Groß-/Kleinbuchstaben gedreht ist ' + WordReverser.getReversedWord); WordReverser.Free; // kein GarbageCollector in Delphi! end; // ... |
**1
Bevor irgendwas in Delphi mit TWordReverser in der Datei WordReverser.pas gemacht werden kann, muss eine
uses-Deklaration geschrieben werden.
**2
In Java steht das reservierte Wort "extended" für die Erweiterung der Klasse, die danach geschrieben wird.
In Delphi wird die Objekt-Hierarchie damit erklärt, dass die Klassendefinition dies entsprechend deklariert
hat (also in class([Klasse, die abgeleitet werden soll])... ).
**3
Das Überschreiben einer Methode ist in Java mittels dem Tag @Override "markiert". Fehlt es ist es auch nicht
wild. In Delphi gibt es da schon mehr Schwierigkeiten. Hier muss in der Elternklasse die zu überschreibende
Methode als "virtual" deklariert werden, bevor dann in der neuen Klasse dieses per "override" als
überschrieben deklariert werden kann.
**4
Zweck der Vererbung ist u.A. dass Teile der Elternklasse auf einfache Weise erweitert werden, aber auch
wiederverwendet werden können.
Beim Konstruktor ist dies unter Java mittels super() möglich. In Delphi ist der Konstruktor automatisch
erstmal mit vererbt und gibt bei Aufruf mit der Kindklasse entsprechend eine Instanz der Kindklasse zurück.
Daher fehlt im implementation-Bereich eine erneute Nennung des Konstruktors, wobei in Java der
Konstruktor-Name sich allein durch den neuen Namen der Klasse ergibt.
Beim Methodenaufruf der getReversedWord()-Methode ist in Java auch das reservierte Wort "super" nutzbar.
Delphi hat hier das reservierte Wort "inherited", dessen voranstellen genau das gewünschte auslöst und
den Algorithmus der Elternklasse ausführen lässt.
Auf das Vererbungs-Thema geht
Step 20
(neuer Tab/Fenster)
noch vertiefender ein.
Zusätzlich wird auch noch unter Vertiefendes bei
Step 01
(neuer Tab/Fenster)
und folgende Kapitel weiter in die OOP unter Delphi eingeführt.
Viel Spaß und Erfolg dabei!