코틀린 인 액션 7장

존스노우·2023년 4월 1일
0

코틀린

목록 보기
7/10

코틀린답게 사용하기

연산자 오버로딩 기타 관례

  • 언어의 기능과 미리 정해진 이름의 함수를 연결 기법을
  • 관례라고 함. (ex plus메서드 정의는 + 연산자 사용)
  • 관례를 사용하면 자바 코드를 수정하지 않아도 새로운 기능 추가 가능함.
  • 코틀린린은 관례에 의존 함.

  • 출력값 8

왜 쓸 까?

  1. 자바와의 상호 운용성 ,
  • 기존 자바 클래스에 새로운 기능을 쉽게 추가가능.
  1. 객체 지향과 함수형 프로그래밍 스타일 통합

산술 연산자 오버로딩

  • 원시 타입에만 산술 연산자 사용 가능

    이항 산술 연산자 오버로딩

    data class Point(val x: Int, val y: Int) {
        operator fun plus(other: Point): Point {
            return Point(x + other.x, y + other.y)
        }
    }
    
    /// 확잠함수로도 나타냄 .
    operator fun Point.plus(other: Point): Point {
      return Point(x + other.x, y + other.y)
    }
    
    
fun main() {
    val p1 = Point(10, 20)
    val p2 = Point(30, 40)
    println(p1 + p2)
}

// 이런 식으로 두 타입이 같지않더라도 됨 repeat 수행

// int를 넣어주지만 스트링을 반환한다
operator fun Char.times(count: Int): String {
return this.toString().repeat(count)
}

  fun main() {
      println('a' * 3)
  }
 /// aaa
  • plus 함수앞에operator 키워드를붙여야한다

  • 연산자 오버로딩 앞에 필수 operator 예약어

  • 언어에서 미리 정해진 연산자만 오버로딩가능!

  • 일반함수와마찬가지로 operator 함수도 오버로딩할수있다.

     operator fun Point.plus(other: Point): Point {
         return Point(x + other.x, y + other.y)
     }
    
     operator fun Point.plus(value: Int): Point {
         return Point(x + value, y + value)
     }
    
     operator fun Int.plus(point: Point): Point {
         return Point(this + point.x, this + point.y)
     }
     
     fun main() {
       val p1 = Point(10, 20)
       val p2 = Point(30, 40)
    
       // 두 Point 객체를 더하는 함수를 호출합니다.
       val p3 = p1 + p2
       println(p3) // 출력: Point(x=40, y=60)
    
       // Point 객체와 정수 값을 더하는 함수를 호출합니다.
       val p4 = p1 + 5
       println(p4) // 출력: Point(x=15, y=25)
    
       // 정수 값과 Point 객체를 더하는 함수를 호출합니다.
       val p5 = 5 + p1
       println(p5) // 출력: Point(x=15, y=25)
    }
    
    
    ## 복합 대입 연산자
    
  • plus 연산자 오버로딩하면 자동으로

  • += -= 등 연산자도 같이 지원 이런걸 복합 대입 연산자

     operator fun Point.plusAssign(other: Point) {
         x += other.x
         y += other.y
     }
     fun main() {
         var p = Point(10, 20)
         val q = Point(30, 40)
    
         // p와 q를 더한 뒤, 그 결과를 p에 대입합니다.
         p += q
         println(p) // 출력: Point(x=40, y=60)
     }
     
     /// += 참조를 다른 참조로 바꿔 치기하거나...
    /// 객체 내부 상태 변경하고 싶을 때.
       val numbers = ArrayList<Int>()
       numbers += 42
       println(numbers[0])
       // 42
  • 변수가 변경 가능할 경우에만 복합 대입연산자 사용 가능

  • 책에서는 += 연산은 참조를 다른참조로 바꿔치기한다.

  • 왜? 기존 객체 상태를 변경함으로 바꿔치기한다 책에서 표현한듯..

    // 책 코드 예시 
    val numbers = ArrayList<Int>()
    numbers += 42
    println(numbers[0])
    
     
  • += 는 plus와 plusAssign 양쪽 컴파일 할 수있어서.

  • 두 함수 모두 정의시 오류 발생.

  • 클래스 일관적인 설계를 위해 동시 정의 금지!

  • 코틀린 표준라이브러리 컬렉션에는 두가지 접근방법 제시

  • +,- 는 항상 새로운 컬렉션 반환

  • =+ , =- 변경 가능 컬렉션에 작용해 메모리 객체상태 변화 읽기전 용컬렉션인경우 변경 적용한 복사본 반환.

단항 연산자 오버로딩

operator fun Point.unaryMinus(): Point {
return Point(-x, -y)
}

// 예시 코드
val p = Point(10, 20)
println(-p) // 출력: Point(x=-10, y=-20)
  • 단항 연산자는 인자를 취하지 않음.

  • bd ++ -> 후위연산자 bd값 반환하고 증가

  • ++bd -> 전위연산자 bd값 증가시키고 반환

