Variables en Dart

Variables

Aprende sobre las variables en Dart. A continuación ejemplos de creación e inicialización de variables:

var name = 'Jack';Lenguaje del código: JavaScript (javascript)

Las variables almacenan referencias. La variable llamada name guarda una referencia que apunta al objeto de cadena con valor «Bob».

Una referencia es una dirección en memoria. Para facilitar el almacenamiento de datos, cada zona de memoria tiene una dirección asignada, similar al número de puerta de una casa.

El compilador deduce automáticamente que el tipo de name es String, pero también puedes especificar el tipo manualmente para modificar la deducción. Si una variable necesita almacenar objetos de varios tipos, declara su tipo como Object (también puedes usar dynamic en caso necesario).

Object name = 'Jack';Lenguaje del código: JavaScript (javascript)

Otra forma es indicar el tipo explícitamente (por ejemplo, sabemos que un nombre es texto y se guarda como String, así que lo especificamos directamente):

String name = 'Jack';Lenguaje del código: JavaScript (javascript)

Seguridad nula (Null safety)

La seguridad nula evita errores causados por acceder accidentalmente a variables con valor null, conocidos como errores de desreferencia nula (null dereference error). Ocurren cuando accedes a una propiedad o llamas un método sobre una expresión cuyo resultado es null.

Existe una excepción: si null soporta esa propiedad o método de forma nativa (como toString(), hashCode), no se producirá error. Gracias a la seguridad nula, el compilador de Dart detecta estos posibles fallos en tiempo de compilación.

Quienes tengan experiencia en programación sabrán que los errores de referencia nula son muy frecuentes. Si se pueden evitar en fase de compilación, la estabilidad y robustez del programa aumentan considerablemente.

Ejemplo: supongamos que quieres obtener el valor absoluto de una variable i de tipo int. Si i vale null, ejecutar i.abs() provocará un error de desreferencia nula. En otros lenguajes este fallo solo aparece en tiempo de ejecución; en cambio, el compilador de Dart bloquea directamente esta llamada inválida y detecta el problema anticipadamente.

Tres cambios fundamentales introducidos por la seguridad nula:

1 Tipos anulables: al indicar un tipo, puedes controlar si admite valores nulos añadiendo un signo de interrogación al final del tipo.

String? name  // Tipo anulable: puede valer null o una cadena
String name   // Tipo no anulable: no admite null, solo almacena cadenasLenguaje del código: JavaScript (javascript)

En muchos lenguajes de programación, una variable string name puede almacenar null, pero en Dart no es posible. Para admitir null debes usar el tipo string?.

2 Las variables deben inicializarse antes de usarse. Las variables anulables tienen por defecto el valor null, lo que equivale a una inicialización automática; en cambio, los tipos no anulables no tienen valor por defecto y exigen asignar un valor inicial manualmente. Dart prohíbe acceder a variables sin inicializar para evitar problemas.

void main() {
  int year;        // Error: variable no anulable sin inicializar
  print(year);     // Error de compilación: year no ha sido inicializada
}
Lenguaje del código: Dart (dart)

El código anterior generará un error al compilar

C:\dartdemo\firstdart>dart run
Building package executable...
Failed to build firstdart:firstdart:
bin/firstdart.dart:7:9: Error: Non-nullable variable 'year' must be assigned before it can be used.
  print(year);     // Error de compilación: year no inicializada
        ^^^^Lenguaje del código: JavaScript (javascript)

En cambio, las variables anulables sí permiten esta escritura:

void main() {
  int? age;        // Variable anulable, valor por defecto null
  print(age);      // Muestra null sin errores
  
  //age = 18;        // Asignación manual
  //print(age);      // Muestra 18
}Lenguaje del código: JavaScript (javascript)

3 No se puede acceder directamente a propiedades ni llamar métodos sobre expresiones de tipo anulable. Igualmente hay excepciones: se permiten llamadas a métodos y propiedades soportados nativamente por null (hashCode, toString()).

void main() {
  String? name; // Tipo anulable, valor por defecto null

  // Error: acceder directamente a length causa fallo
  // print(name.length);

  // Correcto: se permiten métodos nativos de null
  print(name.toString());  // Muestra "null"
  print(name.hashCode);    // Muestra un número entero

  // Correcto: usar operadores de seguridad nula
  print(name?.length);     // Muestra null sin errores
}Lenguaje del código: PHP (php)

