티스토리 뷰

728x90

클래스 종류

 

  1. 이넘 클래스
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
250x250
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2025/02   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28
글 보관함