subindev 님의 블로그

[Flutter] #4 Dart 기본 문법 Null Safety 본문

앱개발/Flutter

[Flutter] #4 Dart 기본 문법 Null Safety

subindev 2025. 1. 6. 10:10

Dart 프로그래밍 기초

 

1. Dart Null Safety


널 세이프티(Null Safety)는 개발자가 널 에러를 피할 수 있도록 도와주는 다트 프로그래밍 언어의 기능입니다. 이 기능은 사운드 널 세이프티 인 다트(Sound Null Safety in dart)라고 불리며, 이를 통해 개발자는 코드 작성 시점에 널 에러를 잡을 수 있습니다.

Sound Null Safety in Dart 이란 ? (Sound Type System)

런타임 중에 null 포인터 예외를 방지하기 위해 Dart 컴파일러가 코드를 분석하고 컴파일할 때 타입 시스템에서 엄격한 규칙을 적용하는 것을 의미합니다.
 
정리하면, Dart 컴파일러가 코드를 분석하고, null이 될 수 있는 타입과 null이 될 수 없는 타입을 분명히 구분해 주기 때문에, 우리가 실수로 null을 넣거나 반환하게 되면 빌드(컴파일) 시점에 오류를 발견할 수 있습니다.
이를 사운드 널 세이프티(Sound
런타임 중에 null 포인터 예외를 방지하기 위해 Dart 컴파일러가 코드를 분석하고 컴파일할 때 타입 시스템에서 엄격한 규칙을 적용하는 것을 의미합니다.
 
정리하면, Dart 컴파일러가 코드를 분석하고, null이 될 수 있는 타입과 null이 될 수 없는 타입을 분명히 구분해 주기 때문에, 우리가 실수로 null을 넣거나 반환하게 되면 빌드(컴파일) 시점에 오류를 발견할 수 있습니다.
이를 사운드 널 세이프티(Sound Null Safety)라고 하며, 런타임에서의 null 참조 에러 를 효과적으로 예방해줍니다.
 
void main() {
  String name = '길동';
  int age = 30;

  String? nullableName;
  int? nullableAge;

  print('name : ${name}');
  // print('nullAbleName : ${nullableName.length}'); 컴파일 시점 - 오류 확인
  print('nullAbleAge : ${nullableAge}');

  // 방어적 코드도 작성해 보자.
  if (nullableName != null) {
    print(nullableName.length);
  }
} // end of main​

만약 NullSafety를 사용하지 않는다면, 기존 Java에서 했던 것처럼 방어적 코드를 작성하여 if 조건문을 걸어줘야한다

 

2. Null Check 연산자Null 병합 연산자


Null Check 연산자 ?. 

?. 연산자는 객체가 null인지 확인한 후, null이 아닐 경우에만 속성이나 메서드에 접근하도록 합니다.
만약 객체가 null이라면, null을 반환하고 런타임 오류를 방지합니다.

객체가 null일 수 있는 상황에서 속성/메서드에 안전하게 접근하려고 할 때 사용합니다.

 

Null 병합 연산자 ?? 

?? 연산자는 왼쪽 값이 null인 경우에만 오른쪽 값을 반환합니다.
왼쪽 값이 null이 아니면 그 값을 그대로 반환합니다.

null 값이 발생할 경우에 기본값을 제공하려고 할 때 사용합니다.

// 널 체크 연산자( ?. ) 와 널 병합 연산자 ( ?? ) 에 대해서 알아 보자.

void main() {
  // 1. 널 체크 연산자 ?. 있으면 그것에 대해 참조 없으면 null
  String? userName = getNullableUserName();

  int? userNameLength = userName?.length;
  print('사용자 이름의 길이 값 : ${userNameLength}');
  print('------------------------------------');

  // 2. 널 병합 연산자  변수명 ?? 디폴트값
  String finalUserName = userName ?? '홍길동';
  print('finalUserName : ${finalUserName}');

  // 3. ?. 와 ?? 를 함께 사용하는 예시
  String upperOrNoName = userName?.toUpperCase() ?? 'NO_NAME';
}


// 선택적 명명 매개변수 ( '{ }' )
String? getNullableUserName({String? name}) {
  return name;
}​

 

 

 

 

3. Null 억제 연산자


Null 억제 연산자 !

  • ! 는 Null 억제 연산자(Null Assertion Operator)로,
    **이 값이 절대 null이 아니다**라는 것을 Dart 컴파일러에게 보증할 때 사용합니다.
    즉, null-safety가 적용된 상황에서 nullable 값non-nullable 타입으로 변환합니다.
  • 그러나, 값이 실제로 null일 경우 런타임 에러가 발생하므로 신중히 사용해야 합니다.
// null 억제 연산자 ( ! )
// 개발자가 해당 값이 null 아님을 확신하고, 예외를 발생시키지 안을 때 유용하게 사용할 수 있다.
void main() {

  // 이 코드는 절대 널이 될 수 없다.
  String? nullableName = getNullableName();

  // 1. 널 억제 연산자 사용해 보기
  // 널체크 연산자 또는 널병합 연산자를 사용할 수 있지만
  // null 아님을 확신 한다면 개발자가 널 억제 연산자를 사용할 수 있다.
  String forcedName = nullableName!; 
  print('forcedName = ${nullableName}');
  
}

String? getNullableName() {
  // return null;
  return '홍길동';
}

 

4. late 키워드

 


late 키워드란 ?

 
Dart에서 변수를 초기화 시점을 지연시키는 키워드입니다. 즉, 변수 선언 시점에서는 값을 할당하지 않고, 실제로 값이 필요할 때까지 값을 할당하지 않아도 되는 경우에 사용됩니다. 보통 final 키워드와 함께 많이 사용이 됩니다.

 

late와 final이 함께 사용되면?

late와 final을 함께 사용하면, 변수가 한 번만 초기화되도록 보장하면서도 초기화 시점을 지연시킬 수 있습니다.

즉, 값을 한 번만 설정해야 하며, 그 값을 언제 설정할지 제어하고 싶은 상황에 사용하면 됩니다.

void main() {
  // 1. late 키워드를 사용해 보자.
  late String greeting;

  greeting = getGreetingMessge();
  print(greeting);

  print('---------------------------');

  // 2. late final 변수
  late final int number;
  number = 100;
  print(number);

  // 컴파일 시점에 사용시 오류 확인
  late String notAssigned;
  // print(notAssigned);
}

String getGreetingMessge() {
  print('함수  호출'); // 로깅을 찍어 본다.  디버깅
  return 'Hello, Dart!';
}