Valores por defecto

Las variables anulables sin asignación manual tienen como valor inicial predeterminado null. Incluso las variables numéricas valen null por defecto,

ya que en Dart los números, al igual que todos los datos, son objetos en esencia.

void main() {
  int? lineCount;
  assert(lineCount == null);
}Lenguaje del código: JavaScript (javascript)

Aviso: el chequeo assert() se ignora en código de producción; en desarrollo, si la condición dentro de assert() no se cumple, el programa lanza una excepción

Con la seguridad nula activada, las variables no anulables deben inicializarse antes de usarse:

int lineCount = 0;

Las variables locales no necesitan asignarse en la misma línea de declaración, pero sí antes de usarse. El siguiente código es válido porque Dart analiza que al ejecutar print(), lineCount siempre tendrá un valor no nulo:

int lineCount;

if (weLikeToCount) {
  lineCount = countLines();
} else {
  lineCount = 0;
}

print(lineCount);

Lenguaje del código: Dart (dart)

Ten en cuenta que en el código anterior lineCount = countLines(); y lineCount = 0; son asignaciones a lineCount, no usos del valor. «Usar» significa leer el contenido de la variable. Por ejemplo, el siguiente código generará un error:

void main() {
	int lineCount;

	print(lineCount);
}
Lenguaje del código: Dart (dart)

Error de compilación

Las variables de nivel superior y los miembros de clase usan inicialización diferida: solo se ejecuta la lógica de asignación la primera vez que se acceden.

Modificador late para variables

El modificador late tiene dos casos de uso principales:

  1. Declarar variables no anulables a las que se les asignará valor posteriormente;
  2. Implementar inicialización diferida de variables.

En la mayoría de los casos, el análisis de flujo de Dart detecta si una variable no anulable ha recibido un valor no nulo antes de usarse. Sin embargo, en determinados escenarios falla este análisis, los más comunes son las variables globales y las variables de instancia: el compilador normalmente no puede confirmar si han sido inicializadas y marca error.

Si estás seguro de que la variable recibirá un valor antes de usarse pero el compilador no lo detecta, añade el modificador late para eliminar el error:

En el siguiente ejemplo, description es una variable global y el compilador no logra identificar si se asigna valor, pero si estás completamente seguro de que se asignará, usa late:

late String description;

void main() {
  description = 'Feijoada!';
  print(description);
}
Lenguaje del código: Dart (dart)

Aviso

Si declaras una variable late y nunca le asignas valor, al intentar acceder a ella se lanzará una excepción en tiempo de ejecución.

Si asignas un valor inicial al declarar una variable late, ese código de inicialización solo se ejecutará la primera vez que se acceda a la variable. Esta inicialización diferida es muy útil en dos situaciones:

  1. Es posible que la variable nunca se use y su inicialización requiera un coste computacional alto;
  2. Al inicializar una variable de instancia, la lógica de inicialización necesita acceder a this.

En el siguiente ejemplo, si nunca se usa temperature, la función costosa readThermometer() nunca se ejecutará:

// readThermometer() solo se llama al acceder por primera vez a temperature
late String temperature = readThermometer(); // Inicialización diferida
Lenguaje del código: JavaScript (javascript)

final y const

Si no quieres que el valor de una variable se modifique, usa final o const; se pueden usar solos o junto a una anotación de tipo, sustituyendo a var.

  • Las variables final solo admiten una única asignación;
  • Las variables const son constantes de tiempo de compilación (las variables const tienen implícitamente el comportamiento de final).

Aviso

Los miembros de instancia pueden modificarse con final, pero no con const; para constantes a nivel de clase usa static const.

Ejemplos de definición de variables final:

final name = 'Jack'; // Sin anotación de tipo
final String nickname = 'Jason';
Lenguaje del código: PHP (php)

No puedes modificar el valor de una variable final, el compilador marcará error directamente:

void main() {
	final name = 'Jack'; // Sin anotación de tipo
	final String nickname = 'Jason';
	
	name = 'Alice'; // Error: las variables final solo admiten una asignación
}Lenguaje del código: JavaScript (javascript)
C:\dartdemo\firstdart>dart run
Building package executable...
Failed to build firstdart:firstdart:
bin/firstdart.dart:6:2: Error: Can't assign to the final variable 'name'.
        name = 'Alice'; // Error: las variables final solo admiten una asignaciónLenguaje del código: PHP (php)

