Brak wartości zerowej (domyślnie)
Eksperyment bez wartości zerowej (domyślnie) można obecnie znaleźć na stronie nullsafety.dartpad.dev .
Pamiętaj, że możesz przeczytać pełną specyfikację tutaj i pełną mapę drogową tutaj .
Co domyślnie oznacza wartość zerowa?
void main() {
String word;
print(word); // illegal
word = 'Hello, ';
print(word); // legal
}
Jak widać powyżej, zmienna domyślnie nie ma wartości zerowej oznacza, że każda deklarowana normalnie nie może być null
. W konsekwencji każda operacja dostępu do zmiennej przed jej przypisaniem jest nielegalna.
Ponadto przypisywanie null
do zmiennej nie dopuszczającej wartości zerowej jest również niedozwolone:
void main() {
String word;
word = null; // forbidden
world = 'World!'; // allowed
}
Jak mi to pomaga?
Jeśli zmienna nie ma wartości zerowej , możesz być pewien, że nigdy nie jest null
. Z tego powodu nigdy nie musisz tego sprawdzać wcześniej.
int number = 4;
void main() {
if (number == null) return; // redundant
int sum = number + 2; // allowed because number is also non-nullable
}
Zapamiętaj
Pola instancji w klasach należy zainicjować, jeśli nie mają wartości zerowej:
class Foo {
String word; // forbidden
String sentence = 'Hello, World!'; // allowed
}
Zobacz late
poniżej, aby zmodyfikować to zachowanie.
Typy zerowalne ( ?
)
Możesz użyć typów zerowalnych , dodając znak zapytania ?
do typu zmiennej:
class Foo {
String word; // forbidden
String? sentence; // allowed
}
Pustych zmienna nie musi być zainicjowany zanim zostanie użyty. Jest on inicjowany null
domyślnie:
void main() {
String? word;
print(word); // prints null
}
!
Dołączanie !
do dowolnej zmiennej e
rzuci runtime error , jeśli e
jest zerowy, a inaczej przekształcić go non wartości pustych wartości v
.
void main() {
int? e = 5;
int v = e!; // v is non-nullable; would throw an error if e were null
String? word;
print(word!); // throws runtime error if word is null
print(null!); // throws runtime error
}
late
Słowa kluczowego late
można użyć do oznaczenia zmiennych, które zostaną zainicjowane później , tj. Nie wtedy, gdy zostaną zadeklarowane, ale kiedy zostaną udostępnione. Oznacza to również, że możemy mieć niepuste pola instancji, które są inicjowane później:
class ExampleState extends State {
late String word; // non-nullable
@override
void initState() {
super.initState();
// print(word) here would throw a runtime error
word = 'Hello';
}
}
Uzyskiwanie dostępu word
przed jego zainicjowaniem spowoduje wygenerowanie błędu czasu wykonywania.
late final
Zmienne końcowe można teraz także oznaczać późno:
late final int x = heavyComputation();
Tutaj heavyComputation
zostanie wywołany tylko raz x
. Dodatkowo możesz również zadeklarować opcję „ late final
bez inicjatora”, co jest równoznaczne z posiadaniem tylko late
zmiennej, ale można ją przypisać tylko raz.
late final int x;
// w/e
x = 5; // allowed
x = 6; // forbidden
Zauważ, że wszystkie zmienne najwyższego poziomu lub statyczne z inicjatorem będą teraz oceniane late
, bez względu na to, czy są final
.
required
Dawniej adnotacja ( @required
), teraz wbudowana jako modyfikator. Pozwala oznaczyć dowolny nazwany parametr (dla funkcji lub klas) jako required
, co powoduje, że nie mają one wartości zerowej:
void allowed({required String word}) => null;
Oznacza to również, że jeśli parametr nie ma wartości zerowej , należy go oznaczyć jako required
lub mieć wartość domyślną:
void allowed({String word = 'World'}) => null;
void forbidden({int x}) // compile-time error because x can be null (unassigned)
=>
null;
Każdy inny nazwany parametr musi mieć wartość null :
void baz({int? x}) => null;
?[]
?[]
Dla operatora indeksu dodano operator o wartości NULL []
:
void main() {
List<int>? list = [1, 2, 3];
int? x = list?[0]; // 1
}
Zobacz także ten artykuł na temat decyzji dotyczącej składni .
?..
Operator kaskada ma teraz również nowy operator zerowej świadomy: ?..
.
Powoduje to, że następujące operacje kaskadowe są wykonywane tylko wtedy, gdy odbiorca nie ma wartości null . Dlatego ?..
musi być pierwszym operatorem kaskadowym w sekwencji kaskadowej:
void main() {
Path? path;
// Will not do anything if path is null.
path
?..moveTo(3, 4)
..lineTo(4, 3);
// This is a noop.
(null as List)
?..add(4)
..add(2)
..add(0);
}
Never
Aby uniknąć zamieszania: programiści nie muszą się tym martwić. Chcę o tym wspomnieć w celu uzupełnienia.
Never
będzie typem takim jak poprzednio Null
( nienull
) zdefiniowany w dart:core
. Obie te klasy nie mogą być rozszerzane, implementowane ani mieszane, więc nie są przeznaczone do użycia.
Zasadniczo Never
oznacza, że żaden typ nie jest dozwolony i Never
sama nie może być utworzona.
Nic jednak Never
w ciągu List<Never>
spełnia ogólny typ ograniczenie listy, co oznacza, że musi być pusty . List<Null>
może jednak zawierać null
:
// Only valid state: []
final neverList = <Never>[
// Any value but Never here will be an error.
5, // error
null, // error
Never, // not a value (compile-time error)
];
// Can contain null: [null]
final nullList = <Null>[
// Any value but Null will be an error.
5, // error
null, // allowed
Never, // not a value (compile-time error)
Null, // not a value (compile-time error)
];
Przykład: kompilator będzie wnioskować List<Never>
o pustym const List<T>
.
Never
moim zdaniem nie powinien być używany przez programistów.
Never
można je wykorzystać?