Uważam, że odpowiedź Lasse na blogu Chrisa Stormsa to świetne wyjaśnienie.
Mam nadzieję, że nie przeszkadza im to, że kopiuję zawartość.
To jest dobre wyjaśnienie końcowych pól, ale tak naprawdę nie wyjaśnia konstruktorów const. Nic w tych przykładach nie używa, że konstruktory są konstruktorami const. Każda klasa może mieć końcowe pola, konstruktory const lub nie.
Pole w Dart jest w rzeczywistości anonimową lokalizacją pamięci połączoną z automatycznie utworzonym geterem i ustawiaczem, który odczytuje i aktualizuje pamięć, a także może być zainicjowany na liście inicjalizacyjnej konstruktora.
Ostatnie pole jest takie samo, tylko bez metody ustawiającej, więc jedyny sposób na ustawienie jego wartości znajduje się na liście inicjalizatora konstruktora i nie ma możliwości zmiany wartości po tym - stąd „ostateczna”.
Celem konstruktorów const nie jest inicjowanie pól końcowych, każdy konstruktor generujący może to zrobić. Chodzi o to, aby utworzyć wartości stałych czasu kompilacji: obiekty, w których wszystkie wartości pól są znane już w czasie kompilacji, bez wykonywania jakichkolwiek instrukcji.
To nakłada pewne ograniczenia na klasę i konstruktora. Konstruktor const nie może mieć treści (żadne instrukcje nie są wykonywane!), A jego klasa nie może mieć żadnych pól innych niż końcowe (wartość, którą „znamy” w czasie kompilacji, nie może być później zmieniona). Lista inicjalizacyjna musi również inicjalizować pola tylko na inne stałe czasu kompilacji, więc prawa strona jest ograniczona do „wyrażeń stałych czasu kompilacji” [1]. Musi być poprzedzony przedrostkiem „const” - w przeciwnym razie po prostu otrzymasz normalnego konstruktora, który spełni te wymagania. To jest w porządku, po prostu nie jest to konstruktor const.
Aby użyć konstruktora const do faktycznego utworzenia obiektu stałego czasu kompilacji, zamień „new” na „const” w „nowym” -wyrażeniu. Nadal możesz używać "new" z konstruktorem const i nadal będzie tworzył obiekt, ale będzie to po prostu normalny nowy obiekt, a nie stała czasu kompilacji. To znaczy: Konstruktor const może być również używany jako normalny konstruktor do tworzenia obiektów w czasie wykonywania, a także do tworzenia obiektów stałych czasu kompilacji w czasie kompilacji.
A więc jako przykład:
class Point {
static final Point ORIGIN = const Point(0, 0);
final int x;
final int y;
const Point(this.x, this.y);
Point.clone(Point other): x = other.x, y = other.y; //[2]
}
main() {
// Assign compile-time constant to p0.
Point p0 = Point.ORIGIN;
// Create new point using const constructor.
Point p1 = new Point(0, 0);
// Create new point using non-const constructor.
Point p2 = new Point.clone(p0);
// Assign (the same) compile-time constant to p3.
Point p3 = const Point(0, 0);
print(identical(p0, p1)); // false
print(identical(p0, p2)); // false
print(identical(p0, p3)); // true!
}
Stałe czasu kompilacji są kanonizowane. Oznacza to, że bez względu na to, ile razy napiszesz „const Point (0,0)”, utworzysz tylko jeden obiekt. Może to być przydatne - ale nie tak bardzo, jak mogłoby się wydawać, ponieważ możesz po prostu utworzyć zmienną const, która będzie przechowywać wartość i zamiast tego użyć zmiennej.
A więc do czego służą stałe czasu kompilacji?
- Są przydatne w przypadku wyliczeń.
- W przypadkach przełączników można używać wartości stałych czasu kompilacji.
- Są używane jako adnotacje.
Stałe czasu kompilacji były ważniejsze, zanim Dart przełączył się na leniwe inicjowanie zmiennych. Wcześniej można było zadeklarować tylko zainicjowaną zmienną globalną, taką jak „var x = foo;” jeśli "foo" było stałą w czasie kompilacji. Bez tego wymagania większość programów można napisać bez używania obiektów stałych
Krótkie podsumowanie: konstruktory Const służą tylko do tworzenia wartości stałych czasu kompilacji.
/ L
[1] Albo tak naprawdę: „Potencjalnie wyrażenia stałe w czasie kompilacji”, ponieważ może również odnosić się do parametrów konstruktora. [2] Więc tak, klasa może mieć jednocześnie konstruktory const i non-const.