const se usa para definir constantes de compilación. Si la constante se declara dentro de una clase, debes añadir static y escribir static const. Al declarar una variable const, el valor asignado debe ser determinable en tiempo de compilación: números, literales de cadena, otras variables const o resultados de operaciones aritméticas con números constantes:

const bar = 1000000; // Unidad de presión (dinas/cm²)
const double atm = 1.01325 * bar; // Presión atmosférica estándar
Lenguaje del código: JavaScript (javascript)

La palabra reservada const no solo sirve para declarar variables constantes, también puedes usarla para crear objetos constantes o definir constructores que generen instancias constantes. Cualquier variable puede recibir un objeto constante como valor.

var foo = const [];
final bar = const [];
const baz = []; // Equivalente a const []
Lenguaje del código: PHP (php)

En expresiones de asignación a variables const se puede omitir la palabra clave const, como en el ejemplo de baz arriba.

Incluso si una variable ha recibido un objeto constante, si no es final ni const, se puede modificar su referencia:

foo = [1, 2, 3]; // foo apuntaba originalmente a un array vacío constante, ahora se cambia su referencia
Lenguaje del código: JavaScript (javascript)

Sin embargo, las variables const no permiten reasignación, el compilador dará error:

// Error de análisis estático
baz = [42]; // Error: las variables const no permiten reasignación
Lenguaje del código: JavaScript (javascript)

Al definir constantes se pueden usar comprobaciones de tipo, conversiones de tipo (is, as), if dentro de colecciones y operadores de expansión (..., ...?):

void main() {
	const Object i = 3; // i es una constante de tipo Object que almacena un valor int
	const list = [i as int]; // Uso de conversión de tipo
	const map = {if (i is int) i: 'int'}; // Uso de comprobación is e if en colección
	const set = {if (list is List<int>) ...list}; // Combinado con operador de expansión
}Lenguaje del código: JavaScript (javascript)

Aviso

La referencia de un objeto modificado con final no se puede cambiar, pero sus campos internos sí se pueden modificar; en contraste, un objeto const y todos sus datos internos son totalmente inmutables.

Para saber más sobre crear colecciones constantes con const, consulta la documentación de listas, mapas y clases.

* Variables comodín

Nota de versión: las variables comodín requieren Dart 3.7 o superior.

Las variables comodín con nombre _ son variables locales/parámetros sin enlace, funcionan únicamente como marcadores de posición. Si llevan una expresión de inicialización, esta se ejecuta, pero no puedes leer el valor almacenado en la variable. En el mismo ámbito se pueden definir varias variables llamadas _ sin conflictos de nombres.

¿Qué es una variable comodín?

En Dart, el guion bajo simple _ es la variable comodín:

  1. Sirve para recibir valores que no necesitas usar, indicando al compilador «no leeré ni utilizaré esta variable»;
  2. El compilador no genera advertencias de «variable definida pero no usada»;
  3. No se puede leer _ (provoca error), solo sirve para descartar datos.
// Solo tomamos el 1.º y 3.º valor, el segundo se descarta con _
final [a, _, c] = [10, 20, 30];
print(a); // 10
print(c); // 30
// print(_); // Error, no se puede leer el comodínLenguaje del código: PHP (php)

No se permiten variables comodín en declaraciones de nivel superior ni en miembros de clase que afecten a la privacidad de la librería. Solo se pueden usar dentro de ámbitos de bloque, ejemplos a continuación:

  1. Declaración de variables locales
void main() {
  var _ = 1;
  int _ = 2;
}
Lenguaje del código: JavaScript (javascript)
  1. Variable de bucle for
for (var _ in list) {}
Lenguaje del código: PHP (php)
  1. Parámetro capturado en catch
try {
  throw '!';
} catch (_) {
  print('oops');
}
Lenguaje del código: PHP (php)
  1. Parámetros genéricos, parámetros genéricos de función
class T<_> {}
void genericFunction<_>() {}

takeGenericCallback(<_>() => true);
Lenguaje del código: JavaScript (javascript)
  1. Parámetros de función
Foo(_, this._, super._, void _()) {}

list.where((_) => true);

void f(void g(int _, bool _)) {}

typedef T = void Function(String _, String _);Lenguaje del código: JavaScript (javascript)

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *