[코틀린 인 액션] CH6 코틀린 타입 시스템

0

코틀린 인 액션

목록 보기
7/13
post-thumbnail
post-custom-banner

[코틀린 인 액션] CH6 코틀린 타입 시스템

이 포스팅은 <<Kotlin in Action>>, 드미트리 제메로프 & 스베트라나 이사코바, 에이콘출판사(2017)을 읽고 개인 학습용으로 정리한 글입니다.

6.1 널 가능성

  • 널 가능성(nullability): NullPointerException(NPE) 오류를 피할 수 있게 돕기 위한 코틀린 타입 시스템 특성

6.1.1 널이 될 수 있는 타입

  • 어떤 타입이든 타입 이름 뒤에 물음표를 붙이면: 그 타입의 변수나 프로퍼티에 null참조를 저장할 수 있음

  • 물음표가 없는 타입: 그 변수가 null 참조를 저장할 수 X
    -> *모든 타입 기본적으로 널이 될 수 없음

  • 널이 될 수 있는 타입인 변수에 대해 변수.메서드()처럼 메서드 직접 호출 X

  • 널이 될 수 있는 값을 널이 될 수 없는 타입의 변수에 저장 X

  • 널이 될 수 있는 값을 널이 될 수 없는 타입의 파라미터를 받는 함수에 전달 X

  • null 검사를 추가해야 코드가 컴파일된다

6.1.2 타입의 의미

  • 실행 시점에서 널이 될 수 있는 타입이나 널이 될 수 없는 타입의 객체는 같다
    -> 널이 될 수 있는 타입은 널이 될 수 없는 타입을 감싼 래퍼 타입 X
    -> 널이 될 수 있는 타입 처리 부가 비용 X

NPE 오류를 다루는 방법

  • 자바 애노테이션 사용(@Nullable, @NotNull)
  • null값을 코드에서 절대로 쓰지 않는 것
    -> null 대신 null을 감싸는 특별한 래퍼 타입 활용
    (자바8에 새로 도입된 Optional 타입: 어떤 값이 정의되거나 정의되지 않을 수 있음을 표현)

6.1.3 안전한 호출 연산자: ?.

  • null 검사와 메서드 호출을 한 번의 연산으로 수행
    -> 호출하려는 값이 null이 아니면: 일반 메서드처럼 호출
    -> 호출하려는 값이 null이면: 호출 무시, null이 결과 값

  • 안전한 호출의 결과 타입: 널이 될 수 있는 타입

6.1.4 엘비스 연산자: ?:

  • null 대신 사용할 디폴트 값을 지정할 때 편리

  • 이항 연산자로 좌항을 계산한 값이 null인지 검사
    -> 좌항 값이 null이 아니면: 좌합 값이 결과 값
    -> 좌항 값이 null이면: 우항 값이 결과 값

  • 코틀린에서는 return이나 throw등의 연산: 식
    -> 엘비스 연산자의 우항에 넣을 수 있음
    -> 함수의 전제조건(precondition)검사에 유용

6.1.5 안전한 캐스트: as?

  • 코틀린 타입 캐스트 연산자 as: 대상 값을 지정한 타입으로 바꿀 수 없으면 ClassCastException 발생
    -> is를 통해 미리 as로 변환 가능한 타입인지 검사해야
  • as?연산자는 대산 값을 지정한 타입으로 바꿀 수 없으면 null 반환

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

6.1.6 널 아님 단언: !!

  • 널 아님 단언(not null assertion): 어떤 값이든 널이 될 수 없는 타입으로 (강제로) 바꿈
    -> 실제 null에 대해 !! 적용하면 NPE 발생

  • !!를 null에 대해 사용해서 발생하는 예외의 스택 트레이스(stack trace)
    -> 어떤 파일의 몇 번째 줄인지에 대한 정보 O
    -> 어떤 식에서 예외가 발생했는지에 대한 정보 X
    -> 어떤 값이 null이었는지 확실히 하기 위해 여러 !! 단언문 한 줄에 쓰지 X

6.1.7 let 함수

  • let 함수는 자신의 수신 객체를 인자로 전달받은 함다에게 넘김

  • 안전한 호출 구문을 사용해 let 호출 & 널이 될 수 없는 타입을 인자로 받는 람다 let에 전달
    -> 수신 객체 null이 아닌 경우: 널이 될 수 없는 타입의 값으로 람다에 전달
    -> 수신 객체 null인 경우: 아무 일도 일어나지 않는다

6.1.8 나중에 초기화할 프로퍼티

  • 코틀린에서는 일반적으로 생성자에서 모든 프로퍼티 초기화해야
    프로퍼티 타입이 널이 될 수 없는 타입이라면 반드시 널이 아닌 값으로 초기화해야

  • lateinit변경자를 붙이면 프로퍼티 나중에 포기화 가능

  • 나중에 초기화하는 프로퍼티는 항상 var이어야 함
    (val 프로퍼티는 final 필드로 컴파일됨 -> 생성자 안에서 반드시 초기화해야)

  • 나중에 초기화하는 프로퍼티를 초기화하기 전에 접근하면
    -> lateinit property has not been initialized 예외 발생

