← Назад к вопросам

Как объявить non-nullable переменную и при этом установить для нее значение?

1.2 Junior🔥 241 комментариев
#Dart

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как объявить non-nullable переменную и при этом установить для неё значение?

Это один из ключевых вопросов о null safety в Dart. Non-nullable переменные требуют инициализации значением, не null.

Основные способы объявления

1. Инициализация при объявлении (самый простой способ)

// ✅ Правильно - инициализируем сразу
String name = "John";
int age = 25;
bool isActive = true;
List<String> tags = ["flutter", "dart"];

Это гарантирует, что переменная всегда имеет значение.

2. Инициализация в конструкторе класса

Для полей класса это самый распространённый способ:

class User {
  late String name;  // Объявляем non-nullable
  late int age;
  
  User(this.name, this.age);  // Инициализируем в конструкторе
}

final user = User("Alice", 30);
print(user.name);  // "Alice" - гарантированно не null

Даже лучше — использовать инициализацию параметров:

class User {
  final String name;  // final гарантирует immutability
  final int age;
  
  User(this.name, this.age);  // Сигнатура требует оба параметра
}

// Компилятор проверит, что оба параметра передаются
final user = User("Alice", 30);
final broken = User("Bob");  // ❌ Ошибка компиляции!

3. Ключевое слово late — отложенная инициализация

late позволяет объявить non-nullable переменную без сразу установки значения, но с обещанием, что оно будет установлено позже:

class DataProvider {
  late String _cachedData;  // Declare non-nullable
  
  void initialize() {
    _cachedData = fetchData();  // Set value later
  }
  
  String getData() {
    return _cachedData;  // Всегда non-null, если initialize() был вызван
  }
  
  String fetchData() => "Cached value";
}

final provider = DataProvider();
provider.initialize();  // ДОЛЖНО быть вызвано перед getData()
print(provider.getData());  // Безопасно

Опасность late:

class BadExample {
  late String value;  // Declare late
  
  void doSomething() {
    print(value);  // ❌ LateInitializationError если value не инициализирована!
  }
}

final bad = BadExample();
bad.doSomething();  // ❌ Runtime ошибка: LateInitializationError

4. Инициализация через final с выражением

// ✅ Правильно - вычисляется один раз
final currentTime = DateTime.now();
final userCount = getUserCount();  // Функция должна вернуть не-null значение
final config = loadConfig();

// Компилятор проверит типы возвращаемых значений

Инициализация в зависимости от типов

Примитивные типы:

String name = "";  // Empty string
int count = 0;     // Default int
double price = 0.0;  // Default double
bool flag = false; // Default bool

Коллекции:

List<String> items = [];  // Empty list
Set<int> numbers = {};    // Empty set
Map<String, String> data = {};  // Empty map

Объекты:

class User {
  final String id;
  final String name;
  
  User(this.id, this.name);
}

// ✅ Правильно - всегда инициализируем
final user = User("123", "John");

// ❌ Неправильно - может быть null
User? nullableUser = null;  // nullable type

Паттерны инициализации в классах

Паттерн 1: Required fields

class Product {
  final String id;
  final String name;
  final double price;
  
  Product({
    required this.id,      // Требуется передать
    required this.name,
    required this.price,
  });
}

final product = Product(
  id: "1",
  name: "Widget",
  price: 9.99,
);  // ✅ Все параметры переданы

final broken = Product(
  id: "2",
  name: "Gadget",
);  // ❌ Ошибка компиляции: missing required parameter 'price'

Паттерн 2: Default values

class Configuration {
  final String appName;
  final bool isDarkMode;
  final int maxRetries;
  
  Configuration({
    required this.appName,
    this.isDarkMode = false,  // Default значение
    this.maxRetries = 3,      // Default значение
  });
}

final config1 = Configuration(appName: "MyApp");
// isDarkMode = false, maxRetries = 3 (defaults)

final config2 = Configuration(
  appName: "MyApp",
  isDarkMode: true,
  maxRetries: 5,
);  // Переопределяем defaults

Паттерн 3: Factory constructor

class DatabaseConnection {
  final String host;
  final int port;
  late final String connectionString;
  
  DatabaseConnection._(this.host, this.port) {
    connectionString = "$host:$port";  // Инициализируем late переменную
  }
  
  factory DatabaseConnection.localhost() {
    return DatabaseConnection._("localhost", 5432);
  }
  
  factory DatabaseConnection.production() {
    return DatabaseConnection._("db.prod.example.com", 5432);
  }
}

final local = DatabaseConnection.localhost();
final prod = DatabaseConnection.production();

Nullable vs Non-nullable

Сравнение:

// ❌ Nullable - может быть null
String? name = null;
if (name != null) {
  print(name.length);  // Нужна проверка
}

// ✅ Non-nullable - ВСЕГДА имеет значение
String name = "John";
print(name.length);  // Безопасно всегда

// ✅ Non-nullable late
late String lazyName;
lazyName = "Alice";
print(lazyName.length);  // Безопасно (если инициализирована)

Best Practices

1. Предпочитайте инициализацию при объявлении:

// ✅ Хорошо
final String name = "John";
final int age = 25;

// Избегайте
late String name;
name = "John";

2. Используйте final для immutable данных:

// ✅ Правильно
final user = User("123", "John");
final config = loadConfig();

// ❌ Избегайте переменного состояния
var user = User("123", "John");
user = User("456", "Jane");  // Переассигнили

3. Делайте параметры required в конструкторах:

// ✅ Хорошо
class User {
  final String id;
  final String name;
  
  User({
    required this.id,
    required this.name,
  });
}

// ❌ Плохо
class User {
  String? id;
  String? name;
  
  User({this.id, this.name});
  // Нужно проверять на null везде
}

4. Используйте late только когда необходимо:

// ✅ Хорошо - инициализируется в initState
class MyWidget extends StatefulWidget {
  @override
  State<MyWidget> createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
  late AnimationController controller;
  
  @override
  void initState() {
    super.initState();
    controller = AnimationController(duration: Duration(seconds: 1));
  }
}

Корректное использование non-nullable переменных — это основа безопасности типов в Dart и ключ к написанию надёжного кода.