[kotlin] 3장 함수 정의와 호출 - 1

김우경·2021년 12월 8일
0

코액션

목록 보기
1/3

3.1 코틀린의 컬렉션

val setExample = hashSetOf(1, 7, 53)
val listExample = arrayListOf(1, 7, 53)
val mapExample = hashMapOf(1 to "one", 7 to "seven", 53 to "fifty-three")

println(setExample.javaClass)
println(listExample.javaClass)
println(mapExample.javaClass)

결과

: 코틀린은 자신만의 컬렉션을 제공하지 않고, 기존 자바 컬렉션을 활용한다.
→ 왜? 자바 코드와의 상호작용을 위해서 굳이 컬렉션을 서로 변환할 필요가 없게 한다.

val strings = listOf("first", "second", "fourteenth")
println(strings.last())

:코틀린은 기존 자바 컬렉션을 활용하지만, 자바보다 더 많은 기능을 쓸 수 있다.

3.2 함수를 호출하게 쉽게 만들기

자바 컬렉션의 toSring()

  • 디폴트 toString()
import java.util.HashSet;

class Main {  
  public static void main(String args[]) { 
    HashSet set = new HashSet();
    set.add("abc");
    set.add("efg");
    set.add("hij");
    System.out.println(set.toString());
  } 
}

>>> [abc, efg, hij]

→ 원하는 형식으로 toString()을 재정의 하려면?
: 구아바, 아파치 커먼즈 같은 라이브러리 혹은 직접 관련 로직 구현해야 한다 → 번거롭다

코틀린에서의 toString()

방법 1) 기본 호출

fun <T> joinToString(
    collection: Collection<T>,
    separator: String,
    prefix: String,
    postfix: String
): String {
    val result = StringBuilder(prefix)
    
    for((index, element) in collection.withIndex()) {
        if (index > 0) result.append(separator)
        result.append(element)
    }
    
    result.append(postfix)
    return result.toString()
}

호출 결과

>>> val list = listOf(1, 2, 4)
>>> println(joinToString(list, ":", "(", ")"))
>>> (1:2:4)

: 의도대로 잘 작동한다. 하지만? 호출 부분이 번거롭다
→ 인자로 전달한 separator, prefix, postfix가 각각 어떤 역할하는지 구분이 어렵고, 매번 함수의 시그니처를 찾아봐야함

방법2) 이름 붙힌 인자

>>> println(joinToString(list, separator = ":", prefix = "(", postfix = ")"))

: 전달하는 인자 중 일부/전부의 이름을 명시할 수 있다.
→ 인자 중 하나라도 이름을 명시하면 그 뒤에 오는 모든 인자는 이름을 꼭 명시해야 한다??

명시하지 않아도 잘 컴파일이 된다 ..?