비교 연산자 오버로딩

  • 자바에서 Equals CompareTo 대신

  • 코틀린은 == 직접 사용가능 간결해짐.

    동등성 연산자 equals

  • === 식별자 비교 연산자

class Point(val x: Int, val y: Int) {

    override fun equals(other: Any?): Boolean {
        // 최적화: 파라미터가 "this"와 같은 객체인지 살펴본다.
        if (other === this) return true
        
        // 파라미터 타입을 검사한다.
        if (other !is Point) return false
        
        // Point로 스마트 캐스트해서 x와 y 프로퍼티에 접근한다.
        return other.x == x && other.y == y
    }
}

// 예시 코드
println(Point(10, 20) == Point(10, 20)) // true
println(Point(10, 20) != Point(5, 5)) // true
println(null == Point(1, 2)) // false
  • === 식별자 비교 연산자 오버로딩이 불가능
  • Any에 Operator가 정의되어있어서 오버라이드하면
  • Operator 생략 된다.

순서 연산자 CompareTo

  • 정렬 최소 최댓값 비교에 Comparable 인터페이스 구현해서 구함.

  • 한 객체의 다른객체의 크기를 비교해 정수로 나타내줌

  • 코틀린에서도 CompareTo 지원

  • 관례를 적용해 인터페이스 아넹있는 compareTo 메소드 호출

  • < , > , <= , >= 는 compareTo 호출 컴파일됨.

    class Person(
        val firstName: String,
        val lastName: String
        ) : Comparable<Person> {
          override fun compareTo(other: Person): Int {
          return compareValuesBy(this, other,
          Person::lastName, Person::firstName
          )
    }
    }
    
    val pl = Person("Alice", "Smith")
    val p2 = Person("Bob", "Johnson")
    println(pl < p2) // false
    

  • compareValuesBy 두객 체를 받은뒤 프로퍼티 비교
  • 이때까지 말을 정리하면 ..
  • 자바에서쓰이는 것들을 관례를 통해 좀더 코드를 간결하게작성 된다...

컬렉션 범위에 대해 쓸 수 있는 관례


  • 이미 선언되있는 get , set
  • 간결하게 list[] 등 모든 컬렉션에 쓸 수 있는 이유

in 관례

  • in 연산자와 대응되는 함수는 contain

  data class Rectangle(val upperLeft: Point, val lowerRight: Point)

  operator fun Rectangle.contains(p: Point): Boolean {
      return p.x in upperleft.x until lowerRight.x &&
             p.y in upperleft.y until lowerRight.y
  }

  val rect = Rectangle(Point(10, 20), Point(50, 50))
  println(Point(20, 30) in rect) // true
  println(Point(5, 5) in rect) // false
  •             
  • 열린 범위 10..20 10이상 20 이하
  • 닫힌 범위 10until 20 10 이상 19 이하

rangeTo 관례

  • operator fun <T: Comparable> T.rangeTo(that: T): ClosedRange
  • start..end -> start.rangeTO(end)
  • Comparable 인터페이스를구현하면rangero를정의할필요가없다.
  • Comparable 인터페이스의 정의된 RangeTo 함수 사용하여 구현 되기 때문.
val now = LocalDate.now()
val vacation = now..now.plusDays(10)
printIn(now.plusWeeks(1) in vacation) // true

// now(오늘)부터 시작해 10일짜리 범위를 만든다.
// 그 특정 날짜가 날짜 범위 안에 들어가는지 검사한다.
// now..now.plusDays(10)이라는 식은 컴파일러에 의해 now.rangeTo(now.plusDays(10))으로 변환된다. 
// rangeTo 함수는 LocalDate의 멤버는 아니며, Comparable에 대한 확장 함수이다.
// rangeTo 연산자는 다른 산술 연산자보다 우선순위가 낮다. 하지만 혼동을 피하기 위해 괄호로 인자를 감싸 주면 더 좋다.

val n = 9
printIn(0..(n + 1)) // 0..10
// 0..n + 1이라고 써도 되지만 괄호를 치면 더 의미가 명확해진다.
// 또한 0.n.forEach()와 같은 식은 컴파일할 수 없음에 유의하라. 
// 범위 연산자는 우선순위가 낮아서 범위의 메소드를 호출하려면 범위를 괄호로 둘러싸야 한다.

(0..7).forEach { print("$it ") } // 0 1 2 3 4 5 6 7
// 범위를 괄호로 둘러싸라.
 

For 루프를 위한 Iterator 관례

  • for(x in list) -> List.iterator() 호출

  • hasNext , next 호출 반복식을 ㅗ변환 됨.

    구조 분해 선언과 componet 함수

val p = Point(10, 20)
val (x, y) = p
println(x) // 10
println(y) // 20
  • 구조 분해 선언은 다음 과 같은 관례 사용
  • 각 변수를 초기화 하기위해 componetN 함수 호출
  • 여기서 N은 변수 위치에 따라 붙는 번호
    // data 클래스 타입이 아닌경우 어떻게 구현하는지
    class Point(val x: Int, val y: Int) {
    operator fun component1() = x
    operator fun component2() = y
    }
