Dart: Typumwandlungen und statische Variablen
Zuletzt aktualisiert:
7 Min. Lesezeit

Allen, die mit Flutter anfangen, sage ich in den ersten Stunden dasselbe: Wer ein paar grundlegende Verhaltensweisen von Dart verinnerlicht, bevor er Widgets anfasst, spart in den folgenden Wochen viele Stunden. Dieser Beitrag behandelt zwei davon. Erstens Typumwandlungen, denn fast alles, was Sie in Flutter bekommen (der Inhalt eines TextField, eine JSON-Antwort), kommt selten im gewünschten Typ an. Zweitens static-Member, denn die Frage „Gehört dieser Wert zum Objekt oder zur Klasse?“ richtig zu beantworten, ist der erste Schritt beim Klassenentwurf.
Live-Demo: Typumwandlungen in Dart
Probieren Sie Typumwandlungen und statische Konzepte interaktiv aus:
Warum wandelt Dart nicht automatisch um?
Dart ist statisch typisiert und wandelt Typen niemals stillschweigend um. Das überrascht vor allem Lernende, die von JavaScript kommen:
void main() {
// int zahl = "3"; // Kompilierfehler: String kann nicht int zugewiesen werden
// print("Alter: " + 25); // Kompilierfehler: int kann nicht an String angehängt werden
final alter = 25;
print("Alter: $alter"); // Richtig: String-Interpolation
}Diese Strenge ist kein Hindernis, sondern eine Garantie: Sie schreiben jede Umwandlung ausdrücklich hin und wissen deshalb auch, wo ein Fehler entstehen kann.
Von String zu Zahl: parse und tryParse
Jeder Zahlentyp hat eine eigene parse-Methode, um Text in eine Zahl zu verwandeln:
void main() {
final a = int.parse('42'); // 42
final b = double.parse('3.14'); // 3.14
final c = num.parse('7'); // 7 (int)
final d = num.parse('7.5'); // 7.5 (double)
final hex = int.parse('ff', radix: 16); // 255
final binaer = int.parse('1010', radix: 2); // 10
print('$a $b $c $d $hex $binaer');
}Die parse-Methoden werfen eine FormatException, wenn der Text keine gültige Zahl ist. Sowohl int.parse('3a') als auch int.parse('3.5') schlagen fehl; Letzteres überrascht Lernende am meisten, denn „3.5“ ist zwar eine Zahl, aber keine Ganzzahl.
Für Daten, denen Sie nicht trauen können, nehmen Sie tryParse. Statt zu werfen, liefert es null, und Sie geben mit ?? einen Standardwert vor:
void main() {
final zahl = int.tryParse('3a'); // null
final sicher = int.tryParse('3a') ?? 0; // 0
print('$zahl $sicher');
}Manchmal ist es besser, den Fehler abzufangen; dazu mehr im Beitrag try-catch und async/await in Dart. Als Faustregel: Ist ungültige Eingabe ein erwarteter Fall, nehmen Sie tryParse; ist sie wirklich eine Ausnahme, parse zusammen mit try-catch.
Benutzereingaben umwandeln
In Flutter liefert ein TextField immer einen String. Eine Funktion, die mit dem Alter rechnet, könnte so aussehen:
String zukunftsAlterText(String eingabe) {
final alter = int.tryParse(eingabe.trim());
if (alter == null) return 'Bitte geben Sie Ihr Alter als Zahl ein.';
if (alter < 0 || alter > 130) return 'Bitte geben Sie ein gültiges Alter ein.';
// Hier ist alter nicht mehr int?, sondern int: Die Null-Prüfung hat den Typ eingegrenzt.
return 'In 5 Jahren sind Sie ${alter + 5}.';
}
void main() {
print(zukunftsAlterText(' 25 ')); // In 5 Jahren sind Sie 30.
print(zukunftsAlterText('zwanzig')); // Bitte geben Sie Ihr Alter als Zahl ein.
}Darin stecken drei Gewohnheiten: die Eingabe mit trim() bereinigen, mit tryParse umwandeln und prüfen, ob der Wert in einem sinnvollen Bereich liegt. Das Letzte wird oft vergessen; -5 ist eine gültige Ganzzahl, aber kein gültiges Alter.
Auch der Kommentar in der letzten Zeile ist wichtig: Nach der Prüfung alter == null weiß Dart von selbst, dass die Variable int und nicht int? ist. Das nennt man Type Promotion, und es macht den !-Operator überflüssig.
Von Zahl zu String
Die Gegenrichtung ist einfacher, aber die Formatierung ist meist die eigentliche Arbeit:
void main() {
final preis = 1249.5;
print(preis.toString()); // 1249.5
print(preis.toStringAsFixed(2)); // 1249.50
print('Summe: ${preis.toStringAsFixed(2)} €');
print(7.toString().padLeft(2, '0')); // 07 (für Stunden/Minuten)
}toStringAsFixed liefert einen String; rechnen Sie mit dem Ergebnis nicht weiter. Brauchen Sie ländertypische Formate wie Währungszeichen oder Tausendertrennzeichen, ist die Klasse NumberFormat aus dem Paket intl genau dafür gemacht.
Zwischen int und double
Bei Zahlenliteralen ist Dart großzügig: double x = 3; ist gültig. Eine int-Variable lässt sich einer double-Variable aber nicht direkt zuweisen:
void main() {
int anzahl = 3;
// double anteil = anzahl; // Kompilierfehler
double anteil = anzahl.toDouble();
const wert = 3.7;
print(wert.toInt()); // 3 (schneidet die Nachkommastellen ab)
print(wert.round()); // 4 (rundet zum nächsten Wert)
print(wert.floor()); // 3 (abrunden)
print(wert.ceil()); // 4 (aufrunden)
print((-3.7).toInt()); // -3 (schneidet in Richtung null ab)
print((-3.7).floor()); // -4
print(-3.7.floor()); // -3 (Achtung: erst 3.7.floor(), dann das Minuszeichen)
print(anteil);
}Die letzte Zeile ist einer der Irrtümer, die ich im Unterricht am häufigsten sehe: Das Minuszeichen wird erst nach dem Methodenaufruf angewendet. Vergessen Sie bei negativen Zahlen die Klammern nicht.
Bei der Division lauert eine ähnliche Falle. Der Operator / liefert immer einen double:
void main() {
final summe = 7;
final anzahl = 2;
// int schnitt = summe / anzahl; // Kompilierfehler: Ergebnis ist double
print(summe / anzahl); // 3.5
print(summe ~/ anzahl); // 3 (Ganzzahldivision)
print(summe % anzahl); // 1 (Rest)
}as, is und Patterns: Prüfung statt Umwandlung
Ein Punkt, den Lernende ständig verwechseln: as ist kein Umwandlungsoperator. Es macht aus einem Wert keinen anderen Typ, sondern teilt dem Compiler nur mit: „Dieser Wert hat bereits diesen Typ.“ Liegen Sie falsch, gibt es zur Laufzeit einen TypeError:
void main() {
Object wert = '42';
// final zahl = wert as int; // TypeError: ein String ist kein int
if (wert is String) {
// In diesem Block ist wert automatisch auf String eingegrenzt.
final zahl = int.parse(wert);
print(zahl + 1); // 43
}
}Die Zahl 42 aus dem Text '42' bekommen Sie also mit int.parse, nicht mit as. is dagegen prüft den Typ und grenzt die Variable innerhalb des Blocks auf diesen Typ ein.
In echten Projekten begegnet Ihnen das vor allem bei JSON. jsonDecode liefert eine Map<String, dynamic>, und ein Preisfeld kommt einmal als 20, ein andermal als 19.9 an. Das Erste wird als int, das Zweite als double dekodiert; Code mit json['preis'] as double bricht im ersten Fall. Switch-Ausdrücke und Patterns aus Dart 3 lösen das sauber:
double preisLesen(Object? roh) {
return switch (roh) {
int n => n.toDouble(),
double n => n,
String s => double.tryParse(s) ?? 0.0,
_ => 0.0,
};
}
void main() {
final json = <String, dynamic>{'name': 'Stift', 'preis': 20};
print(preisLesen(json['preis'])); // 20.0
print(preisLesen('19.9')); // 19.9
print(preisLesen(null)); // 0.0
}Es gibt auch einen kürzeren Weg: Bei einem Feld, das sicher numerisch ist, akzeptiert (json['preis'] as num).toDouble() sowohl int- als auch double-Werte.
Mit Dart 3.0 kamen außerdem bool.parse und bool.tryParse hinzu. Standardmäßig erkennen sie nur die Texte 'true' und 'false'; mit caseSensitive: false wird die Groß- und Kleinschreibung ignoriert.
Statische Member: Sie gehören zur Klasse, nicht zum Objekt
Ein normales Feld (Instanzfeld) existiert in jedem Objekt als eigene Kopie. Ein static-Feld gehört dagegen zur Klasse, und davon gibt es genau eine Kopie, egal wie viele Objekte Sie erzeugen.
Übertragen wir das Diagramm in Code:
class Zaehler {
static int summe = 0; // Gehört zur Klasse, eine Kopie
int wert = 0; // In jedem Objekt getrennt
void erhoehen() {
wert++;
summe++; // Innerhalb der Klasse ohne Präfix erreichbar
}
}
void main() {
final z1 = Zaehler();
final z2 = Zaehler();
z1.erhoehen();
z1.erhoehen();
z2.erhoehen();
print(z1.wert); // 2
print(z2.wert); // 1
print(Zaehler.summe); // 3
// print(z1.summe); // Kompilierfehler: kein Zugriff auf static über eine Instanz
}Von außerhalb der Klasse erreichen Sie ein statisches Member nur über den Klassennamen (Zaehler.summe). Wer von Java kommt, ist den Zugriff über eine Instanz vielleicht gewohnt; Dart erlaubt das nicht.
static const, static final und statische Variablen
Alle drei gehören zur Klasse; sie unterscheiden sich darin, wann der Wert feststeht und ob er sich ändern kann:
class Einstellungen {
static const maxBenutzername = 20; // Zur Kompilierzeit bekannt
static const anfrageTimeout = Duration(seconds: 10);
static final gestartetAm = DateTime.now(); // Beim ersten Zugriff einmal berechnet
static int startAnzahl = 0; // Veränderbar
}static const: Der Wert muss zur Kompilierzeit feststehen. Die richtige Wahl für feste Grenzen, Zeitspannen und Schlüsselnamen.static final: Der Wert wird zur Laufzeit beim ersten Zugriff einmal berechnet und ändert sich danach nicht mehr. Statische Felder werden in Dart ohnehin erst initialisiert, wenn sie benutzt werden.- Veränderbare Felder wie
static int: Sie erzeugen einen Zustand, den die ganze App teilt. In kleinen Beispielen harmlos, doch wer die Daten eines Flutter-Bildschirms in einem solchen Feld hält, bekommt Probleme, etwa dass die Oberfläche von Änderungen nichts erfährt. Dafür gibt es State-Management-Werkzeuge wie Provider.
Frühere Fassungen dieses Beitrags enthielten ein Beispiel wie static late int wert;. late verspricht dem Compiler hier: „Ich weise den Wert zu, bevor ich ihn lese.“ Wird das Versprechen gebrochen, gibt es einen LateInitializationError. Können Sie einen Startwert angeben (static int wert = 0;), brauchen Sie kein late.
Statische Methoden
Auch statische Methoden gehören zur Klasse und laufen nicht auf einem Objekt. Deshalb gibt es in ihnen kein this, und sie können nicht auf Instanzfelder zugreifen:
class Temperatur {
final double celsius;
const Temperatur(this.celsius);
static double ausFahrenheit(double f) => (f - 32) * 5 / 9;
// static void ausgeben() => print(celsius); // Fehler: kein this in static
}
void main() {
print(Temperatur.ausFahrenheit(212)); // 100.0
}Nicht alles in eine statische Klasse packen
Lernende mit Java- oder C#-Hintergrund sammeln Hilfsfunktionen oft so:
class Calculation {
static double add(double a, double b) => a + b;
static double subtract(double a, double b) => a - b;
}In Dart ist das unnötig, denn Funktionen müssen nicht in einer Klasse stehen. Auch der Leitfaden Effective Dart empfiehlt, Klassen zu vermeiden, die nur statische Member enthalten. Top-Level-Funktionen erledigen dieselbe Aufgabe einfacher:
double add(double a, double b) => a + b;
double subtract(double a, double b) => a - b;
void main() {
print(add(5, 3)); // 8.0
}Die Ausnahme im Leitfaden ist das Gruppieren zusammengehöriger Konstanten; die Klasse Einstellungen oben ist so ein Fall. Eine feste Menge zusammengehöriger Werte lässt sich aber oft besser als Enum ausdrücken.
Häufige Fehler
- Benutzereingaben mit
parsestatttryParseund ohnetrim()umwandeln. - Glauben,
as intkönne einen String in eine Zahl verwandeln. - Das Ergebnis von
/einerint-Variable zuweisen; für Ganzzahldivision gibt es~/. - Bei negativen Zahlen die Klammern vergessen:
-3.7.floor()und(-3.7).floor()liefern unterschiedliche Ergebnisse. - Eine JSON-Zahl mit
as doublelesen; der Weg übernumundtoDouble()ist sicherer. - Den Zustand, den ein Bildschirm anzeigt, in veränderbaren statischen Feldern halten.
Nächster Schritt
Sitzen Typen und statische Member, geht es natürlich weiter mit Enums und Composition, die feste Wertemengen modellieren. Ist das Dart-Fundament komplett, wechseln Sie mit Widget-Baum und Layout in Flutter auf die Seite der Oberfläche.
Ressourcen
Kontaktieren Sie mich:
Häufig gestellte Fragen
Was ist der Unterschied zwischen int.parse und int.tryParse?
int.parse wirft eine FormatException, wenn der Text keine gültige Zahl ist. int.tryParse gibt stattdessen null zurück und ist deshalb bei Daten, denen Sie nicht trauen können – etwa Nutzereingaben –, die praktischere Wahl.
Kann ich einen String mit as in einen int umwandeln?
Nein. as wandelt nichts um, sondern behauptet nur, dass der Wert diesen Typ bereits hat. '42' as int wirft zur Laufzeit einen TypeError; um aus Text eine Zahl zu gewinnen, verwenden Sie int.parse oder int.tryParse.
Kann ich über ein Objekt auf eine statische Variable zugreifen?
Nein. Von außerhalb der Klasse erreichen Sie statische Member in Dart nur über den Klassennamen (Zaehler.summe); der Zugriff über eine Instanz kompiliert nicht. Innerhalb der eigenen Methoden der Klasse können Sie sie ohne Präfix verwenden.
Was ist der Unterschied zwischen static final und static const?
const verlangt, dass der Wert schon zur Kompilierzeit feststeht. Ein static final-Feld wird zur Laufzeit beim ersten Zugriff einmal berechnet und ändert sich danach nicht mehr.
Verwandte Artikel
Flutter-Kamera ohne erzwungene Ausrichtung: drei Fehler und ihre Lösungen
Gedrehte Vorschau, Einfrieren beim Aufnahmestart, tote Kamera: drei Flutter-Kamerafehler aus Recapday und der Code, der sie behoben hat.
Dart: Interface und implements richtig einsetzen
Verträge mit implements in Dart, interface class und abstract interface class aus Dart 3, der Unterschied zu Mixins mit with und Beispiel für testbaren Code.
Dart: Vererbung und Polymorphie – die Eltern-Kind-Beziehung
Vererbung in Dart mit extends, super und @override, Polymorphie, abstrakte Klassen, sichere Typprüfung und die Klassenmodifikatoren sealed, final und base.