관리 메뉴

hyeals study

[코틀린] 타입 시스템 (1) - 널 가능성 본문

코틀린

[코틀린] 타입 시스템 (1) - 널 가능성

hyeals 2020. 2. 23. 02:08

※ 코틀린의 타입 시스템(type system)은 자바와 비교해서 코드의 가독성을 향상시키는데 도움을 주는 몇 가지 특성이 있음.

  1. 널이 될 수 있는 타입(nullable type)
  2. 읽기 전용 컬렉션
  3. 배열 지원 

[널 가능성(nullability)]

 

: NullPointerException오류를 피할 수 있게 도움을 주는 코틀린 타입 시스템을 특성

널이 될 수 있는지의 여부를 타입 시스템에 추가함으로써 컴파일러가 컴파일 시 오류를 미리 감지해서 실행 시점에 발생할 수 있는 예외 가능성을 줄일 수 있음.


[널이 될 수 있는 타입]

 

* 코틀린과 자바의 가장 중요한 차이: 코틀린 타입 시스템이 널이 될 수 있는 타입을 명시적으로 지원하는 것.

 

* 널이 될 수 있는 타입: 어떤 프로퍼티나 변수에 null을 허용.

→ 어떤 변수가 null이 될 수 있다면 그 변수에 대해 메소드를 호출하면 NullPointerException이 발생할 수 있어서 안전하지 않음. 따라서 코틀린은 이런 메소드 호출을 금지함으로써 오류를 방지함.

 

자바를 사용한 아래의 코드로 예를 들면, 이 함수에 null을 넘기면 NullPointerException이 발생하기 때문에 안전하지 않음.

자바를 이용한 간단한 함수


이를 코틀린에서 다시 작성해보면 아래와 같음. 널이 인자로 들어올 수 없다면 아래와 같이 함수를 정의할 수 있음.

 

널이 인자로 들어올 수 없는 함수 예시

  • 위의 코드에서 strLen에 null이거나 널이 될 수 있는 인자를 넘기는 것은 금지이며 만약 이러한 값을 넘기게 되면 컴파일 시 오류가 발생함. (바로 아래에 이 결과를 확인할 수 있게끔 예시를 적어둠)

널이 인자로 들어올 수 없는 함수에 널 값 넣은 예시

 

실행 결과

- 위의 에러 설명: 이 코드에서 s의 타입은 항상 String의 인스턴스이어야 하는데 널을 넣었기 때문에 에러가 발생.

 

☞ 위아 같이 코틀린의 컴파일러가 널이 될 수 있는 값을 인자로 넘기지 못하게 막기 때문에 NullPointerException가 실행 시점에서 발생하지 않음을 장담해줌.


* 앞서 본 예시가 & 문자열을 인자로 받을 수 있게끔 하기 위해서는 타입 이름 뒤에 물음표(?)를 명시하면 됨.

 

널이 될 수 있는 타입 명시 예시

  • 위의 코드처럼 어떤 타입이든 타입 이름 뒤에 ?를 붙이면 그 타입의 변수나 프로퍼티에 널 참조를 저장할 수 있다는 뜻임.

* 하지만 널이 될 수 있는 타입의 변수가 있으면 그에 대해 수행할 수 있는 연산이 제한됨.

 

예를 들어, 아래의 코드에서 변수는 널이 될 수 있는 타입이며, 변수.메소드() 처럼 메소드를 직접 호출할 수 없음.

 

널이 될 수 있는 변수 메소드 호출 불가능 예시

 

실행 결과

또한,

  1. 될 수 있는 값될 수 없는 타입의 변수에 대입할 수 없음
  2. 될 수 있는 타입의 값될 수 없는 타입의 파라미터를 받는 함수에 전달 할 수 없음

<널이 될 수 있는 타입의 장점>

