No anulable (por defecto)
El experimento no anulable (por defecto) se puede encontrar actualmente en nullsafety.dartpad.dev .
Tenga en cuenta que puede leer la especificación completa aquí y la hoja de ruta completa aquí .
¿Qué significa no anulable por defecto?
void main() {
String word;
print(word); // illegal
word = 'Hello, ';
print(word); // legal
}
Como puede ver arriba, una variable que no es anulable por defecto significa que cada variable que se declara normalmente no puede serlo null
. En consecuencia, cualquier operación que acceda a la variable antes de que se le haya asignado es ilegal.
Además, la asignación null
a una variable no anulable tampoco está permitida:
void main() {
String word;
word = null; // forbidden
world = 'World!'; // allowed
}
¿Cómo esto me ayuda?
Si una variable no es anulable , puede estar seguro de que nunca lo es null
. Debido a eso, nunca necesita verificarlo de antemano.
int number = 4;
void main() {
if (number == null) return; // redundant
int sum = number + 2; // allowed because number is also non-nullable
}
Recuerda
Los campos de instancia en las clases deben inicializarse si no son anulables:
class Foo {
String word; // forbidden
String sentence = 'Hello, World!'; // allowed
}
Ver late
continuación para modificar este comportamiento.
Tipos anulables (?
)
Puede usar tipos anulables agregando un signo de interrogación ?
a un tipo variable:
class Foo {
String word; // forbidden
String? sentence; // allowed
}
No es necesario inicializar una variable anulable antes de poder usarla. Se inicializa como null
por defecto:
void main() {
String? word;
print(word); // prints null
}
!
Agregar !
a cualquier variable e
arrojará un error de tiempo de ejecución si e
es nulo y, de lo contrario, lo convertirá en un valor no anulablev
.
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
La palabra clave late
se puede usar para marcar variables que se inicializarán más adelante , es decir, no cuando se declaran sino cuando se accede a ellas. Esto también significa que podemos tener campos de instancia no anulables que se inicializan más tarde:
class ExampleState extends State {
late String word; // non-nullable
@override
void initState() {
super.initState();
// print(word) here would throw a runtime error
word = 'Hello';
}
}
Accediendo word
antes de que se inicialice arrojará un error de tiempo de ejecución.
late final
Las variables finales ahora también se pueden marcar tarde:
late final int x = heavyComputation();
Aquí heavyComputation
solo se llamará una vez que x
se acceda. Además, también puede declarar un late final
sin inicializador, que es lo mismo que tener solo una late
variable, pero solo se puede asignar una vez.
late final int x;
// w/e
x = 5; // allowed
x = 6; // forbidden
Tenga en cuenta que todas las variables de nivel superior o estáticas con un inicializador ahora se evaluarán late
, sin importar si lo son final
.
required
Anteriormente una anotación ( @required
), ahora incorporada como modificador. Permite marcar cualquier parámetro con nombre (para funciones o clases) comorequired
, lo que los hace no anulables:
void allowed({required String word}) => null;
Esto también significa que si un parámetro no puede ser anulado , debe marcarse comorequired
o tener un valor predeterminado:
void allowed({String word = 'World'}) => null;
void forbidden({int x}) // compile-time error because x can be null (unassigned)
=>
null;
Cualquier otro parámetro con nombre debe ser anulable :
void baz({int? x}) => null;
?[]
Se ?[]
agregó el operador nulo para el operador de índice []
:
void main() {
List<int>? list = [1, 2, 3];
int? x = list?[0]; // 1
}
Consulte también este artículo sobre la decisión de sintaxis .
?..
El operador en cascada ahora también tiene un nuevo operador con reconocimiento nulo: ?..
.
Hace que las siguientes operaciones en cascada solo se ejecuten si el destinatario no es nulo . Por lo tanto, ?..
debe ser el primer operador en cascada en una secuencia en cascada:
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
Para evitar confusiones: esto no es algo de lo que los desarrolladores tengan que preocuparse. Quiero mencionarlo en aras de la integridad.
Never
va a ser un tipo como el previamente existente Null
( nonull
) definido endart:core
. Ambas clases no se pueden ampliar, implementar o mezclar, por lo que no están destinadas a ser utilizadas.
Básicamente, Never
significa que no se permite ningún tipo y Never
que no se puede crear una instancia.
Nada más que Never
en List<Never>
satisface la restricción de tipo genérico de la lista, lo que significa que tiene que estar vacío . List<Null>
, sin embargo, puede contener 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)
];
Ejemplo: el compilador inferirá List<Never>
para un vacío const List<T>
.
Never
no se supone que sea utilizado por los programadores en lo que a mí respecta.
Never
se pueden usar?