6.1.9 널이 될 수 있는 타입 확장

  • ⚡일반 멤버 호출은 객체 인스턴스를 통해 디스패치 됨 -> 그 인스턴스가 널인지 여부 검사 X

  • 동적 디스패치: 객체의 동적 타입에 따라 적절한 방식을 호출해주는 방식
    정적 디스패치: 컴파일러가 컴파일 시점에 어떤 메서드가 호출될지 결정하는 방식

  • null이 될 수 있는 타입에 대해 확장 함수 정의하면
    -> 널이 될 수 있는 값에 대해 안전한 호출 없이도 호출 가능
    -> 그 확장 함수 내부에서 this는 널이 될 수 있음 -> 명시적으로 널 여부 검사

  • 자바에서 메서드 안의 this는 항상 널이 아니지만, 코틀린에서는 가능함

  • 예. String? 타입의 수신객체에 대해 호출할 수 있는 isNullOrEmpty, isNullOrBlank 메서드

6.1.10 타입 파라미터의 널 가능성

  • 코틀린에서는 함수나 클래스의 모든 타입 파라미터 기본적으로 널 가능
    -> 타입 파라미터 T 이름 끝에 ?가 없더라도 T는 널이 될 수 있는 타입

  • 타입 파라미터가 널이 아님을 확실히 하려면 널이 될 수 없는 타입 상한(upper bound) 지정해야

fun <T> printHashCode(t:T){
	println(t?.hashCode()) //T의 타입 Any?로 추론됨
}

fun <T:Any> printHashCode(t:T){
	println(t.hashCode())
}

6.1.11 널 가능성과 자바

플랫폼 타입

  • 코틀린이 널 관련 정보를 알 수 없는 타입
    -> 널이 될 수 있는 타입으로 처리해도 되고 널이 될 수 없는 타입으로 처리해도 됨
    -> 컴파일러는 모든 연산 허용

  • !표기: 타입의 널 가능성에 대해 아무 정보 없다는 의미

  • 코틀린에서 플랫폼 타입 선언 X
    자바에서 가져온 타입만 플랫폼 타입

  • 공개 가시성인 코틀린 함수의 널이 아닌 타입의 파라미터와 수신 객체
    -> 코틀린 컴파일러가 널 검사 추가해줌
    -> 함수 호출 시점에 검사
    -> 널 값을 사용하면 즉시 예외 발생

상속

  • 자바 클래스나 인터페이스를 코틀린에서 구현할 경우 널 가능성 처리 중요

  • 코틀린 컴파일러는 구현 메서드의 널이 될 수 없는 타입으로 선언한 모든 파라미터에 대해 널 아님을 검사하는 단언문 만들어줌

6.2 코틀린의 원시 타입

6.2.1 원시 타입: Int, Boolean 등

  • 자바는 원시 타입과 참조 타입 구분

    • 원시 타입(primitive type): 변수에 그 값이 직접 들어감
    • 참조 타입(reference type): 변수에 메모리 상의 객체 위치 들어감
  • ⚡원시 타입 값에 대해 메서드 호출 X
    컬렉션에 원시 타입 값 담을 수 X
    -> 참조 타입이 필요한 경우 특별한 래퍼 타입으로 원시 타입 값 감싸서 사용

  • 코틀린은 원시 타입과 래퍼 타입 구분 X, 항상 같은 타입 사용
    -> 원시 타입 값에 대해 메서드 호출 O

6.2.2 널이 될 수 있는 원시 타입: Int? Boolean? 등

  • null은 자바의 참조 타입 변수에만 대입 가능
    -> 널이 될 수 있는 코틀린 타입은 자바 원시 타입으로 표현 불가능
    -> 자바의 래퍼 타입으로 컴파일됨

  • 자바나 코틀린 모두 제네릭 클래스는 박스 타입 사용
    -> 타입 인자로 원시 타입 넘기면 그 타입에 대한 박스 타입(래퍼 타입)사용

6.2.3 숫자 변환

  • 코틀린은 한 타입의 숫자를 다른 타입의 숫자로 자동 변환 X
    -> 직접 변환 메서드 호출해야

  • 코틀린은 모든 원시 타입(Boolean 제외)에 대한 변환 함수 제공

  • 두 박스 타입 간의 equals 메서드는 그 안에 들어있는 값이 아니라 박스 타입 객체를 비교

  • 숫자 리터럴을 사용할 때는 변환 함수 호출 필요 X
    -> 상수 뒤에 타입을 표현하는 문자 붙이기 (ex. 42L, 42.0f)
    -> 타입이 알려진 변수에 대입하면 컴파일러가 자동으로 변환

