Today I Learned

오류를 예방하는 타입 안정성(1) 본문

Kotlin/다재다능 코틀린 프로그래밍

오류를 예방하는 타입 안정성(1)

하이라이터 2023. 7. 27. 20:23
728x90
  • 코틀린은 함수나 메소드가 null을 받거나 리턴할 수 있는지 명확하게 표현되며, 그 시점도 알 수 있다.
  • 참조가 null이 될 수 있다면 참조하고 있는 객체의 속성이나 메소드를 사용할 땐 언제나 null 체크를 하도록 강제한다.

Any와 Nothing 클래스

  • 코틀린의 모든 클래스는 Java의 Object 클래스처럼 Any 클래스에 상속을 받는다.
  • Any 클래스는 코틀린의 모든 클래스에서 사용 가능한 유용한 메소드를 포함하고 있다.
  • Nothing 클래스는 함수가 아무것도 리턴하지 않을 경우에 리턴하는 클래스이다.

 

베이스 클래스 Any

  • 함수가 여러 타입의 객체를 파라미터로 받는다면, 함수의 파라미터를 Any로 설정해 놓으면 된다.
  • 함수에서 특정 타입을 리턴하기 난감하다면 Any를 리턴하면 된다.
  • Any 클래스는 개발자에게 최대한 유연성을 제공하지만, 제한적으로 사용해야 한다.(과한 기능)
  • Any의 목적은 변수의 타입을 Any로 정의하도록 하는 게 아니라, 모든 코틀린의 타입에 공통으로 적용되는 메소드를 만들기 위해 존재한다.(equals(), hashCode(), toString() 등등)

 

Nothing은 void보다 강력하다

  • 코틀린에서 표현식이 리턴을 하지 않을 때 void 대신 Unit을 사용한다.
  • 함수가 절대로 리턴을 하지 않는 상황이라면 Nothing 클래스를 리턴타입으로 사용한다.
  • 예외는 Nothing 타입을 대표한다.
  • Nothing의 목적은 컴파일러가 프로그램의 타입 무결성을 검증하도록 도와주는 것이다.

 

Null 가능 참조

  • Java에서는 NullPointerException의 해결책으로 Optional을 사용하지만 문제가 있다.
    1. 컴파일러가 Optional을 사용을 강제하지 않고, 개발자가 직접 사용해야 한다. 
    2. Optional이 객체의 참조 또는 null을 감쌀 때 객체가 없다면 작은 오버헤드가 발생한다.
    3. 개발자가 Optional이 아닌 null을 리턴하더라도 컴파일러는 별다른 경고를 주지 않는다.
  • 코틀린은 이런 이슈들이 없으며, 컴파일 시간에 null 체크 여부를 확인시켜준다.
  • 일반적으로 코틀린 코트를 작성할 때 Java와 상호운용할 목적이 아니라면 null과 nullable 타입은 절대 사용하지 않는 편이 좋다.

Null 가능 타입 사용하기

  • null 불가 타입들은 각자 대응하는 null 가능 타입이 존재한다. null 가능 타입은 이름 뒤에 ?가 붙는다.
  • null 가능 참조 타입일 경우, 받는 쪽에서 null 체크를 해야만 사용할 수 있다.

 

세이프 콜 연산자

  • ? 연산자(=세이프 콜 연산자)를 이용하면 메소드 호출 또는 객체 속성 접근과 null 체크를 하나로 합칠 수 있다.
if(name != null) {
  return name.reversed()
}
return null

//세이프 콜 연산자 사용
return name?.reversed()
  • 세이프 콜 연산자를 연결해서 사용할 수도 있다.
return name?.reversed()?.toUpperCase()

 

엘비스 연산자

  • 엘비스 연산자 ?: 를 이용하면 좌측 표현식의 결과가 null이 아닐 경우 결과를 리턴하고 null일 경우 우측 표현식의 결과를 리턴한다.
val result = name?.reversed()?.toUpperCase()
return if (result == null) "Joker" else result

//엘비스 표현식 사용
return name?.reversed()?.toUpperCase() ?: "Joker"

 

사용해서는 안될 안전하지 않은 확정 연산자 !!

  • 특정 참조가 절대 null이 아니란 사실을 알고 있다면 !! 연산자를 사용해서 코틀린에게 null 체크를 할 필요가 없다고 전달할 수 있다.
