Dart Kurs Klassendiagramme wiederholen

Lektion 22 - Operator Overloading

Operatoren fuer eigene Klassen definieren

Operator Overloading bedeutet: Du legst fest, wie Operatoren wie +, -, *, == oder [] bei deinen eigenen Klassen funktionieren. Dadurch kann objektorientierter Code natuerlicher und lesbarer werden.

1. Was ist Operator Overloading?

Normalerweise kennst du Operatoren aus Zahlen oder Texten. In Dart kannst du bestimmte Operatoren aber auch fuer eigene Klassen definieren.

final a = 3 + 4;              // int plus int
final b = 'Hallo ' + 'Dart';  // String plus String

final c = pointA + pointB;    // eigener Operator fuer eigene Klasse

Wichtig: Du veraenderst nicht den Operator selbst. Du definierst nur, was der Operator fuer deine Klasse bedeuten soll.

2. Syntax in Dart

In Dart schreibst du Operatoren wie Methoden, aber mit dem Schluesselwort operator.

Rueckgabetyp operator Symbol(Parameter) {
  return Ergebnis;
}
class Score {
  int value;

  Score(this.value);

  Score operator +(Score other) {
    return Score(value + other.value);
  }
}

other ist das Objekt rechts vom Operator. Bei a + b ist a das aktuelle Objekt und b der Parameter.

3. Beispiel: Punkte addieren

Stell dir ein Lernsystem vor, in dem Aufgaben Punkte bringen. Zwei Punktwerte sollen addiert werden koennen.

class Score {
  final int value;

  Score(this.value);

  Score operator +(Score other) {
    return Score(value + other.value);
  }

  @override
  String toString() {
    return '$value Punkte';
  }
}

void main() {
  final homework = Score(20);
  final quiz = Score(15);

  final total = homework + quiz;
  print(total); // 35 Punkte
}

Ohne Operator Overloading muesstest du zum Beispiel homework.add(quiz) schreiben. Mit + liest es sich wie eine echte Addition.

4. Operatoren sollen logisch bleiben

Operator Overloading ist nur gut, wenn die Bedeutung sofort nachvollziehbar ist. + sollte addieren, verbinden oder kombinieren. Es sollte nicht loeschen, speichern oder etwas Ueberraschendes tun.

SinnvollVerwirrend
Vector + Vector addiert KoordinatenUser + User loescht einen Account
Score + Score addiert PunkteScore + Score startet eine App
Money + Money addiert BetraegeMoney + Money sendet eine Rechnung

5. Beispiel: Vector mit + und -

Bei mathematischen Klassen ist Operator Overloading besonders gut lesbar.

class Vector2 {
  final double x;
  final double y;

  const Vector2(this.x, this.y);

  Vector2 operator +(Vector2 other) {
    return Vector2(x + other.x, y + other.y);
  }

  Vector2 operator -(Vector2 other) {
    return Vector2(x - other.x, y - other.y);
  }

  @override
  String toString() {
    return 'Vector2($x, $y)';
  }
}

void main() {
  final start = Vector2(10, 5);
  final movement = Vector2(3, -2);

  print(start + movement); // Vector2(13, 3)
  print(start - movement); // Vector2(7, 7)
}

6. Vergleich mit ==

Bei eigenen Klassen prueft == ohne Anpassung oft nicht das, was Lernende erwarten. Zwei verschiedene Objekte mit gleichen Werten sind nicht automatisch gleich.

class UserId {
  final int value;

  const UserId(this.value);

  @override
  bool operator ==(Object other) {
    return other is UserId && other.value == value;
  }

  @override
  int get hashCode {
    return value.hashCode;
  }
}

Wenn du == ueberschreibst, solltest du auch hashCode ueberschreiben. Das ist wichtig fuer Collections wie Set und Map.

7. Warum hashCode wichtig ist

Collections muessen Objekte schnell finden koennen. hashCode ist dafuer eine Art Kurzkennung. Wenn zwei Objekte mit == gleich sind, muessen sie auch denselben hashCode haben.

final first = UserId(7);
final second = UserId(7);

print(first == second); // true

Ohne passenden hashCode koennen Sets und Maps unerwartet reagieren.

8. Zugriff mit []

Der Index-Operator [] kann sinnvoll sein, wenn deine Klasse wie eine Sammlung oder Liste wirken soll.

class LessonCollection {
  final List<String> lessons;

  LessonCollection(this.lessons);

  String operator [](int index) {
    return lessons[index];
  }
}

void main() {
  final course = LessonCollection(['Dart', 'Flutter']);
  print(course[0]); // Dart
}

course[0] ruft intern deine Methode operator [] auf.

9. Wert setzen mit []=

Wenn eine Klasse Elemente veraendern darf, kannst du auch den Set-Operator definieren.

class LessonCollection {
  final List<String> lessons;

  LessonCollection(this.lessons);

  String operator [](int index) {
    return lessons[index];
  }

  void operator []=(int index, String value) {
    lessons[index] = value;
  }
}

void main() {
  final course = LessonCollection(['Dart', 'Flutter']);
  course[1] = 'Flutter Widgets';
  print(course[1]);
}

10. Welche Operatoren kann man ueberladen?

Dart erlaubt Operator Overloading fuer eine feste Auswahl. Du kannst keine komplett neuen Operator-Symbole erfinden.

operator +
operator -
operator *
operator /
operator %
operator ==
operator <
operator <=
operator >
operator >=
operator []
operator []=

Operatoren wie && und || kannst du nicht fuer eigene Klassen ueberladen.

11. Operator Overloading vs. Methode

Nicht alles braucht einen Operator. Manchmal ist eine normale Methode klarer.

Nutze OperatorNutze Methode
Wenn es wie Mathe oder Vergleich wirkt.Wenn die Aktion einen Namen braucht.
scoreA + scoreBuser.save()
moneyA == moneyBcourse.publish()

12. Operator planen

Bevor du einen Operator programmierst, beschreibe erst, was links und rechts vom Operator steht und welches Ergebnis entstehen soll.

Operator + fuer Score:
  Linker Score hat value
  Rechter Score hat value
  Addiere beide Werte
  Erzeuge neuen Score mit Ergebnis
  Gib neuen Score zurueck
Score operator +(Score other) {
  return Score(value + other.value);
}

13. Typische Fehler

FehlerErklaerung
Operator macht etwas UnerwartetesCode wird schwer lesbar.
== ohne hashCodeSets und Maps koennen falsch reagieren.
Objekt wird heimlich veraendertBei + erwartet man oft ein neues Ergebnis.
Zu viele OperatorenNormale Methoden sind manchmal klarer.

14. Aufgaben zu Operator Overloading

  1. Erstelle eine Klasse Score mit value und ueberlade +.
  2. Erweitere Score um -, damit Punkte abgezogen werden koennen.
  3. Ueberschreibe toString(), damit print(score) lesbar ist.
  4. Erstelle eine Klasse Money mit amount und currency.
  5. Ueberlade == und hashCode fuer Money.
  6. Schreibe zuerst eine kurze Planung fuer jeden Operator.
Planung fuer Money ==:

Wenn other kein Money ist:
  Gib false zurueck

Wenn amount gleich other.amount ist
und currency gleich other.currency ist:
  Gib true zurueck

Sonst:
  Gib false zurueck
Weiter zu Entwicklerdokumentation