6.2.4 Any, Any?: 최상위 타입

  • 자바 Object: 클래스 계층(참조 타입만 포함)의 최상위 타입
    코틀린 Any: 모든(원시 타입 포함) 널이 될 수 없는 타입의 조상 타입

  • 코틀린에서 널을 포함하는 모든 값을 대입할 변수를 선언하려면 Any? 타입 사용

  • 모든 코틀린 클래스에는 toString, equals, hashCode라는 세 메서드 들어있음
    -> 이 세 메서드는 Any에 정의된 메서드를 상속한 것

6.2.5 Unit 타입: 코틀린의 void

  • 코틀린의 Unit타입: 자바 void와 같은 기능

  • Unit은 모든 기능을 갖는 일반적인 타입
    -> void와 달리 타입 인자로 쓸 수 O

  • Unit 타입에 속한 값 하나 뿐, 그 이름도 Unit

  • Unit 타입의 함수는 Unit 값을 묵시적으로 반환
    -> 컴파일러가 묵시적으로 return Unit 넣어줌

6.2.6 Nothing 타입: 이 함수는 결코 정상적으로 끝나지 않는다

  • 결코 성공적으로 값을 돌려주는 일이 없으므로 '반환 값' 개념 자체가 없는 의미 없는 함수 존재

  • Nothing 타입은 아무 값도 포함하지 X
    -> 함수의 반환 타입이나 반환 타입으로 쓰일 타입 파라미터로만 사용 가능
    -> Nothing 타입의 변수를 선언하더라도 아무 값도 저장할 수 X

6.3 컬렉션과 배열

6.3.1 널 가능성과 컬렉션

  • 널이 될 수 있는 값으로 이뤄진 컬렉션: 널 값을 걸러내는 경우가 자주 있음
    -> 코틀린 표준 라이브러리는 filterNotNull 함수 제공

6.3.2 읽기 전용과 변경 가능한 컬렉션

  • 코틀린에서는 컬렉션의 데이터를 접근하는 인터페이스와 컬렉션 안의 데이터를 변경하는 인터페이스 분리

    • kotlin.collections.Collection
    • kotlin.collections.MutableCollection
  • kotlin.collections.MutableCollection은 kotlin.collections.Collection을 확장

  • 방어적 복사(defensive copy): 원본의 변경을 막기 위해 변경 가능한 컬렉션을 읽기 전용 컬렉션으로 복사하여 전달

  • 읽기 전용 컬렉션이 꼭 변경 불가능한 컬렉션일 필요 X
    -> 읽기 전용 인터페이스 타입인 변수를 사용할 때
    그 인터페이스는 실제로는 어떤 컬렉션 인스턴스를 가리키는 수많은 참조 중 하나일 수 있음

  • 읽기 전용 컬렉션이 항상 스레드 안전하지는 않다

6.3.3 코틀린 컬렉션과 자바

  • 자바는 읽기 전용 컬렉션과 변경 가능 컬렉션 구분 X
    -> 코틀린에서 읽기 전용 컬렉션으로 선언된 객체라도 자바 코드에서는 내용 변경 가능

  • 컬렉션을 변경하는 자바 메서드에 읽기 전용 컬렉션을 넘겨도 컴파일러가 막을 수 X

  • 컬렉션에 null을 넣는 자바 메서드에 널이 아닌 원소로 이루어진 컬렉션을 넘겨도 컴파일러가 막을 수 X

6.3.4 컬렉션을 플랫폼 타입으로 다루기

  • 자바 컬렉션 타입을 어떤 코틀린 타입으로 표현할지 결정해야
    • 컬렉션이 널이 될 수 있는가?
    • 컬렉션의 원소가 널이 될 수 있는가?
    • 오버라이드하는 메서드가 컬렉션을 변경할 수 있는가?

6.3.5 객체의 배열과 원시 타입의 배열

  • 기본적으로는 배열보다 컬렉션을 더 먼저 사용해야

  • 컬렉션 -> 배열 변환: toTypedArray 매서드 사용

  • 코틀린에서 배열을 만드는 방법:

    • arrayOf 함수에 원소 넘기기
    • arrayOfNulls 함수에 정수값 인자로 넘김
      -> 모든 원소 null, 배열의 크기 = 인자로 넌긴 값
    • Array 생성자에 배열 크기와 람다 인자로 넘김
      -> 람다를 호출하여 각 배열 원소 초기화해줌
      -> 람다는 배열 원소의 인덱스를 인자로 받아서 배열의 해당 위치에 들어갈 원소 반환
  • 코틀린은 원시 타입의 배열을 표현하는 별도의 클래스를 워시 타입마다 하나씩 제공
    (ex. IntArray, ByteArray, ...)
    -> 자바의 원시 타입 배열로 컴파일됨 (int[], byte[], ...)
    -> 배열의 값 박싱되지 X

  • 코틀린 표준 라이브러리는 배열에 배열 기본 연산 + 컬렉션에 사용할 수 있는 모든 확장 함수 제공

📌참고자료

profile
Be able to be vulnerable, in search of truth
post-custom-banner

0개의 댓글