본문 바로가기
  • GDG on campus Ewha Tech Blog
3-1기 스터디/Kotlin

[12주차] 코틀린 타입 시스템(2)

by 이긍빈 2022. 1. 30.

 

프로그램에서 사용하는 Int, Boolean, Any 등의 원시 타입에 대해 살펴보자. 코틀린은 원시 타입과 래퍼 타입 구분하지 않는다.

 

6.2.1 원시 타입: Int, Boolean 등

자바에서는 원시 타입 (primitive type)과 참조 타입 (reference type)을 구분한다. 하지만 코틀린에서는 구분하지 않으므로 항상 같은 타입 사용.

val i : Int = 1
val list : List<Int> = listOf(1,2,3)

 

코틀린에서는 숫자 타입 등 원시 타입의 값에 대해 메소드 호출 가능. 아래 예시 코드는 표준 라이브러리 함수 coerceIn을 사용해 값을 특정 범위로 제한.

fun showProgress(progress:Int) {
		val percent = progress.coerceIn(0, 100)
		println("We're ${percent}% done!")
}

>>> showProgress(146)
We're 100% done!

실행 시점, 숫자 타입은 가장 효율적인 방식으로 표현. 변수, 프로퍼티, 파라미터, 반환 타입 등 코틀린의 Int 타입은 자바 int 타입으로 컴파일. 컬렉션과 같은 제네릭 클래스 사용할 경우 컴파일이 불가.

 

자바 원시 타입에 해당하는 타입

  • 정수 타입 : Byte, Short, Int, Long
  • 부동소수점 수 타입 : Float, Dougle
  • 문자 타입 : Char
  • 불리언 타입 : Boolean

 

Int와 같은 코틀린 타입에는 널 참조 들어갈 수 없으므로 그에 상응하는 자바 원시 타입으로 컴파일 가능. 마찬가지로 자바 원시 타입 값은 널이 될 수 없으므로 자바 원시 타입을 코틀린에서 사용할 때도 널이 될 수 없는 타입으로 취급 가능.

 

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

null 참조 : 자바의 참조 타입 변수에만 대입 가능. 널이 될 수 있는 코틀린 타입은 자바 원시 타입으로 표현 불가.

 

data class Person(val name: String,
									val age: Int? = null) {
		fun isOlderThan(other: Person) : Boolean? {
				if (age == null || other.age == null)
						return null
				return age > other.age
		}
}

>>> println(Person("Sam", 35).isOlderThan(Person("Amy", 42)))
false
>>> println(Person("Sam", 35).isOlderThan(Peron("Jane")))
null

 

제네릭 클래스 : 래퍼 타입 사용.

어떤 클래스의 타입 인자로 원시 타입을 넘기면 코틀린은 그 타입에 대한 박스 타입 사용한다. JVM은 타입 인자로 원시 타입 허용하지 않기 때문에 자바, 코틀린 모두에서 제네릭 클래스는 항상 박스 타입 사용해야 함

 

6.2.3 숫자 변환

코틀린 : 한 타입의 숫자를 다른 타입의 숫자로 자동 변환하지 않음

결과 타입이 허용핳는 숫자의 범위가 원래 타입의 범위보다 넓은 경우에도 자동 변환 불가

val i = 1
val l: Long = i

코틀린 컴파일러는 위의 코드 거부. 아래와 같이 직접 변환 메소드 호출해야 함.

val i = 1
val l: Long = i.toLong()

 

코틀린은 모든 원시 타입(Boolean 제외)에 대한 변환 함수 제공. toByte(), toShort(), toChar() 등. 양방향 변환 함수가 모두 제공

코드에서 동시에 여러 숫자 타입 사용하려면 예상치 못한 동작 피하기 위해 각 변수를 명시적으로 변환해야 함