return name!!.reversed().toUpperCase() //Bad code

 

when의 사용

  • null 가능 참조의 값을 추출할 때는 세이프 콜이나 엘비스 연산자를 사용하고, null 가능 참조에 대한 처리를 할때는 when을 사용한다
fun nickName(name :String?) = when (name) {
  "William" -> "Bill"
  null -> "Joker"
  else -> name.reversed().toUpperCase()
}
  • null에 해당하는 절은 else 위면 어디든 상관없다.
  • null 체크를 했기 때문에 when에 있는 다른 모든 경우 전달받은 참조가 null이 아닌 경우에만 동작한다.

 

타입 체크와 캐스팅

타입 체크

  • 타입 체크는 확장성 측면에서 봤을때 최소한으로만 해야하며, 개방-폐쇄 원칙에도 위배된다.
  • 하지만 실행 시간에 타입 체크를 하는건 유용하고 꼭 필요한 경우도 있다.
    • equal() 메소드를 구현하려면 현재 가지고 있는 객체가 해당 클래스의 인스턴스인지 알아야한다.
    • when의 분기가 인스넡스 타입에 기반해서 이루어지는 경우 타입 체크가 필요하다.

 

is 사용하기

  • 아래 코드에서 Animal 클래스는 equals() 메소드가 모든 Animal 클래스의 인스턴스를 동일하게 취급하도록 오버라이드한다.
  • is 연산자는 객체가 참조로 특정 타입을 가리키는지 확인한다.
class Animal {
  override operator fun equals(other: Any?) = other is Animal
}

val greet: Any = "hello"
var odie: Any = Animal()
var toto: Any = Animal()
println(odie == greet) //false
println(odie == toto) //true
  • 코틀린은 == 연산자를 equals() 메소드에 맵핑해놨다.
  • is 연산자는 모든 타입의 참조에 사용될 수 있다.
  • 참조가 null이라면 is의 연산 결과는 false이다.
  • !is를 사용해서 부정을 나타내는 연산자로 사용할 수도 있다.

 

스마트 캐스트

  • java에서는 instanceof로 타입을 체크했더라도 속성을 참조하려면 직접 캐스팅을 해야한다.
    ex) ((Animal)other).age
  • 코틀린은 참조의 타입이 확인되면 스마트 캐스팅을 한다.
class Animal(val age: Int) {
  override operator fun equals(other: Any?):Boolean {
    return if (other is Animal) age == other.age else false
  }
}
  • other.age에 if문 전에 접근하려고 했다면 컴파일 오류가 났을 것이다.
  • is 연산자로 체크했기 때문에 캐스팅 할 필요가 없다.
  • 스마트 캐스트는 if문 뿐만 아니라 || 혹은 && 연산자 이후에도 작동한다.
override operator fun equals(other: Any?) =
other is Animal && age == other.age
  • 코틀린은 스마트 캐스트가 가능하다면 자동으로 스마트 캐스트를 해준다.
  • 객체가 null 참조가 아니라고 판별하면 null 가능 타입이 null 불가 타입으로 자동으로 캐스팅된다.

 

when과 함께 타입 체크와 스마트 캐스트 사용하기

  • when 명령문 또는 표현식에 is나 !is, 스마트 캐스팅을 사용할 수 있다.
fun whatToDo(dayOfWeek : Any) = when (dayOfWeek) {
  "Saturday", "Sunday", -> "Relax"
  in listOf("Monday", "Tuesday", "Wednesday", "Thursday") -> "Work hard"
  in 2..4 -> "Work hard"
  "Friday" -> "party"
  is String -> "What?, yo provided a String of length ${dayOfWeek.length}"
  else -> "No Clue"
}

 

명시적 타입 캐스팅

  • 명시적 타입 캐스팅은 컴파일러가 타입을 확실하게 결정할 수 없어 스마트 캐스팅을 하지 못할 경우에만 사용해야 한다.
  • 코틀린은 명시적 타입 캐스트를 위해 as와 as?를 제공한다.
  • as 연산자는 우측에 지정된 타입과 같은 타입을 결과로 주며, 캐스팅이 실패하면 에러가 발생하고 종료된다.
  • as?는 null 가능 참조 타입을 결과로 가지며, 캐스팅이 실패하면 null을 할당한다.
728x90
Comments