본문 바로가기
Flutter

[Flutter 13] Null 억제 연산자(!) 와 late

by JINJINC 2025. 1. 6.
728x90
반응형

Dart의 Null 억제 연산자 ! 완벽 정리


Dart의 Null Safety 기능은 null로 인해 발생할 수 있는 오류를 방지하는 데 중점을 둡니다. 하지만 때로는 null일 가능성이 있는 값을 개발자가 null이 아님을 보장할 수 있는 상황이 있습니다. 이런 경우 **Null 억제 연산자 !**를 사용하여 null 체크를 생략할 수 있습니다. 아래에서 이 연산자에 대해 자세히 알아보겠습니다.


 

1. Null 억제 연산자 !란?

  • 기능: 변수나 값이 null이 아님을 Dart 컴파일러에게 명시적으로 알리는 연산자.
  • Null Safety에서는 nullable 타입 (String?, int? 등)에 접근할 때 컴파일러가 null 여부를 검사합니다.
  • ! 연산자를 사용하면 컴파일러가 null 체크를 강제로 생략하도록 합니다.
  • 주의: 값이 실제로 null일 경우 런타임 에러가 발생합니다.

2. Null 억제 연산자 사용 방법

  • nullableVariable! 형식으로 사용.
  • 컴파일러가 nullable 타입(T?)을 non-null 타입(T)로 간주하도록 강제합니다.

3. 사용 예제

기본 예제

void main() {
  String? nullableName = '홍길동';
  
  // Null 억제 연산자를 사용하여 nullableName을 null 불가 타입으로 변환
  String name = nullableName!;
  print('name: $name'); // 출력: name: 홍길동
}

 

null일 가능성이 없는 값

 

void main() {
  String? nullableName = getName(); // getName()이 null을 반환하지 않을 것이라 확신
  print(nullableName!.length); // 컴파일러에게 null이 아님을 보장
}

String? getName() {
  return '김철수'; // 항상 null이 아닌 값 반환
}
 

null일 경우 런타임 에러 발생

 
void main() {
  String? nullableName = null;
  
  // Null 억제 연산자를 사용하지만, 실제 값이 null이라 런타임 에러 발생
  print(nullableName!.length); // 런타임 에러: Null check operator used on a null value
}

4. 사용 시 주의사항

  1. 무조건 사용하지 말 것:
    • ! 연산자는 개발자가 null이 아님을 확신하는 경우에만 사용해야 합니다.
    • 그렇지 않으면 null 값으로 인해 런타임 에러가 발생할 수 있습니다.
  2. 방어적 코드와 함께 사용:
    • Null 억제 연산자를 사용하기 전에 null 여부를 방어적으로 확인하는 것이 좋습니다.
void main() {
  String? nullableName = null;

  // 방어적 코드
  if (nullableName != null) {
    print(nullableName.length); // null이 아님을 확인 후 접근
  }
}

 

3. 대안으로 ?.와 ?? 사용:

  • 가능하면 널 체크 연산자(?.)나 널 병합 연산자(??)를 사용하는 것이 더 안전합니다.
void main() {
  String? nullableName = null;
  
  // ?.와 ?? 사용
  int nameLength = nullableName?.length ?? 0; // null일 경우 기본값 0
  print(nameLength); // 출력: 0
}

5. Null 억제 연산자의 사용 사례

Flutter에서 Null Safety로 인해 강제 변환이 필요한 경우

Flutter와 같은 UI 프레임워크에서는 위젯 트리에서 nullable 값이 자주 사용되지만, 실제로 null이 아님을 보장할 수 있는 상황이 많습니다.

 
void main() {
  String? nullableText = getText();
  // Flutter에서는 null이 아님을 확신할 때 ! 사용
  print(nullableText!.toUpperCase());
}

String? getText() {
  return 'Flutter';
}
 
 
 

외부 API 호출 시

외부 API 응답에서 특정 필드가 null이 아님을 보장하는 경우:

Map<String, dynamic>? apiResponse = {'name': 'Dart'};
String name = apiResponse!['name']; // apiResponse가 null이 아님을 확신
print(name); // 출력: Dart

6. Null 억제 연산자 vs. 방어적 코드

Null 억제 연산자 !방어적 코드 (if, ?. 등)

null이 아님을 확신할 때 사용 null 여부를 확인하거나 기본값을 사용할 때
컴파일러가 null 체크를 생략 명시적으로 null 여부를 확인
null일 경우 런타임 에러 발생 가능 안전하게 null 상황 처리 가능

7. 결론

  • Null 억제 연산자 !는 null 여부를 컴파일러에 강제적으로 알리는 유용한 도구지만, 남용하면 위험합니다.
  • 사용하기 전에 해당 값이 null이 아님을 100% 확신해야 하며, 가능하면 방어적 코드를 통해 null 상황을 처리하는 것이 안전합니다.
  • Dart Null Safety는 null 에러를 예방하는 데 중점을 두지만, Null 억제 연산자를 올바르게 사용하면 더 유연한 코드 작성을 도와줍니다.

 

Dart의 late 키워드 완벽 정리


Dart에서는 late 키워드를 통해 변수를 선언만 해두고 나중에 초기화할 수 있는 기능을 제공합니다. 특히, **지연 초기화(Lazy Initialization)**를 활용해야 하는 경우 유용합니다. 아래에서 late 키워드의 사용법과 주의할 점을 정리해 보겠습니다.


