티스토리 뷰
728x90
클래스 종류
- 이넘 클래스
enum class Week {
MON, TUE, WED, THU, FRI, SAT, SUN
}
fun Week.isWorkDay() =
this != Week.SAT && this != Week.SUN
fun main() {
println(Week.MON.isWorkDay())
println(Week.SAT.isWorkDay())
}
- 미리 정의된 상수의 집합을 표현하는 클래스. (컴파일 시점 상수)
- 어떤 값이 가능한 범위 안에 들어가 있는지 일일이 검사할 필요가 없어 type-safe하다.
- 컴파일러는 이넘 타입의 변수가 이넘 클래스에 정의된 값 중 하나를 사용하는지 검사한다.
- 미리 정해진 전역 상수를 정의하기 때문에, 객체와 마찬가지로 내부 클래스나 함수 본문에 정의할 수 없다.
enum class Direction {
NORTH, SOUTH, WEST, EAST
}
fun rotate(direction: Direction) = when (direction) {
Direction.NORTH -> Direction.EAST
Direction.SOUTH -> Direction.WEST
Direction.WEST -> Direction.NORTH
Direction.EAST -> Direction.SOUTH
}
fun main() {
println(rotate(Direction.EAST))
}
- when을 통해 이넘 변수를 각각의 값과 비교할 수 있다.
- when 식에서 모든 이넘 상수를 다룬 경우는 else를 생략해도 된다.
- 빠진 부분이 없는 when 식에는 NoWhenBranchMatchedException 예외를 던지는 else가 암시적으로 추가된다.
enum class Direction {
NORTH, SOUTH, WEST, EAST;
val lowerCase get() = name.lowercase()
}
- 이넘 클래스에 멤버가 있는 경우, 상수 목록 뒤에 와야 한다.
- 상수 목록과 다른 부분을 구분하기 위해 상수 목록을 세미콜론으로 끝내야 한다.
enum class Week(val isWork: Boolean) {
MON(true), TUE(true), WED(true), THU(true), FRI(true), SAT(false), SUN(false);
}
fun main() {
println(Week.MON.isWork)
println(Week.SAT.isWork)
}
- 이넘 클래스에 생성자가 있으면 각 이넘 상수 정의 뒤에 생성자 호출을 추가해야 한다.
- 이넘 상수에 본문이 포함될 수 있다. (익명 타입)
interface Test {
val i: Int
}
enum class Direction : Test {
NORTH { override val i = 1 },
SOUTH { override val i = 2 }
}
- 이넘 클래스는 인터페이스를 구현할 수 있다.
- 모든 상수에 대해 각각 인터페이스 멤버를 구현해야 한다.
enum class Direction {
NORTH, SOUTH, WEST, EAST
}
fun main() {
println(Direction.WEST.name) // WEST
println(Direction.WEST.ordinal) // 2
println(Direction.SOUTH >= Direction.NORTH) // true ( 1 >= 0 )
}
- 모든 이넘 클래스는 kotlin.Enum 클래스의 하위 타입이다.
- 이넘 값 순서에 따른 인덱스 ordinal, 이넘 값의 이름 name 프로퍼티를 제공한다.
- 이넘값의 비교는 각각의 ordinal 값에 따라 정해진다.
- 이넘 클래스는 Comparable 인터페이스를 암시적으로 구현한다.
EnumClass.valueOf(value: String): EnumClass
EnumClass.values(): Array<EnumClass>
enum class RGB { RED, GREEN, BLUE }
fun main() {
for (color in RGB.values()) println(color.toString())
println(RGB.valueOf("RED"))
}
- valueOf()는 이넘 값의 이름을 문자열로 넘기면 이넘 값을 돌려준다. (이름이 잘못된 경우 예외 던짐)
- values()는 모든 이넘 값이 들어있는 배열을 반환한다.
- values()는 호출할 때마다 새로운 배열 인스턴스가 생성된다. (→ entries는 미리 할당된 immutable list 반환)
- 제네릭 메서드 enumValues(), enumValueOf()를 사용할 수 있다.
2. 데이터 클래스
- 데이터를 저장하기 위한 목적으로 쓰이는 클래스
- 주생성자의 프로퍼티에 관련된 각종 멤버를 암시적으로 포함시킨다.
- 최소 하나의 주생성자 프로퍼티가 있어야 한다.
- 주생성자의 매개변수는 val/var 키워드를 통해 프로퍼티를 생성해야 한다.
- abstract, open, sealed, inner 클래스일 수 없다.
- equals() / hashCode()
data class Person(val name: String) {
var age: Int = 0
}
fun main() {
val person1 = Person("John")
val person2 = Person("John")
person1.age = 10
person2.age = 20
println(person1 == person2) // true
}
-
- 데이터 클래스의 인스턴스끼리 비교하면, 주소값이 아닌 주생성자 프로퍼티의 값을 비교한다.
- 명시적으로 euqals(), hashCode(), toString()을 구현하거나 부모 클래스에 final 메소드로 구현된 경우 기존 구현이 사용된다.
- toString()
data class Person(val name: String, val age: Int)
fun main() {
val person1 = Person("John", 24)
val person2 = Person("John", 20)
println(person1) // Person(name=John, age=24)
println(person2) // Person(name=John, age=20)
}
-
- 클래스 인스턴스의 주생성자 프로퍼티 값을 출력한다.
- copy()
fun copy(name: String = this.name, age: Int = this.age) = Person(name, age)
data class Person(val name: String, val age: Int)
fun main() {
val person = Person("John", 24)
println(person.copy()) // Person(name=John, age=24)
println(person.copy(age = 20)) // Person(name=John, age=20)
}
-
- 데이터 클래스의 인스턴스를 복사하면서 일부 프로퍼티를 변경할 수 있다.
- 주생성자의 시그니처와 같다.
- 디폴트 값으로 원본 인스턴스의 프로퍼티 값이 지정된다.
- 구조 분해 선언 (Destructuring declaration)
data class Person(val name: String, val age: Int)
fun main() {
val (name, age) = Person("John", 24)
}
-
- 여러 변수를 한꺼번에 정의할 수 있다.
- 프로퍼티의 위치를 기준으로 변수에 매칭된다.
- 프로퍼티의 수보다 적은 수의 변수가 들어갈 수 있다.
- 사용하지 않는 프로퍼티를 _로 대체한다.
- 데이터 클래스에서 제공하는 componentN() 함수를 호출하여 반환된다.
- 람다 파라미터와 for 루프, Map에서도 사용할 수 있다.
- 지역 변수에서만 사용할 수 있다.
- Pair, Triple
fun main() {
val pair = Pair(1, "two") // first, second
val triple = Triple("one", 2, 4) // first, second, third
}
-
- 표준 라이브러리에서는 두 가지 값을 저장하는 Pair, 세 가지 값을 저장하는 Triple 데이터 클래스를 제공한다.
3. 인라인 클래스
- 래퍼 클래스는 추가적인 힙 할당으로 인해 런타임 오버헤드가 발생한다. (원시 타입인 경우 더 많은 부가 비용)
- 이를 해결하기 위해 인라인 클래스를 사용한다.
@JvmInline
value class Password(private val s: String)
- 주생성자에 불변 프로퍼티 하나만 선언해야 한다.
- 런타임에 클래스 인스턴스는 별도의 래퍼 객체를 생성하지 않고 이 프로퍼티 값으로 대체된다.
- 함수와 프로퍼티, init 블록을 가질 수 있다.
- 하위 클래스를 가질 수 없는 final class다.
- 프로퍼티는 backing field를 사용할 수 없다.
- lateinit를 사용할 수 없다.
- 접근자를 명시해야 한다.
- 인터페이스를 구현할 수 있으며 클래스를 상속할 수 없다.
interface I
@JvmInline
value class Foo(val i: Int) : I
fun asInline(f: Foo) {}
fun asInterface(i: I) {}
fun asNullable(i: Foo?) {}
fun main() {
val f = Foo(42)
asInline(f) // unboxed
asInterface(f) // boxed
asNullable(f) // boxed
}
- 컴파일러는 가능하면 박싱하지 않은 값을 사용하려 한다.
- 하지만 박싱하지 않은 값을 사용할 수 없는 경우, 인라인되지 않는 형태로 클래스를 사용한다.
- 인라인 클래스가 래핑하고 있는 타입과 정확히 상응하는 타입의 경우 인라인된다.
- 인라인 클래스는 일반 값과 래퍼 클래스 모두 표현될 수 있기 때문에 참조 동등성을 사용할 수 없다.
- 맹글링
@JvmInline
value class UInt(val x: Int)
fun compute(x: Int) { }
fun compute(x: UInt) { }
-
- 컴파일 시에 함수 시그니처가 충돌되는 것을 방지하기 위해 인라인 클래스를 사용하는 함수는 함수명 뒤에 해시 코드를 추가한다.
- type alias vs inline class
typealias TypeAlias = String
@JvmInline
value class TypeInlineClass(val s: String)
fun acceptString(s: String) {}
fun acceptTypeAlias(n: TypeAlias) {}
fun acceptTypeInlineClass(p: TypeInlineClass) {}
fun main() {
val typeAlias: TypeAlias = ""
val typeInlineClass: TypeInlineClass = TypeInlineClass("")
val string: String = ""
acceptString(typeAlias) // OK
acceptString(typeInlineClass) // error
acceptTypeAlias(string) // OK
acceptTypeInlineClass(string) // error
}
-
- type alias와 비슷해 보이지만 인라인 클래스는 함수와 프로퍼티를 추가할 수 있다.
- type alias은 기존 타입이 쓰이는 코드에 동일하게 쓰일 수 있지만 인라인 클래스는 가능하지 않다.
- unsigned integer
- 부호 없는 정수는 인라인 클래스로 정의되어 있다.
- 부호가 있는 일반 정수 타입 이름 앞에 U를 붙여 사용한다.
- 부호 없는 값을 표현하기 위해 정수 리터럴 뒤에 U/u를 붙인다.
fun main() {
val a: Int = 1000u // error
val b: Int = 1000u.toInt() // Ok
val c: UInt = 1000 // error
val d: UInt = 1000.toUInt() // Ok
}
-
- 부호 있는 타입과 없는 타입은 서로 호환되지 않는다.
- toInt(), toUInt() 등의 함수로 변환한다.
728x90
'kotlin' 카테고리의 다른 글
[kotlin/코틀린] 컬렉션 (0) | 2023.07.19 |
---|---|
[kotlin/코틀린] 클래스 계층 (0) | 2023.07.19 |
[kotlin/코틀린] 영역 함수 (0) | 2023.07.19 |
[kotlin/코틀린] 확장 (0) | 2023.07.19 |
[kotlin/코틀린] 고차 함수 (0) | 2023.07.19 |