방법 3) 디폴트 파라미터 값

  • 자바의 문제점 : java.Lang.Thread의 생성자는 8개나 정의되어 있다.
    → 파라미터 이름과 타입이 계속 반복되고, 인자 중 일부가 생략된 오버로드 함수를 호출하면 어떤 함수를 호출할지 모호하다.
  • 코틀린에서의 디폴트 파라미터
    : 함수 선언에서 파라미터의 디폴트 값을 지정할 수 있다.

    fun <T> joinToString(
        collection: Collection<T>,
        separator: String = ",",
        prefix: String = "(",
        postfix: String = ")"
    ): String {
        val result = StringBuilder(prefix)
    
        for((index, element) in collection.withIndex()) {
            if (index > 0) result.append(separator)
            result.append(element)
        }
    
        result.append(postfix)
        return result.toString()
    }
    
    >>> println(joinToString(list)
    >>> println(joinToString(list, ":") // 일부를 생략하면 맨 뒤에 n개가 생략된다.
    • 디폴트 파라미터가 설정된 코틀린 함수를 자바에서 호출하면?
      : 자바에는 디폴트 파라미터 개념이 없어서 디폴트 파라미터가 설정된 코틀린 함수더라도 모든 인자를 명시해야 된다.
      @JvmOverloads : 코틀린 컴파일러가 자동으로 맨 마지막 파라미터로 부터 하나씩 생략한, 오버로딩된 자바 메소드를 추가함
      → joinToString위에 JvmOverloads 어노테이션을 추가하면?
      String joinToString(Collection<T> collection, String seperator, String prefix, String postfix); 
      String joinToString(Collection<T> collection, String seperator, String prefix); 
      String joinToString(Collection<T> collection, String seperator); 
      String joinToString(Collection<T> collection);
      와 같은 네개의 오버로딩한 함수가 만들어진다. 생략된 파라미터는 코틀린 디폴트 파라미터 값을 사용

정적인 유틸리티 클래스 없애기

  • 정적 유틸리티 클래스?
    : 다양한 정적 메소드를 모아두는 역할로 특별한 상태나 인스턴스 메소드가 없는 클래스

  • 코틀린에서의 정적 유틸리티 클래스
    : 코틀린 파일의 모든 최상위 함수는 해당 클래스의 정적 메소드가 된다.

package strings

fun <T> joinToString(
    collection: Collection<T>,
    separator: String = ",",
    prefix: String = "(",
    postfix: String = ")"
): String {
    val result = StringBuilder(prefix)

    for ((index, element) in collection.withIndex()) {
        if (index > 0) result.append(separator)
        result.append(element)
    }

    result.append(postfix)
    return result.toString()
}

→ 이 함수의 실행 과정
: 컴파일러가 join.kt를 컴파일할때 새로운 클래스 정의 → JVM에서 호출할때는

pakage strings;

public class JoinKt {
	public static String joinToString(...) {...}
}

와 같이 컴파일된다.

  • 파일에 대응하는 클래스 이름 변경하기
    : 코틀린 최상위 함수가 포함되는 클래스의 이름 바꿀때는 @JvmNames 어노테이션을 사용한다.
    → 어떤 경우에 클래스 이름이 바뀌어야 하는지 잘 이해가 안간다..
  • 최상위 프로퍼티
var opCount = 0

fun performOperation() {
	opCount++
}

fun reportOperationCount() {
	println("Operation performed $opCount times")
}

fun main() {
    performOperation()
    performOperation()
    performOperation()
    reportOperationCount()
}

>>> Operation performed 3 times

: 최상위 프로퍼티를 활용해서 코드에 상수를 추가할 수 있음

확장 함수와 확장 프로퍼티

확장 함수

  • 기존 자바 API를 재작성 하지 않고도 코틀린 제공 기능 활용하는 방법
  • 추가하려는 함수 이름 앞에 확장할 클래스 이름 덧붙히기
fun String.lastChar(): Char = this.get(this.length - 1)
// fun String.lastChar(): Char = get(length - 1) 
// this 생략 가능

fun main() {
    println("Kotlin".lastChar())
}

>>> n

→ String: 수신 객체 타입 → 확장할 클래스
→ this: 수신 객체 → 확장 함수가 호출되는 대상이 되는 값

  • 왜 사용하는가? 확장할 클래스(예제에서는 String)을 직접 작성한게 아니어도 해당 클래스에 새로운 메소드를 추가하는 것과 같게 동작한다.
  • 캡슐화를 깨지 않는다
    : 수신 객체 타입의 private이나 protected 멤버는 사용할 수 없다!

임포트와 확장 함수

  • 클래스 임포트할 때와 같이 개별 함수 임포트 가능
import strings.lastChar

fun main() {
    val c = "Kotlin".lastChar()
    println(c)
}
  • 코틀린 문법 상 확장 함수는 반드시 짧은 이름을 써야 한다 ??

자바에서 확장 함수 호출하기

: 정적 메소드를 호출하면서 첫번째 인자로 수신 객체 넘기기

char c = StringUtilsKt.lastChar("Java");

확장 함수로 유틸리티 함수 정의하기

fun main() {
    val list1 = listOf(1,2,3)
    println(list1.joinToString(separator = ";", prefix = "[", postfix = "]"))

    val list2 = arrayListOf(1,2,3)
    println(list2.joinToString(separator = ";", prefix = "[", postfix = "]"))
}

→ joinToString을 마치 클래스 멤버인거처럼 호출이 가능

확장 함수는 오버라이드할 수 없다

오버라이딩

open class View {
    open fun click() = println("View clicked")
}

// Button은 View를 확장한다.
class Button: View() {
    override fun click() = println("Button clicked")
}

fun main() {
    val view: View = Button()
    view.click()
}

>>> Button clicked

확장 함수의 오버라이딩

  • 확장 함수는 이렇게 작동하지 x → 클래스 밖에 선언되므로 확장 함수를 호출할 때 수신객체로 지정한 변수의 정적 타입에 의해 어떤 확장 함수가 호출될지 결정된다.
  • 코틀린은 호출될 확장 함수를 정적으로 결정한다
open class View {
    open fun click() = println("View clicked")
}

// Button은 View를 확장한다.
class Button: View() {
    override fun click() = println("Button clicked")
}

fun View.showOff() = println("I'm a view")
fun Button.showOff() = println("I'm a button")

fun main() {
    val view: View = Button()
    view.showOff()
}

>>> I'm a view

→ view가 가리키는 객체의 실제 타입은 Button이지만, view의 type은 View이므로 View.showOff()가 호출된다.

💡 확장 함수와 멤버 함수의 이름이 같으면? 멤버 함수의 우선 순위가 더 높아서 멤버 함수가 호출된다.

fun FunctionUtil.hello() {
    println("hello-extend")
}

class FunctionUtil {
    constructor()

    fun hello() {
        println("hello-class")
    }
}

fun main() {
    val func: FunctionUtil = FunctionUtil()
    func.hello()
}

>>> hello-class

확장 프로퍼티

  • 프로퍼티 형식 구문으로 사용 가능
  • 이름은 프로퍼티지만 상태 저장할 방법은 x
var StringBuilder.lastChar: Char
	get() = get(length - 1)
	set(value: Char) {
        this.setCharAt(length - 1, value)
    }
        

fun main() {
    val sb = StringBuilder("Kotlin?")
    sb.lastChar = '!'
    println(sb)
}
>>> Kotlin!
  • 일반적인 프로퍼티와 같은데 수신 객체 클래스만 추가됨
  • 자바에서 사용하고 싶다면? 게터&세터를 명시적으로 호출해야됨
profile
Hongik CE

0개의 댓글