☞ 널이 될 수 있는 타입과 널이 될 수 없는 타입을 구분하면 각 타입의 값에 대해 어떤 연산이 가능할지 명확히 이해할 수 있으며, 실행 시점에 예외를 발생시킬 수 있는 연산을 판단할 수 있음.


[호출 연산자 ?.]

 

* ?.: 호출연산자이며, null 검사와 메소드 호출을 한 번의 연산으로 수행함.

→ 예를 들어, s?.toUpperCase()if (s!=null) s.toUpperCase() else null 과 같음

 

☞ 따라서 ?.은 호출하려는 값이 null 이 아니면 일반 메소드 호출처럼 작동함. (호출하려는 값이 null 이면 무시되고 null이 결과값이 됨)

 

호출 연산자 ?. 사용 예시
출력 결과

 

* 메소드 호출뿐만 아니라 프로퍼티를 읽거나 쓸 때도 안전한 호출 사용 가능.


[엘비스 연산자 ?:]

 

* ?:는 null 대신 사용할 디폴트 값을 지정할 때 사용하는 연산자임. (엘비스 연산자(=null coalescing 연산자)라 부름)

 

사용 방법은 아래와 같음

 

엘비스 연산자 ?: 사용 예시

  • 위의 코드에서 s가 null이면 결과가 빈 문자열("")이됨 ( null 이 아니면 결과는 s)

[as? 연산자]

 

* as 연산자: 타입 캐스트 연산자 (https://hyeals.tistory.com/13 참조)

☞ 자바의 타입 캐스트와 마찬가지, 대상 값을 as로 지정한 타입으로 바꿀 수 없으면 ClassCastException이 발생

 

* as? 연산자: 어떤 값을 지정한 타입으로 캐스트. 값을 대상 타입으로 변환할 수 없는 경우 null 반환.

 

* 안전한 캐스트를 할 때 일반적인 패턴: 캐스트 수행엘비스 연산자 사용

☞ 이 패턴을 사용할 경우 장점

  1. 파라미터로 받은 값이 원하는 타입인지 쉽게 검사하고 캐스트할 수 있음
  2. 타입이 맞지 않으면 쉽게 false 반환 가능

 

* 아래의 코드는 as? 연산자를 사용해서 equals 메소드를 구현한 예시임.

 

as? 사용해서 equals 구현 예시
출력 결과

  • val otherPerson = o as? Person ?: return false 부분: 타입이 일치하지 않으면 false 반환
  • equals 메소드 return 부분: as? 연산자를 사용해서 안전한 캐스트를 하고 난 후 otherPerson이 Person 타입으로 스마트 캐스트 됨.
  • == 연산자는 equals 메소드를 호출함

[!! 연산자]

 

* !! 연산자: 어떤 값이든 널이 될 수 없는 타입으로 강제로 바꿀 수 있음.

☞ 컴파일러에게 이 값은 널이 아님을 단언해 주는 것임.


[타입 파라미터 널 가능성]

 

: 코틀린에서 함수 or 클래스의 모든 타입 파라미터는 기본적으로 널이 될 수 있음.

 

* 널이 될 수 있는 타입을 포함하는 어떤 타입이라도 타입 파라미터를 대신할 수 있음.

☞ 타입 파라미터 T를 클래스나 함수 안에서 타입 이름으로 사용하면 이름 끝에 물음표가 없어도 T가 널이 될 수 있는 타입임.

 

널이 될 수 있는 타입 파라미터 예시

  • println(t?.hashCode()) 부분: t가 널이 될 수 있으므로 ?.를 사용해서 안전한 호출을 해야 함
  • printHashCode(null) 부분: T의 타입이 Any?로 추론됨.

* 만약 타입 파라미터가 널이 아님을 확실히 하고 싶으면 널이 될 수 없도록

"fun <T:Any> printHashCode(t:T)" 처럼 타입 상한(upper bound)을 두면 됨.

 

 

 

본 게시물은 "Kotlin IN ACTION"을 참고함.

Comments