플러터ㅣ널 포인트 예외 관리, 널 안전성 연산자

휘Bin·2023년 6월 19일
0
post-thumbnail

널 안전성?

'널 안전성' 이란, '널 포인트 예외'를 프로그램을 실행하기 전 코드를 작성하는 시점에 점검하는 것을 의미한다.
널 포인트 예외는 객체가 특정 값이 아닌 null을 가리켜서 발생하는 오류이며, 컴파일러가 걸러내지 못하고 프로그램 실행 중에 발생하므로 치명적일 수 있다.

널 안전성을 지원하지 않는 프로그래밍 언어들은 객체에 널값을 대입할 수 있고, 널인 객체에 접근하면 NPE가 발생하지만, 널 안전성을 지원하는 언어들은 객체가 널일 때 발생할 수 있는 오류를 코드 작성하는 시점에 점검해준다.
즉, NPE 발생 가능성을 컴파일러가 미리 점검해주어 널에 안전한 코드를 작성할 수 있게 된다.

널 허용과 널 불허

널 안전성을 지원하는 프로그래밍 언어에서는 변수를 선언할 때, '널 허용(Nullable)''널 불허(NoNull)'로 구분한다. 따라서 컴파일러에 널을 대입할 수 있는지 없는지를 명확히 알려줘야 한다.

다트 언어에서 변수는 기본으로 널 불허로 선언된다. 만약 널 허용으로 선언하려면 타입 뒤에 물음표(?)를 추가해줘야 한다.
이러한 널 허용, 널 부허 변수 선언법은 모든 타입에 적용된다.

Ex)

int? i1 = 10;

널 불허 변수의 초기화

말했듯, 다트에서 모든 변수는 객체이다. 그런데 변수를 선언하면서 초깃값을 주지 않으면 자동으로 널로 초기화된다. 당연히 널 불허로 선언한 변수는 선언과 동시에 널이 아닌 값으로 초기화해야 한다. 만약 널 불허 변수를 초기화하지 않으면 오류가 발생한다.

다만, 널 불허 변수를 선언과 동시에 초기화해야 한다는 규칙은 톱 레벨에 선언된 변수와 클래스의 멤버 변수에만 해당한다. 즉, 함수에서 지역 변수를 널 불허로 선언할 때는 초기화하지 않아도 된다. 하지만 사용하기 전에는 반드시 값을 대입 해주어야 한다.

※ 모든 변수에 물음표를 붙인다?
=> 변수를 선언할 때 무조건 물음표를 붙여서 널 허용으로 관리하면 객체의 멤버를 이용할 때 널 안전성 연산자를 이용해야 한다. 이러면 널 불허로 선언할 수 있는 변수를 굳이 널 허용으로 선언하게 되면서 오히려 코드가 복잡해질 수 있다.
또한, 널 안전성이라는 개념은 변수에 널을 대입할 수 있는지를 명확하게 구분해서 사용하고 컴파일러의 도움을 받아 NPE가 발생하지 않는 코드를 작성하자는 것이 목적이다. 따라서 널 안전성을 지원하는 대부분의 언어에서는 모든 변수를 널 불허로 선언하고 널을 대입할 가능성이 있는 변수만 선별해서 널 허용으로 선언하라고 말한다.

var 타입의 널 안전성

다트에서 변수를 선언할 때 var를 사용하면 대입하는 값에 따라 타입이 결정된다.
var로 선언한 변수는 널 허용 여부도 대입하는 값에 따라 컴파일러가 자동으로 결정하게 된다. 따라서 var 뒤에는 물음표를 붙일 수 없다.

dynamic 타입의 널 안전성

dynamic 타입에는 물음표를 추가할 수 있다. 하지만 의미가 없다. dynamic 타입은 모든 타입의 데이터를 대입할 수 있다. 즉, 널을 허용하는 Nullable도 포함하는 것이다. 따라서 dynamic 타입으로 선언한 것 자체가 널을 허용하게 되는 것이다.

널 안전성과 형 변환

'널 허용(Nullable)'과 '널 불허(NonNull)'는 타입(클래스)이다.
Nullable은 NonNull의 상위 타입이다.
즉, int?가 int의 상위 타입이다. 따라서 널 불허 변수를 널 허용에 대입할 때는 자동으로 형 변환된다. 하지만 널 허용 변수를 널 불허에 대입할 때는 오류가 발생한다.

만약 널 허용 변수를 널 불허에 대입할 수 있게 하려면, 명시적으로 형 변환을 해줘야 한다. 다트에서 명시적 형 변환 연산자는 as 이다.

Ex)

i1 = a2 as int;

초기화를 미루는 late 연산자

널 불허 변수는 선언과 동시에 초기화해야 한다. 하지만 개발을 하다보면 초기화 시기가 애매할 때가 있다. 이럴 때는 late 연산자를 사용하면 된다. late 연산자를 사용하면, 변수를 널인 상태로 이용하다가 앱이 실행될 때 값을 결정할 수 있다. 즉, late는 초기화를 미루는 연산자이다.

널 안전성 연산자

다트는 널 안전성을 지원하므로 널 허용 변수를 이용할 때는 널에 안전한 코드를 작성할 수 있게 몇 가지 연산자를 제공한다.

! 연산자 - 널인지 점검할 때

어떤 변수가 널인지 점검할 때는 '! 연산자'를 사용한다. 변수 이름 뒤에 ! 를 추가하면, 이 변숫값이 널일 때 런 타임 오류가 발생하게 된다. 널 불허 변수 뒤에 추가할 수도 있지만 널 불허 변수에는 널을 대입할 수 없으므로 의미는 없다.

! 연산자는 변수 이외에 함수 호출 같은 구문에도 사용할 수 있다. 변수든 구문이든 결과가 널이면 런 타임 오류가 발생한다.

?. ?[] 연산자 - 멤버에 접근할 때

널 허용 객체나 리스트의 멤버에 접근할 때는 ?. or ?[] 연산자를 사용해야 한다.
?. 연산자를 사용하면 객체가 널이 아닐 때만 멤버에 접근하게 되고, 널이면 멤버에 접근할 수 없고 null을 반환한다.

Ex)

int? i1 = 10;
bool? r1 = i1?.isEven;

?[] 연산자는 널 허용 List의 데이터를 인덱스로 접근할 때 사용된다.
List 객체가 널이 아닐 때는 데이터에 접근할 수 있으며, 널이면 null을 반환한다.

List<int>? list = [1,2,3];
print('list[0] : ${list?[0]}');

??= 연산자 - 값을 대입할 때

널 허용 변수에 널이 아닌 값만 대입하고 싶다면 ??= 연산자를 사용한다.
??= 연산자는 오른쪽의 대입할 값이 널이 아닐 때만 대입하고 널이면 대입하지 않는다.

Ex)

data ??= 10;

?? 연산자 - 값을 대체할 때

널 허용 변수가 널일 때 대체할 값을 지정하고 싶다면 ?? 연산자를 사용한다.
?? 연산자를 적용한 변수가 널이 아닐 때는 변수 값을 대입하지만, 널일 때는 오른쪽에 지정한 값을 대입한다.

Ex)

String? s1 = 'hi';
String? result = s1 ?? 'hello';

s1이 널이 아닐 때는 'hi'를 대입, 만약 s1이 널이라면 hello 대입.

profile
One-step, one-step, steadily growing developer

0개의 댓글