![](https://velog.velcdn.com/images/superkkj/post/16109ca8-bb3c-4d47-a948-8280994862b2/image.png)

   - Data 클래스에서 주 생성자 프로퍼티는 컴파일러가 자동으로
   - ComponetN 만들어 줌 .
  
   - 구조 분해선언은 함수에서 여러값 반환 할 때 유용
  
  
  
  data class NameComponents(val name: String, val ext: String)

  fun splitFilename(fullName: String): NameComponents {
      val result = fullName.split('.', limit = 2)
      return NameComponents(result[0], result[1])
  }

  val (name, ext) = splitFilename("example.kt")
  println(name)
  println(ext)

///////
  data class NameComponents(val name: String, val extension: String)

fun splitFilename(fullName: String): NameComponents {
    val (name, extension) = fullName.split('.', limit = 2)
    return NameComponents(name, extension)
}

val (name, extension) = splitFilename("example.kt")
println(name)
println(extension)
  • 구조 분해선언을 한 예제들 ,,

  • 코틀린 표준 라이브러리는 componentN 5까지 제공함

  • 책에서는 그이상은 컴파일 오류발생.

    구조 분해 선언과 루프

    fun printEntries(map: Map<String, String>) {
        for ((key, value) in map) {
            println("$key -> $value")
        }
    }
    
    val map = mapOf("Oracle" to "Java", "JetBrains" to "Kotlin")
    printEntries(map)
    
    Oracle -> Java
    JetBrains -> Kotlin
    
  • 맵에서도 이렇게 활용 가능함.. 루프인가..??

  • Map.Entry 확장함수로 componet1,2 제공한다고 책에 나와있다.

프로퍼티 접근자 로직 재활용: 위임 프로퍼티

  • 위임 프로퍼티란 ?
  • 위임 패턴을 사용하면 객체가 직접 작업을 수행하지 않고 다른 도우미 객체가 그 작업을 처리하게 맡기는 디자인 패턴
  • 이해한 바로는 다른 곳에서 get set을 구현해 제공하는 것
  • 단순히 값을 읽거나 저장하는게아니라 로직을 통하는 것 같다.

위임 프로퍼티 소개

  • 위에 내용 코드로 예시를 들어줌

    class Delegate {
        operator fun getValue(thisRef: Any?, property: KProperty<*>): Type {
            // 게터 로직을 정의합니다.
        }
        operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Type) {
            // 세터 로직을 정의합니다.
        }
    }
    
    class Foo {
        var p: Type by Delegate()
    }

위임 프로퍼티 사용: by lazy() 사용한 프로퍼티 초기화 지연

  • 지연 초기화란? 객체 일부분 초기화하지 않고 남겨 뒀다가 실제로 사용시

  • 초기화 하는패턴

    class Person(val name: String) {
        private var remails: List<Email>? = null
        val emails: List<Email>
            get() {
                if (emails == null) {
                    emails = loadEmails(this)
                    //  "_emails" 속성을 사용하여 데이터를 저장하고 이메일의 대리자/객체 역할을 합니다.
                    // Get your email on first access.
                }
                return emails!!
            }
    }
    
    // Example usage:
    val p = Person("Alice")
    println("Loading emails for Alice...")
    println(p.emails) // Loads emails for Alice
    println(p.emails) // Returns previously loaded emails
  • 뒷받침프로퍼티란 속성에대해 get /set을 내가 원하는대로 정의하는 것 그래서 속성을 저장하거나 읽는것

  • 위에 코드에는 뒷받침 프로퍼티 기법을 사용

  • emails는 _emails라는프로퍼티에대한읽기 연 산 을 제 공 한 다 .

  • Null이 될수있는타입 _email , 널이 될수없는타입 Email

    단점

  • 스레드 안전하지 않고,, 지연 초기화 필드가많으면?

  • 위임 프로퍼티를 사용하자!

  • 데이터를 저장할때 쓰이는 뒷받침 프로퍼티 값이 오직 한 번만 초기화됨을 보장하는 게터 로직을 캡슐화해줌 (뒷받침 프로퍼티자동?)

class Person(val name: String) {
    val emails by lazy { loadEmails(this) }
    // Uses the "lazy" delegate to lazily initialize the "emails" property with the result of "loadEmails()"
    // The "this" keyword refers to the current instance of the "Person" class
    // The lambda expression is only called the first time "emails" is accessed, and the result is cached for subsequent accesses
}

  // Example usage:
  val p = Person("Alice")
  println("Loading emails for Alice...")
  println(p.emails) // Loads emails for Alice
  println(p.emails) // Returns previously loaded emails
  • by lazy를 사용함으로서 해결가능
  • 스레드 안전, 동기화락 을 전달 가능

위임 프로퍼티 구현

profile
어제의 나보다 한걸음 더

0개의 댓글