1. late 키워드란?

  • late는 변수를 나중에 초기화할 수 있도록 도와주는 키워드입니다.
  • 일반적으로 변수를 선언과 동시에 초기화해야 하지만, late를 사용하면 필요할 때 초기화가 가능합니다.
  • 사용 상황:
    • 초기화 비용이 큰 변수.
    • 변수를 선언만 하고 나중에 값이 결정되는 경우.
    • 초기화가 반드시 실행되어야 하지만 지연하고 싶을 때.

2. late 키워드의 특징

  • 변수가 초기화되기 전에는 사용할 수 없습니다.
  • 초기화를 수행하기 전까지 메모리를 차지하지 않습니다.
  • 컴파일 시점에 초기화되지 않았다는 사실을 확인할 수 있습니다.
  • 주로 final 키워드와 함께 사용됩니다.

3. late 키워드 사용법

void main() {
  late String greeting;
  
  // 변수 선언 후 나중에 초기화
  greeting = getGreetingMessage();
  print(greeting); // 출력: Hello, Dart!!
}

String getGreetingMessage() {
  print('함수 호출');
  return 'Hello, Dart!!';
}

 

late final 변수

  • final 키워드와 함께 사용: 한 번만 초기화할 수 있는 변수를 선언합니다.
void main() {
  late final int number;

  // 초기화
  number = 100;
  print(number); // 출력: 100
  
  // number = 200; // 오류: final 변수는 재할당할 수 없습니다.
}

 

초기화하지 않은 경우

  • late 변수를 초기화하지 않고 사용하려고 하면 런타임 에러가 발생합니다.
void main() {
  late String notAssigned;

  // print(notAssigned); // 오류: LateInitializationError
}

 

 

4. late 키워드의 장점

  1. 지연 초기화로 성능 최적화
    • 초기화 비용이 큰 변수(예: 파일 읽기, 네트워크 요청 등)를 필요할 때만 초기화.
  2. 런타임 에러 방지
    • 초기화되지 않은 변수 사용 시 컴파일 시점에 오류를 확인 가능.
  3. 의도적인 코드 작성
    • 선언만 해두고, 초기화 시점을 명확히 할 수 있어 가독성 향상.

5. 사용 시 주의점

  1. 초기화 보장
    • late로 선언된 변수는 초기화하지 않고 사용하려 하면 **LateInitializationError**가 발생합니다.
void main() {
  late String name;

  // print(name); // 오류: 초기화되지 않은 변수 접근
}

 

2. 지연 초기화 변수의 재할당 제한

  • late final 변수는 한 번 초기화된 이후에는 값 변경이 불가능합니다.
void main() {
  late final String name;
  name = '홍길동';

  // name = '김철수'; // 오류: final 변수는 재할당 불가
}

3. 초기화 타이밍 확인

  • late 변수의 초기화는 실제 사용 시점에 이루어지므로, 의도적으로 초기화를 놓치는 실수를 주의해야 합니다.

 

 

6. 활용 예시

비싼 초기화 작업을 나중으로 미루기

void main() {
  late String greeting;
  print('greeting 변수를 초기화하지 않고 사용 준비 중...');
  
  // 초기화
  greeting = getGreetingMessage();
  print(greeting); // 출력: Hello, Dart!!
}

String getGreetingMessage() {
  print('비싼 초기화 작업 수행 중...');
  return 'Hello, Dart!!';
}

 

클래스에서 사용

  • 생성자와 함께 late 변수 사용:
class User {
  late String name;

  void setName(String userName) {
    name = userName;
  }
}

void main() {
  User user = User();
  user.setName('홍길동');
  print(user.name); // 출력: 홍길동
}

 

Flutter에서 사용

  • 위젯의 late 변수를 통해 특정 값 초기화를 연기.

 

class MyWidget extends StatelessWidget {
  late final String title;

  MyWidget(String data) {
    title = data;
  }

  @override
  Widget build(BuildContext context) {
    return Text(title); // 초기화된 값 사용
  }
}

 

7. late 키워드와 일반 변수의 차이점

구분일반 변수late 변수

초기화 시점 선언과 동시에 초기화 필요 선언 후 초기화 가능
메모리 사용 즉시 메모리 할당 사용 시점까지 메모리 사용 없음
초기화 여부 검사 컴파일 시 검사되지 않음 초기화 전 접근 시 런타임 에러 발생
성능 최적화 항상 메모리 차지 초기화 비용을 지연시켜 성능 최적화 가능

8. 결론

  • late 키워드는 초기화 시점을 유연하게 관리할 수 있는 Dart의 강력한 기능입니다.
  • 사용 시점에 초기화가 필요한 변수, 비싼 초기화 작업, 또는 클래스 생성 시 의도적으로 값을 나중에 설정해야 하는 경우에 적합합니다.
  • 단, 초기화되지 않은 변수를 사용하지 않도록 주의해야 하며, late를 남용하지 않고 필요한 곳에서만 사용하는 것이 중요합니다.

이 글이 여러분의 Dart 개발에 도움이 되길 바랍니다! 😊

 

728x90
반응형

댓글