이번 챕터는 내용이 재밌어서 개인적 공부 , 견해, 책 내용을 정리 해 보았다

 

30 : 확장 함수(Extension function)

기존 클래스에 “멤버 함수” 를 추가하는 효과

  • 따라서 확장함수에서는 ,  확장 타겟 클래스의 멤버들에 접근 가능 (this 생략 가능)
    • 물논 public 멤버 들에 대해서만 !! 접근 가능
  • 확장 타겟 클래스 : receiver type(수신 객체 타입)
package ExtensionFunctions

import atomictest.eq

fun String.singleQuote() = "'$this'" // 수신타입.확장함수 
fun String.doubleQuote() = "\\"$this\\""

fun main() {
    "Hi".singleQuote() eq "'Hi'" // 'Hi'
    "Hi".doubleQuote() eq "\\"Hi\\"" // "Hi"
}
  • 이 확장함수를 , “해당 확장함수가 정의되지 않은 다른 패키지” 에서 사용하려면 import 해야함
    • 확장함수 자체를 import 하는게 가능함!! ( 우리는 이미 Java 의 Collection 들을 여러가지로 확장한 Kotlin 라이브러리들을 사용하고있음 ㅇㅇ )

package ExtensionFunctions.OutOfExtension

import ExtensionFunctions.singleQuote
import atomictest.eq

fun main() {
    "Single".singleQuote() eq "'Single'"
}

유용한 경우

