EMZETT.
Login

Kurz: Eine Klasse ist ein Bauplan, ein Objekt ein daraus gebautes Exemplar. Eine Klasse bündelt Daten (Felder) und Verhalten (Methoden).

Teil des Kurses Java

Kapitel 11 von 22 im Kurs Java (Abschnitt „Objektorientierung“). Mit Fortschritt, Quiz und Zertifikat auf der Lernseite.

Eine Klasse ist ein Bauplan, ein Objekt ein daraus gebautes Exemplar. Eine Klasse bündelt Daten (Felder) und Verhalten (Methoden).

public class Hund {
    // Felder (Zustand)
    private String name;
    private int alter;
 
    // Konstruktor: legt das Objekt an
    public Hund(String name, int alter) {
        this.name = name;
        this.alter = alter;
    }
 
    // Methoden (Verhalten)
    public String bellen() {
        return name + " sagt: Wuff!";
    }
 
    public void geburtstag() {
        alter++;
    }
 
    // Getter
    public String getName() { return name; }
    public int getAlter() { return alter; }
 
    @Override
    public String toString() {
        return "Hund[" + name + ", " + alter + "]";
    }
 
    public static void main(String[] args) {
        Hund rex = new Hund("Rex", 3);
        Hund mimi = new Hund("Mimi", 5);
        rex.geburtstag();
        System.out.println(rex.bellen() + " " + rex.getAlter());
        System.out.println(rex + " " + mimi);
    }
}

Ausgabe:

Rex sagt: Wuff! 4
Hund[Rex, 4] Hund[Mimi, 5]
  • new Hund(...) erzeugt ein Objekt und ruft den Konstruktor auf
  • this ist das aktuelle Objekt
  • private versteckt Felder; von außen sind sie nur über Methoden erreichbar (Kapselung)
  • toString() bestimmt die Textdarstellung

Kapselung und Sichtbarkeit

ModifikatorSichtbar für
publicalle
protectedgleiches Paket und Unterklassen
(ohne)nur das gleiche Paket
privatenur die Klasse selbst

Halte Felder private und biete kontrollierte Zugriffe an. So kann die Klasse ihre Regeln durchsetzen:

public class Konto {
    private double stand;
    private final String inhaber;
    private static int anzahl = 0;          // gehört der Klasse, nicht einem Objekt
 
    public Konto(String inhaber, double startwert) {
        if (startwert < 0) throw new IllegalArgumentException("Startwert < 0");
        this.inhaber = inhaber;
        this.stand = startwert;
        anzahl++;
    }
    public Konto(String inhaber) { this(inhaber, 0); }       // Konstruktoren verketten
 
    public void einzahlen(double betrag) {
        if (betrag <= 0) throw new IllegalArgumentException("Betrag muss positiv sein");
        stand += betrag;
    }
    public void abheben(double betrag) {
        if (betrag > stand) throw new IllegalStateException("Nicht genug Guthaben");
        stand -= betrag;
    }
    public double getStand() { return stand; }
    public String getInhaber() { return inhaber; }
    public static int getAnzahl() { return anzahl; }
 
    public static void main(String[] args) {
        Konto k = new Konto("Mia", 100);
        k.einzahlen(50);
        k.abheben(30);
        System.out.println(k.getInhaber() + ": " + k.getStand());
        new Konto("Tom");
        System.out.println("Konten: " + Konto.getAnzahl());
        try {
            k.abheben(1000);
        } catch (IllegalStateException e) {
            System.out.println("Fehler: " + e.getMessage());
        }
    }
}

Ausgabe:

Mia: 120.0
Konten: 2
Fehler: Nicht genug Guthaben

equals und hashCode

Standardmäßig vergleicht equals nur die Speicheradresse. Soll Gleichheit nach Inhalt gelten, überschreibst du equals und hashCode:

import java.util.*;
 
public class Punkt {
    private final int x, y;
    public Punkt(int x, int y) { this.x = x; this.y = y; }
 
