Von IF zu OOP

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 list = new 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 charts = new 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 : TDictionaryString>;
  //# Aufzählungen (Enumerations) - Deklaration
  direction, d : EDirections;
  directionNames : TDictionaryString>;
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.Create;
  // -> 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 := TDictionaryString>.Create;
  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 := TDictionaryString>.Create;
  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!