적절한 선에서 쓰는게 좋을 것 같다는 의견

  • 언제 유용할까?
    • 다른 사람이 만든 ‘라이브러리’ 에 대한 제어가 필요 할 때
      • 라이브러리의 버전 업데이트를 기다리지 않고, 사용자 정의 기능 추가가 가능함.
    • 겉보기로, 해당 클래스와 관련된 기능들을 논리적으로 그룹화 하고 싶은 경우 (주의해야 할 것은 - 실제 해당 클래스의 member 로 추가 되는게 아니라는 것 )
    • 코드 재사용 : 해당 클래스에 대해 추가 정의해서 사용하던 기능을 → 아예 확장함수로 등록해 두면, 다른 클래스들에서도 동일한 기능을 재사용 할 수 있다.
    • 확장함수를 호출하는 코드 입장에서 확장함수는 멤버 함수와 같아 보인다
      • IDE 에서 이를 인식하고 목록을 보여준다 (근데 이게 없음 불편하겠다

주의할 점을 추가 해 봄

주의해야 할 점 들도 있다

실제 receiver class 의 member 로 추가되는 건 아니다

  • 실제로 receiver class 의 member 로 추가 되는게 아니다!!!
    • 컴파일된 kotlin bytecode 를 확인해 보면, 아래와 같이 “final static” 함수 + “파라미터로 수신자 타입 을 받는” 것을 볼 수 있다.

( 참고 : Kotlin 은 JVM 에서 어떻게 돌아가는지 간략 )

 

member extension 은 추가하지 말자..

  • 확장함수에 대한 visibility 를 제한하고 싶다면 , 해당 확장함수를 사용하려는 class 를 정의한 파일 내에서 modifier를 ‘private’ 으로 정의해야하지, 해당 class 의 member function 으로 정의해서는 안됨 !
    • (내가 kotlin 을 사용해 본적은 없지만 나라도 이런 욕구가 들 것 같다. 해당 클래스 내부에서만, String 클래스에 대한 확장 함수를 정의하고, 이 클래스로 만든 객체 내부에서만 동작하게 해서, 외부에 대한 혼란을 줄이고 싶지 않을까? )
    • 간혹 class 나 interface 를 정의하면서 확장함수를 member 함수 처럼 정의 하는 경우가 있는데 이러면 안됨!!
      • 보통 이렇게 하는 이유 : 해당 확장 함수에 대한 가시성(visibility) 를 제한하기 위함
        • 하지만 member extension function 은 실제로 visibility 를 restrict 해 주지 않는다.
        • 무의미한 dispatcher receiver 를 생성해서 이 확장 함수를 사용하는게 가능하다.
      class PhoneBookIncorrect {
      
          fun verify(number: String): Boolean = number.incorrectIsPhoneNumber()
      
          // Bad practice, do not do this
          fun String.incorrectIsPhoneNumber(): Boolean =
              this.length == 7 && this.all { it.isDigit() }
      }
      
      fun main() {
          val phoneBookIncorrect = PhoneBookIncorrect()
      		
      		// 이게 가능함
          phoneBookIncorrect.apply { "abc".incorrectIsPhoneNumber() }
      }
      
      
    • 해당 확장함수를 private 하게 사용하려는 file 내에서 private modifier 를 갖는 확장함수로 정의해 줘야 한다!
      • 그러면 이 확장함수에 대한 접근이 실제로 restrict 된다.

 

31 : 이름붙은 인자와 디폴트 인자(default argument)

  • Java 에는 없는 (C++ 에서는 본 적 있는 )
package ch03.NamedAndDefaultArgs.color1

import atomictest.eq

fun color(red: Int, green: Int, blue: Int) =
    "($red, $green, $blue)"
  • named parameter 를 사용해, 호출 시 “인자 순서를 바꿀 수 “ 있다.
    • 하지만, 한 번이라도 “원래 인자 순서(함수 정의 시의)” 와 다르게 사용 한다면, 그 다음에 이어지는 인자들 모두 “이름 붙은 인자” 를 사용 해야 한다. ( Compiler 가 해당 인자들의 위치를 알 수 있도록)

  • 덧 붙은 콤마(trailing comma) : comma 로 인해 생기던 많은 불편함을 사라지게 할 수 있긴 하겠다 . 근데 정말 단순 편의성을 위해서 나온 듯 하다. ( 팀원 분들에 의하면 프론트에서는 이렇게 쓰는게 국룰이고, 백엔드는 이렇지 않다고 )
    • 각 인자가 서로 다른 줄 에 오도록 해야 유용함.
  • 마지막에 콤마를 사용하면, 인자가 추가 되거나, 인자 순서가 바뀌더라도
    • 콤마를 추가하거나 빼지 않아도 손쉽게 가능 ( 경험이 있다면 이해 될 것 ) → trailing comma 를 사용할 수 없었다면, 인자 순서를 blue 가 중간에 오도록 변경하기 위해 아래와 같이 3가지 step 이 필요함.

 

  • 디폴트 인자 와 결합 시 유용
    • 호출 시 일부 값 생략이 가능 → 가독성 상승
    • 호출 시, “이름 있는 인자” 를 사용해 지정하지 않으면, 순서대로 값이 할당된다.

class Color(
    val red: Int = 0,
    val green: Int = 0,
    val blue: Int = 0,
) {
    override fun toString(): String {
            return "($red, $blue, $green)"
        }
}

fun main() {
    Color(10) eq "(10, 0, 0)"
}
  • 디폴트 인자로 ‘객체 인스턴스’ 를 전달 하는 경우 주의

 

  • 디폴트 인자로 ‘객체 인스턴스’ 를 전달 하는 경우 주의
    class DefaultArg
    
    val da = DefaultArg()
    fun useSameInstance(d: DefaultArg = da) = d
    fun useNewInstance(d: DefaultArg = DefaultArg()) = d
    
    useSameInstance() eq useSameInstance()
    useNewInstance() neq useNewInstance()
    
     
  • 인자 이름을 붙였을 때, 가독성이 향상 되는 경우에만! 인자이름을 사용하자
    • 이런식으로 인자가 여러 개 인 경우
    val list = listOf(1,2,3)
    list.joinToString (". ","","!" ) eq "1. 2. 3!" // separator, prefix, postfix
    list.joinToString(separator = ". ", postfix = "!") eq "1. 2. 3!"
    
  • String 의 trimMargin method : STring 의 각 줄 맨 앞의 공백들을, 지정한 접두사 marginPrefix 때 까지 쳐낸다.
    • marginPrefix default value : |(파이프)
    val poem = """
        |-> Last night
            |-> I saw you"""
    
        poem.trimMargin() eq
                """-> Last night
    -> I saw you"""
    

 

32: 오버로딩(Overloading)

  • 시그니쳐
    • 함수 이름
    • 파라미터 목록
    • 리턴 타입
  • 오버로딩 대상
    • 리턴 타입 미포함
    • 같은 이름 + 서로 다른 파라미터 목록(타입, 순서가 달라야함)

오버로딩의 유용함 (오버로딩을 지원하는 언어들)

  • “같은 주제에 대해 , 다르게 동작” 한다 는 개념“함수 이름을 통해 나타내는 것 보다도 더 명확” 하게 표현 가능

 

fun addInt(i: Int, j: Int) = i + j
fun addDouble(i: Double, j: Double) = i + j

fun add(i: Int, j: Int) = i + j
fun add(i: Double, j: Double) = i + j

fun main() {
    addInt(5,6) eq add(5,6)
    addDouble(56.23, 44.77) eq add(56.23, 44.77)
}
  • 함수 이름은, 함수 자체에 대해 설명하는 이름을 사용해 추상화 수준을 높일 수 있다.
    • 파라미터는 무슨 타입이고요~ 이런 얘기까지 함수 이름에서 하지 않게 됨 → 추상화 수준이 높다고 볼수 있음 ( add, addInt → 파라미터 타입 변경되면 함수 이름까지 변경해야 함 )
  • 함수 이름에서 불필요한 중복을 줄여 줄 수 있다.
    • addDouble 이라는 이름의 Double 은 , 파라미터에 있는 정보를 불필요하게 함수 이름에 중복하는 것.
  • 단순한 이름 → 가독성 좋음.

시그니쳐가 같은 확장함수, 멤버 함수 주의

  • 클래스의 멤버함수 vs 이 멤버함수와 시그니쳐가 같은 확장함수
    • → 멤버함수를 우선시 함

  • 확장함수가, 멤버함수와 같은 이름 + “다른 파라미터 목록” 을 제공하며 “멤버함수 오버로딩” 도 가능하다.

디폴트 인자 흉내내는 확장함수 사용 대신, 그냥 디폴트 인자를 갖는 함수를 하나 작성하자

fun f(n: Int) = n + 373
fun f() = f(0)
// 대신
fun f(n: Int = 0) = n + 373

디폴트 인자를 흉내내기 위한 오버로딩 사용

  • 디폴트 인자를 지원하지 않는 언어(ex-Java) 에서는, 이를 흉내내기 위해 오버로딩을 활용하기도 한다.
  • 하지만 Kotlin 은 디폴트 인자를 지원하고 있으니, 아래와 같이 작성하는게 깔끔하다.
class Overloading {
    fun f() = 0
    fun f(n:Int) = n + 2
}

대신, 이런 디폴트 인자를 지원하는 함수 하나만을 사용하는게 깔끔하다

class Overloading {
    fun f(n:Int = -2) = n + 2
}

 

오버로딩 과 디폴트 인자를 함께 사용하는 경우

  • 호출된 함수가 가장 가깝게 일치되는 “함수 시그니쳐” 를 가진 함수를 호출

복사했습니다!