    @Override public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Punkt p)) return false;
        return x == p.x && y == p.y;
    }
    @Override public int hashCode() { return Objects.hash(x, y); }
    @Override public String toString() { return "(" + x + "|" + y + ")"; }
 
    public static void main(String[] args) {
        Punkt a = new Punkt(1, 2), b = new Punkt(1, 2);
        System.out.println((a == b) + " " + a.equals(b));
        Set<Punkt> menge = new HashSet<>(List.of(a));
        System.out.println(menge.contains(b) + " " + menge);
    }
}

Ausgabe:

false true
true [(1|2)]

Wer equals überschreibt, muss hashCode passend mitüberschreiben, sonst funktionieren HashSet und HashMap nicht richtig. Records (nächste Kapitel) erledigen das automatisch.

Initialisierung und Reihenfolge

Beim Erzeugen eines Objekts passiert in dieser Reihenfolge: statische Teile der Klasse (einmal), Felder mit Anfangswerten, Initialisierungsblöcke, Konstruktor.

public class Reihenfolge {
    static { System.out.println("1 statischer Block (einmal)"); }
    private int a = init("2 Feld");
    { System.out.println("3 Initialisierungsblock"); }
    Reihenfolge() { System.out.println("4 Konstruktor"); }
    static int init(String s) { System.out.println(s); return 1; }
 
    public static void main(String[] args) {
        new Reihenfolge();
        new Reihenfolge();
    }
}

Ausgabe:

1 statischer Block (einmal)
2 Feld
3 Initialisierungsblock
4 Konstruktor
2 Feld
3 Initialisierungsblock
4 Konstruktor

Objekte im Speicher

  • Lokale Variablen mit primitiven Typen liegen im Stack; Objekte im Heap, die Variable enthält nur einen Verweis.
  • Der Garbage Collector gibt Objekte frei, auf die niemand mehr zeigt. Du musst nichts manuell löschen.
  • null bedeutet “kein Objekt”. Zugriff darauf löst eine NullPointerException aus.
public class Verweise {
    static class Box { int wert; Box(int w) { wert = w; } }
    public static void main(String[] args) {
        Box a = new Box(1);
        Box b = a;                  // beide zeigen auf dasselbe Objekt
        b.wert = 99;
        System.out.println(a.wert);
        a = null;                   // b hält das Objekt noch fest
        System.out.println(b.wert);
        String s = null;
        try { s.length(); } catch (NullPointerException e) { System.out.println("NPE"); }
    }
}

Ausgabe:

99
99
NPE

Optional statt null

Optional<T> macht “kein Wert” im Typ sichtbar:

import java.util.*;
 
public class Opt {
    static Optional<String> finde(List<String> l, String prefix) {
        return l.stream().filter(s -> s.startsWith(prefix)).findFirst();
    }
    public static void main(String[] args) {
        List<String> namen = List.of("Anna", "Ben", "Clara");
        System.out.println(finde(namen, "B").orElse("keiner") + " " + finde(namen, "X").orElse("keiner"));
        finde(namen, "C").ifPresent(n -> System.out.println("gefunden: " + n));
        System.out.println(finde(namen, "A").map(String::toUpperCase).get());
    }
}

Ausgabe:

Ben keiner
gefunden: Clara
ANNA

Merke

  • Klasse = Bauplan (Felder + Methoden), Objekt = Exemplar; new ruft den Konstruktor
  • Kapselung: Felder private, Zugriff über Methoden
  • this ist das aktuelle Objekt; static gehört zur Klasse
  • equals und hashCode immer gemeinsam überschreiben
  • Objekte liegen im Heap, Variablen halten Verweise; null vermeiden, Optional nutzen

Übungsaufgabe

Schreibe eine Klasse Rechteck mit Breite und Höhe (Konstruktor, Prüfung auf positive Werte), flaeche(), umfang() und toString().

Quiz zur Selbstkontrolle

Weiter im Kurs

Zurück: Collections: List, Set und Map

Weiter: Vererbung, Interfaces und Polymorphie

Alle Kapitel: Java im Überblick