# Objektorientiert programmieren mit Java

> **Die Abschnitte „In meiner Übung" zeigen echten Code** aus meinen Java-Übungen in IntelliJ vom Herbst 2025: ein Kreis, eine Regatta mit Schiffen und Besitzern und eine Uhrzeit in zwei Formaten. Für diese Seite habe ich jede Übung mit Java 21 neu kompiliert und ausgeführt, die gezeigten Ausgaben sind echt. Die wenigen Stellen mit „Zum Ausprobieren" sind kurze Beispiele, die meine Klassen aufrufen, und ebenfalls ausgeführt.

## Inhaltsverzeichnis

- [Klasse und Objekt](#klasse-und-objekt)
- [Kapselung: private Attribute, Getter und Setter](#kapselung-private-attribute-getter-und-setter)
- [Konstruktoren überladen](#konstruktoren-überladen)
- [static: gehört zur Klasse, nicht zum Objekt](#static-gehört-zur-klasse-nicht-zum-objekt)
- [Beziehungen zwischen Klassen](#beziehungen-zwischen-klassen)
- [Vom Array zur ArrayList](#vom-array-zur-arraylist)
- [toString() überschreiben](#tostring-überschreiben)
- [Sortieren mit Comparable](#sortieren-mit-comparable)
- [Vererbung und abstrakte Klassen](#vererbung-und-abstrakte-klassen)
- [equals() und Kopien](#equals-und-kopien)
- [Zusammenfassung / Spickzettel](#zusammenfassung--spickzettel)

---

## Klasse und Objekt

Eine **Klasse** ist ein Bauplan: Sie legt fest, welche Daten ein Ding hat (Attribute) und was es kann (Methoden). Ein **Objekt** ist ein konkretes Exemplar nach diesem Bauplan und wird mit `new` erzeugt. Von einer Klasse kann es beliebig viele Objekte geben, und jedes hat seine eigenen Werte.

### In meiner Übung: der Kreis

Die Klasse `Circle` beschreibt einen Kreis mit Radius und der Angabe, ob er gefüllt ist. Umfang und Fläche speichert sie nicht, sie rechnet sie bei jedem Aufruf aus dem Radius aus. So können sie nie mit dem Radius auseinanderlaufen.

```java
public class Circle {
    private double radius;
    private boolean filled;

    public boolean isFilled() {
        return filled;
    }

    public void setFilled(boolean filled) {
        this.filled = filled;
    }

    public Circle() {
        this.radius = 1.0;
        this.filled = false;
    }

    public Circle (double radius) {
        this.radius = radius;
        this.filled = false;
    }

    public Circle(double radius, boolean filled) {
        this.radius = radius;
        this.filled = filled;
    }

    public double getRadius() {
        return radius;
    }


    public double getCircumference() {
        return 2 * SomeMaths.PI * radius;
    }
    public double getArea() {
        return SomeMaths.PI * SomeMaths.getSquare(radius);
    }
}
```

Im Hauptprogramm entstehen drei Kreis-Objekte aus demselben Bauplan, jedes mit eigenem Radius:

```java
public static void main(String[] args) {
    Circle c1 = new Circle(3.0);
    Circle c2 = new Circle(8.0);
    Circle c3 = new Circle(5.0);

    printCircleInfo("1", c1);
    printCircleInfo("2", c2);
    printCircleInfo("3", c3);
}
```

Die Ausgabe für den ersten Kreis:

```text
1:
Radius: 3.00000
Umfang: 18.8495
Flaeche: 28.2743
nicht gefüllt
```

## Kapselung: private Attribute, Getter und Setter

Die Attribute einer Klasse sind `private`. Von aussen kommt man nur über Methoden an sie heran: einen **Getter** zum Lesen und einen **Setter** zum Ändern. Das hat zwei Vorteile. Die Klasse kann im Setter prüfen, ob ein Wert Sinn ergibt, und sie kann ihr Inneres später umbauen, ohne dass sich der Code ändern muss, der sie benutzt.

Im Kreis oben sieht man auch, dass nicht jedes Attribut beides braucht. `filled` hat Getter und Setter, ein Kreis kann also nachträglich gefüllt werden. `radius` hat **nur einen Getter**: Nach dem Erzeugen lässt sich der Radius von aussen nicht mehr ändern. Ob ein Attribut einen Setter bekommt, ist also eine Entscheidung und kein Automatismus.

Bei Wahrheitswerten heisst der Getter nach Java-Konvention `isFilled()` statt `getFilled()`.

## Konstruktoren überladen

Ein **Konstruktor** läuft genau einmal, wenn ein Objekt mit `new` entsteht, und gibt ihm seine Anfangswerte. Er heisst wie die Klasse und hat keinen Rückgabetyp.

Eine Klasse darf mehrere Konstruktoren haben, solange sich ihre Parameterlisten unterscheiden. Das heisst **Überladen**. Java wählt beim Aufruf den passenden anhand der Argumente aus. Der Kreis hat drei:

| Aufruf | Ergebnis |
|---|---|
| `new Circle()` | Radius 1.0, nicht gefüllt |
| `new Circle(3.0)` | Radius 3.0, nicht gefüllt |
| `new Circle(3.0, true)` | Radius 3.0, gefüllt |

`this.radius = radius;` unterscheidet zwischen dem Attribut des Objekts (`this.radius`) und dem Parameter gleichen Namens (`radius`). Ohne `this` würde der Parameter sich selbst zugewiesen, und das Attribut bliebe 0.

## static: gehört zur Klasse, nicht zum Objekt

Was `static` ist, gehört zur Klasse selbst und nicht zu einem einzelnen Objekt. Man ruft es über den Klassennamen auf, ohne vorher ein Objekt zu erzeugen. Das passt für Werte und Hilfsfunktionen, die für alle gleich sind. `final` bedeutet, dass sich der Wert nach dem Setzen nicht mehr ändern lässt, zusammen mit `static` ergibt das eine Konstante.

### In meiner Übung: eine kleine Mathe-Klasse

```java
public class SomeMaths {
    public static final double PI = 3.14159;

    public static double getSquare(double value) {
        return value * value;
    }
}
```

Der Kreis benutzt beides direkt über den Klassennamen: `SomeMaths.PI` und `SomeMaths.getSquare(radius)`. Auch `printCircleInfo` im Hauptprogramm ist `static`, weil `main` selbst `static` ist und es noch kein Objekt gibt, auf dem man eine Methode aufrufen könnte.

Java bringt die Zahl Pi schon mit, als `Math.PI` mit viel mehr Stellen. Man sieht den Unterschied in der vierten Nachkommastelle: Mit `3.14159` ergibt der Umfang bei Radius 3 den Wert 18.8495, mit `Math.PI` 18.8496.

## Beziehungen zwischen Klassen

Objekte stehen selten allein. Die zwei häufigsten Beziehungen:

| Beziehung | Bedeutung | In der Regatta |
|---|---|---|
| Assoziation | Ein Objekt kennt ein anderes | Ein Schiff kennt seinen Besitzer |
| Aggregation | Ein Objekt hat mehrere andere, die auch ohne es existieren | Ein Wettkampf hat Schiffe, die Schiffe gibt es aber auch ohne ihn |

Im Code ist beides einfach ein Attribut: bei der Assoziation eines vom Typ der anderen Klasse, bei der Aggregation eine Liste davon.

### In meiner Übung: Schiff und Besitzer

Ein `Schiff` hat eine Nummer, einen Namen, eine Zeit und einen `Besitzer`. Der Besitzer ist ein eigenes Objekt mit Name, Vorname und Wohnort.

```java
public class Schiff implements Comparable<Schiff> {
    private int nr;
    private String name;
    private int time;
    private Besitzer besitzer;

    public Schiff(int nr, String name) {
        this.nr = nr;
        this.name = name;
    }
```

Der Besitzer steht nicht im Konstruktor. Er wird später mit `setBesitzer(...)` gesetzt, ein Schiff kann also auch keinen haben. Deshalb prüft `toString()`, ob einer da ist. Ohne diese Prüfung würde `besitzer.getVorname()` bei einem Schiff ohne Besitzer mit einer `NullPointerException` abbrechen.

```java
@Override
public String toString() {
    String besitzerInfo = (besitzer != null)
            ? besitzer.getVorname() + " " + besitzer.getName()
            : "kein Besitzer";
    return "Nr: " + nr + ", Name: " + name + ", Zeit: " + time + " Sek., Besitzer: " + besitzerInfo;
}
```

## Vom Array zur ArrayList

Ein **Array** hat eine feste Länge, die beim Erzeugen festgelegt wird. Eine **ArrayList** wächst mit: Man hängt mit `add` an, und sie macht selbst Platz. Dafür kann sie nur Objekte speichern, keine einfachen Typen wie `int` direkt.

### In meiner Übung: zwei Fassungen der Regatta

Die Regatta gibt es bei mir in zwei Fassungen, eine Woche auseinander. In der ersten vom 19. November 2025 hält der Wettkampf seine Schiffe in einem Array mit fünf Plätzen. Beim Hinzufügen sucht eine Schleife den ersten freien Platz, und beim sechsten Schiff kommt „Wettkampf ist voll!".

```java
public class Wettkampf {
    private String name;
    private Schiff[] schiffe = new Schiff[5];

    public Wettkampf(String name) {
        this.name = name;
    }

    public void addSchiff(Schiff s) {
        for (int i = 0; i < schiffe.length; i++) {
            if (schiffe[i] == null) {
                schiffe[i] = s;
                System.out.println(s.getName() + " wurde zum Wettkampf hinzugefügt.");
                return;
            }
        }
        System.out.println("Wettkampf ist voll!");
    }
```

In der zweiten vom 26. November ist daraus eine `ArrayList` geworden. Das Hinzufügen ist eine Zeile, und es gibt keine Obergrenze mehr. Die Kommentare im Code stammen von damals:

```java
public class Wettkampf {
    private String name;
    private ArrayList<Schiff> schiffe = new ArrayList<>();  //änderung hier

    public Wettkampf(String name) {
        this.name = name;
    }

    public void addSchiff(Schiff s) {
        schiffe.add(s);                         // kein limit mehr
        System.out.println(s.getName() + " wurde zum Wettkampf hinzugefügt.");
    }
```

Eine zweite Vereinfachung kommt dazu, die man leicht übersieht: In der Array-Fassung stehen in jeder Schleife über die Schiffe `if (s != null)`-Prüfungen, weil freie Plätze `null` sind. In einer `ArrayList` gibt es keine leeren Plätze, die Prüfungen fallen weg.

## toString() überschreiben

Jede Klasse in Java erbt von `Object` eine Methode `toString()`. Ohne eigene Fassung liefert sie nur den Klassennamen und eine Nummer, etwa `Besitzer@1b6d3586`. Überschreibt man sie, gibt das Objekt eine lesbare Beschreibung von sich. `System.out.println(objekt)` ruft `toString()` automatisch auf.

`@Override` davor ist nicht Pflicht, aber nützlich: Der Compiler prüft dann, ob es die Methode in der Oberklasse wirklich gibt. Ein Tippfehler wie `tostring()` fällt so sofort auf, statt still eine neue Methode anzulegen.

### In meiner Übung: der Besitzer

```java
@Override
public String toString() {
    return vorname + " " + name + " (" + wohnort + ")";
}
```

## Sortieren mit Comparable

Zahlen und Texte kann Java von sich aus sortieren. Bei eigenen Objekten weiss es nicht, was „kleiner" heissen soll. Dafür implementiert die Klasse das Interface `Comparable` und damit die Methode `compareTo`. Sie gibt eine negative Zahl zurück, wenn das eigene Objekt vor dem anderen kommt, eine positive, wenn es danach kommt, und 0, wenn beide gleich sind. Danach sortiert `Collections.sort(...)` eine ganze Liste.

### In meiner Übung: die Rangliste

Ein Schiff ist „kleiner", wenn es schneller war. `Integer.compare` liefert genau die negative Zahl, die positive Zahl oder die 0, die `compareTo` braucht:

```java
@Override
public int compareTo(Schiff anderesSchiff) {
    return Integer.compare(this.time, anderesSchiff.time);
}
```

Die Rangliste sortiert die Liste und gibt sie mit Platzierung aus. `%-12s` im Format schreibt den Namen linksbündig auf zwölf Zeichen, damit die Sekunden in einer Spalte stehen.

```java
public void printRangliste() {
    Collections.sort(schiffe);
    System.out.println("\n Rangliste Regatta ");

    int platz = 1;
    for (Schiff s : schiffe) {

        System.out.printf("%d. %-12s %d Sekunden%n", platz, s.getName(), s.getTime());
        platz++;
    }
}
```

Die Zeiten verteilt `race()` zufällig mit `300 + r.nextInt(301)`, also zwischen 300 und 600 Sekunden. Die Rangliste sieht deshalb bei jedem Start anders aus. Platz 1 hat immer die kürzeste Zeit.

## Vererbung und abstrakte Klassen

Mit `extends` übernimmt eine Klasse alles von einer anderen, der **Oberklasse**, und kann Eigenes dazulegen oder Methoden anders umsetzen. Im Konstruktor der Unterklasse ruft `super(...)` zuerst den Konstruktor der Oberklasse auf.

Eine **abstrakte Klasse** (`abstract class`) ist ein unvollständiger Bauplan: Von ihr kann man kein Objekt erzeugen. Eine **abstrakte Methode** hat keinen Inhalt, nur die Kopfzeile. Jede Unterklasse muss sie selbst umsetzen. So legt die Oberklasse fest, *was* jede Unterklasse können muss, und die Unterklassen bestimmen, *wie*.

`protected` liegt zwischen `private` und `public`: Unterklassen dürfen darauf zugreifen, fremde Klassen nicht.

### In meiner Übung: eine Uhrzeit, zwei Formate

Die abstrakte Oberklasse `ZeitFormat` hält eine `Zeit` und verlangt eine Methode `zeigeZeit()`:

```java
public abstract class ZeitFormat {
    protected Zeit zeit;

    public ZeitFormat(Zeit zeit) {
        this.zeit = zeit;
    }

    public abstract void zeigeZeit();
}
```

Das 24-Stunden-Format gibt die Zeit direkt aus:

```java
public class Zeitformat24 extends ZeitFormat {

    public Zeitformat24(Zeit zeit) {
        super(zeit);
    }

    public void zeigeZeit() {
        int stunde = zeit.getStunde();
        int minute = zeit.getMinute();
        System.out.printf("%02d:%02d Uhr (24h)%n", stunde, minute);
    }
}
```

Das 12-Stunden-Format muss rechnen: Ab 12 Uhr ist es „PM", 0 Uhr wird zu 12, und nach Mittag werden 12 Stunden abgezogen.

```java
public class Zeitformat12 extends ZeitFormat {

    public Zeitformat12(Zeit zeit) {
        super(zeit);
    }

    public void zeigeZeit() {
        int stunde = zeit.getStunde();
        int minute = zeit.getMinute();
        String ampm = (stunde >= 12) ? "PM" : "AM";

        if (stunde == 0) {
            stunde = 12;
        } else if (stunde > 12) {
            stunde -= 12;
        }

        System.out.printf("%02d:%02d %s (12h)%n", stunde, minute, ampm);
    }
}
```

### Zum Ausprobieren: Polymorphie

Weil beide Formate ein `ZeitFormat` sind, passen sie in dasselbe Array. Die Schleife ruft bei jedem Element `zeigeZeit()` auf, und Java nimmt automatisch die Fassung der jeweiligen Unterklasse. Das heisst **Polymorphie**: derselbe Aufruf, verschiedenes Verhalten.

```java
Zeit z = new Zeit(14, 30);
ZeitFormat[] formate = { new Zeitformat24(z), new Zeitformat12(z) };
for (ZeitFormat f : formate) {
    f.zeigeZeit();
}
```

```text
14:30 Uhr (24h)
02:30 PM (12h)
```

Kommt später ein drittes Format dazu, ändert sich an der Schleife nichts. Es braucht nur eine neue Unterklasse.

## equals() und Kopien

`==` vergleicht bei Objekten, ob zwei Variablen auf **dasselbe** Objekt zeigen. Ob zwei Objekte den **gleichen Inhalt** haben, prüft `equals()`. Wie bei `toString()` erbt jede Klasse eine Fassung von `Object`, die aber nur `==` macht. Für einen Vergleich nach Inhalt muss man sie selbst schreiben.

### In meiner Übung: zwei Uhrzeiten vergleichen

`equals` prüft zuerst, ob es dasselbe Objekt ist, dann, ob das andere überhaupt eine `Zeit` ist, und vergleicht erst dann Stunde und Minute. `clone()` baut ein neues Objekt mit denselben Werten.

```java
public boolean equals(Object obj) {
    if (this == obj) return true;
    if (!(obj instanceof Zeit)) return false;
    Zeit z = (Zeit) obj;
    return this.stunde == z.stunde && this.minute == z.minute;
}

public Zeit clone() {
    return new Zeit(this.stunde, this.minute);
}
```

Das Hauptprogramm der Übung vergleicht 14:30 mit 8:15 und kopiert die erste Zeit:

```text
Zeit 1: 14:30
Zeit 2: 8:15
Sind die Zeiten gleich? false
Kopie von z1: 14:30
```

### Zum Ausprobieren: == gegen equals()

Die Kopie hat denselben Inhalt, ist aber ein anderes Objekt:

```java
Zeit z1 = new Zeit(14, 30);
Zeit kopie = z1.clone();
System.out.println(z1 == kopie);
System.out.println(z1.equals(kopie));
```

```text
false
true
```

## Zusammenfassung / Spickzettel

| Thema | Kernaussage |
|---|---|
| Klasse und Objekt | Klasse ist der Bauplan, `new` erzeugt ein Objekt mit eigenen Werten |
| Kapselung | Attribute `private`, Zugriff über Getter und Setter, nicht jedes Attribut braucht einen Setter |
| Konstruktor | Heisst wie die Klasse, kein Rückgabetyp, mehrere mit verschiedenen Parametern möglich |
| `this` | Das eigene Objekt, trennt `this.radius` vom Parameter `radius` |
| `static` | Gehört zur Klasse, Aufruf über den Klassennamen, mit `final` eine Konstante |
| Assoziation und Aggregation | Ein Objekt als Attribut, beziehungsweise eine Liste davon |
| Array und ArrayList | Array hat feste Länge, `ArrayList` wächst mit `add` |
| `null` | Ein optionales Objekt vor dem Zugriff prüfen |
| `toString()` | Lesbare Beschreibung, mit `@Override` prüft der Compiler die Schreibweise |
| `Comparable` | `compareTo` gibt negativ, 0 oder positiv zurück, dann `Collections.sort` |
| Vererbung | `extends`, `super(...)` ruft den Konstruktor der Oberklasse |
| Abstrakte Klasse | Kein Objekt möglich, abstrakte Methoden muss jede Unterklasse umsetzen |
| Polymorphie | Gleicher Aufruf, Verhalten der jeweiligen Unterklasse |
| `==` und `equals()` | Dasselbe Objekt gegen gleicher Inhalt. Wer `equals` überschreibt, sollte auch `hashCode` überschreiben |