숫자 리터럴을 사용할 때는 보통 변환 함수 호출할 필요 없음. 사용자가 직접 변환하지 않아도 숫자 리터럴을 타입이 알려진 변수에 대입하거나 함수에게 인자로 넘기면 컴파일러가 필요한 변환을 자동으로 넣어줌.

 

산술 연술자는 적당한 타입의 값을 받아들일 수 있게 이미 오버로드 되어 있다. 예를 들어 별도 변환 없어도 아래의 코드는 잘 작동이 됨.

fun foo(l: Long) = println(l)
>>> val b : Byte = 1
>>> val l = b + 1L
>>> foo(42)
42

코틀린 산술 연산자에도 숫자 연산 시 값 넘침(overflow)이 발생할 수 있음. 코틀린은 오버플로우를 검사하느라 추가 비용 들이지 않음.

 

6.2.4 Any, Any? : 최상위 타입

코틀린에서 Any 타입 : 모든 널이 될 수 없는 타입의 조상 타입

자바처럼 코틀린에서도 원시 타입 값을 Any 타입 변수에 대입하면 자동으로 값을 객체로 감싼다

val answer: Any = 42
//Any가 참조 타입이기 때문에 42가 박싱됨

Any가 널이 될 수 없는 타입. 따라서 Any 타입 변수에는 null이 들어갈 수 없음. 코틀린에서 널을 포함하는 모든 값ㅇ르 대입할 변수를 선언하려면 Any? 타입을 사용해야 함.

 

6.2.5 Unit 타입: 코틀린의 void

코틀린 Unit 타입 : 자바 void와 같은 기능. 관심 가질만한 내용을 전혀 반환하지 않는 함수의 반환 타입으로 Unit 쓸 수 있음

fun f() : Unit { ... }

위는 반환 타입 선언 없이 정의한 블록이 본문인 함수와 같음

 

fun f() { ... }

코틀린 함수의 반환 타입이 Unit이고 그 함수가 제네릭 함수를 오버라이드 하지 않는다면 그 함수는 내부에서 자바 void 함수로 컴파일. 코틀린 함수를 자바에서 오버라이드 하는 경우 void를 반환 타입으로 해야 함

 

코틀린의 Unit과 자바 void의 다른 점

→ Unit은 모든 기능을 갖는 일반적인 타입, void와 달리 Unit을 타입 인자로 쓸 수 있음

→ Unit 타입에 속한 값은 단 하나 뿐이며, 이름도 Unit.

→ Unit 타입의 함수는 Unit 값을 묵시적으로 반환

 

interface Processor<T> {
	fun process () : T
}

class NoResultProcessor : Processor<Unit> {
	overrice fun process() {
			//업무 처리 코드
	}
}

 

왜 코틀린에서 void가 아니라 Unit이라는 이름을 골랐을까?

→ 함수형 프로그래밍에서 전통적으로 Unit은 '단 하나의 인스턴스만 갖는 타입'을 의미

 

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

코틀린에서는 결코 성공적으로 값을 돌려주는 일이 없음. 따라서 '반환 값'이라는 개념 자체가 의미 없는 함수가 일부 존재

fun fail(message: String) : Nothing {
		throw IllegalStateException(message)
}

>>> fail("Error occurred")
java.lang.IllegalStateException: Error occurred

Nothing 타입은 아무 값도 포함하지 않음. 따라서 Nothing은 함수의 반환 타입이나 반환 타입으로 쓰일 타입 파라미터로만 쓸 수 있음. 이외 다른 용도로 사용하는 경우 Nothing 타입의 변수를 선언하더라도 그 변수에 아무 값도 저장할 수 없으므로 아무 의미 없음.

Nothing 반환하는 함수를 엘비스 연산자의 우항에 사용해서 전제조건 검사 가능

 

val address = company.address ?: fail("No address")
println(address.city)

컴파일러는 Nothing이 반환 타입인 함수가 결코 정상 종료되지 않음을 알고 그 함수를 호출하는 코드를 분석할 때 사용.

 

댓글