다트는 널 안전성을 지원하므로 널 허용 변수를 이용할 때 널에 안전한 코드를 작성할 수 있도록 몇 가지 연산자를 제공합니다. 여기서는 이러한 널 안전성과 관련한 연산자를 알아봅니다.
널인지 점검할 때 -! 연산자
어떤 변수가 널인지 점검할 때는 ! 연산자를 사용합니다. 변수 이름 뒤에 ! 연산자를 추가하면 이 변숫값이 널일 때 런 타임 오류가 발생합니다. 널 불허 변수 뒤에 추가할수도 있지만 널 불허 변수에는 널을 대입할 수 없으므로 의미가 없습니다.
밑에 있는 코드에서 20으로 초기화한 널 허용 변수 a1은 main() 함수 첫 줄에서 ! 연산자를 붙였을때는 괜찮지만, null을 대입한 후 마지막 줄에서 ! 연산자를 붙였을 때는 오류가 발생합니다. 그 앞 줄에서 a1에 null을 대입했기때문입니다.
// 널인지 점검
int? a1 = 20;
main() {
a1!;
a1 = null;
a1!; // 런 타임 오류
}
! 연산자는 변수 이외에 함수 호출 같은 구문에도 사용할 수 있습니다. 변수든 구문이든 결과가 널이면 런 타임 오류가 발생합니다. 다음 코드에서 some() 이라는 함수는 반환 타입이 int?입니다. 즉, 함수 호출 결과로 정수나 널이 반환될 수 있습니다. 그리고 함수 호출문 뒤에 some() ! 처럼 ! 연산자를 추가했습니다. 만약 some() 함수가 널이 아닌 값을 반환하면 정상으로 실행되지만, 널을 반환하면 오류가 발생합니다.
// 함수 호출문에 ! 연산자 사용
int? some(arg) {
if( arg == 10 ) {
return 0;
} else {
return null;
}
}
main() {
int a = some(10)!;
print('a : $a'); // a : 0
int b = some(20)!; // some() 함수가 널을 반환하므로 런 타임 오류
print('b : $b');
}
멤버에 접근할 때 - ?., ?[ ] 연산자
널 허용 객체나 리스트의 멤버에 접근할 때는 ?. 나 ?[ ] 연산자를 사용해야 합니다. ?. 연산자를 사용하면 객체가 널이 아닐 때만 멤버에 접근하며, 널이면 멤버에 접근할 수 없고 null을 반환합니다.
아래 코드에서는 str 변수를 널 허용으로 선언했는데 str의 멤버인 isEmpty에 접근할 때 ?. 을 사용하지 않아서 오류가 발생합니다. 이때 널 허용으로 선언한 변수가 널일 수 있으므로 안전성을 고려해 ?. 연산자를 이용하라는 오류 메시지가 출력됩니다.
// 널 허용 객체의 멤버에 접근하기
String? str = 'hello';
main() {
str.isEmpty; // 오류
}
아래 코드에서는 no1 변수를 널 허용으로 선언했습니다. 그리고 no1 객체의 isEven 멤버에 접근할 때 no1?.isEven을 사용했습니다. 이렇게 하면 no1이 널일 때 isEven에 접근하지 않고 null을 반환합니다.
// 널 객체의 멤버에 접근할 때 null 반환하기
main() {
int? no1 = 10;
bool? result1 = no1?.isEven;
print('result 1 : $result1');
no1 = null;
bool? result2 = no1.isEven;
print('result 2 : $result2');
}
▶ 실행 결과
true
null
코드에서 bool? result1 = no1?.isEven;이 실행될 때 로직을 그림으로 표현하면 아래와 같습니다. 이처럼 ?. 연산자는 널 허용으로 서넌한 변수가 널일 때는 멤버에 접근할 수 없도록 막아서 널에 안전한 코드를 작성할 수 있습니다.
bool? result1 = no1.isEven
no1 == null => true => result1 = null
no1 == null => false => isEven => true/false => result1
?[ ] 연산자는 널 허용 List의 데이터를 인덱스로 접근할 때 사용합니다. List 객체가 널이 아닐 때는 데이터에 접근할 수 있으며 널이면 null을 반환합니다.
// 널 리스트에 인덱스로 접근할 때 null 반환하기
main() {
List<int>? list = [10, 20, 30];
print('list[0] : ${list?[0]}');
list = null;
print('list[0] : ${list?[0]}');
}
▶ 실행 결과
list[0] : 10
list[0] : null
값을 대입할 때 - ?? = 연산자
널 허용 변수에 널이 아닌 값만 대입하고 싶다면 ??= 연산자를 사용합니다. ??= 연산자는 아래의 대입할 값이 널이 아닐 때만 대입하고 널이면 대입하지 않습니다.
main() {
int? data3;
data3 ??= 10;
print('data3 : $data3');
data3 ??= null;
print('data3 : $data3');
}
▶ 실행 결과
data3 : 10
data3 : 10
값을 대체할 때 - ?? 연산자
널 허용 변수가 널일 때 대체할 값을 지정하고 싶다면 ?? 연산자를 사용합니다. 다음 코드를 보면 data4는 널 허용으로 선언했습니다. data4가 널이 아닐 때는 result에 data4값을 대입 하지만, 널일 때는 오른쪽에 지정한 값을 대입합니다.
// 널일 때 값 대체하기
main() {
String? data4 = 'hello';
String? result = data4 ?? 'world';
print('result : $result');
data4 = null;
result = data4 ?? 'world';
print('result : $result');
}
▶ 실행 결과
result : hello
result : world
'Flutter' 카테고리의 다른 글
명명된 매개변수 (0) | 2024.11.12 |
---|---|
함수 선언과 호출하기 (0) | 2024.11.12 |
널 포인트 예외 관리하기 (1) | 2024.11.12 |
컬렉션 타입 - List, Set, Map (0) | 2024.11.11 |
var와 dynamic 타입 (0) | 2024.11.11 |