-
3장. 함수 정의와 호출 - 1Kotlin 2022. 2. 22. 22:54
코틀린에서 컬렉션 만들기
val set = hashSetOf(1, 7, 53) val list = arrayListOf(1,7, 53) val map = hashMapOf(1 to "one", 7 to "seven", 53 to "fifty-three") //to : 일반 함수 println(set.javaClass) //class java.util.HashSet println(list.javaClass)//class java.util.ArrayList println(map.javaClass) //class java.util.HashMap
코틀린이 자체 컬렉션 기능을 제공하지 않고 기존 자바 컬렉션을 활용한다.
이유
표준 자바 컬렉션을 활용하면 자바 코드와 상호작용하기가 훨씬 더 쉽게 때문이다.
자바와 코틀린 컬렉션을 서로 상호 작용할 필요가 없다.
val strings = listOf("first", "second", "fourteenth") println(strings.last()) //fourteenth val numbers = setOf(1, 14, 2) println(numbers.maxOrNull()) //14 -- max()함수 사용 x
함수를 호출하게 쉽게 만들기
자바 컬렉션에는 디폴트 toString 구현이 들어있다.
val list = listOf(1,2,3) println(list)// toString() 호출 - [1, 2, 3]
joinToString() 함수의 초기 구현
1. 컬렉션의 원소를 StringBuilder의 뒤에 덧붙인다.
2. 이때 원소 사이에 구분자(serparator)를 추가하고, StrginBuilder의 맨 앞과 맨 뒤에는 접두사(prefix)와 접미사(postfix)를 추가한다.
→ 이 함수는 제네릭(generic) 하다. 즉, 이 함수는 어떤 타이브이 값을 원소로 하는 컬렉션이든 처리할 수 있다.
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,3) >>>println(joinToString(list, "; ", "(", ")")) (1; 2; 3)
이름 붙인 인자
joinToString(list, "; ", "(", ")")
함수 호출 부분의 가독성 해결법
1. 불리언 대신 enum 타입을 사용하라고 권장
2. 파라미터 이름을 주석에 넣으라고 요구
3. 코틀린을 사용하여 전달하는 인자 중 일부(또는 전부)의 이름을 명시
- 호출 시 인자 중 어느 하나라도 이름을 명시하고 나면 혼동을 막기 위해 그 뒤에 오는 모든 인자는 이름을 꼭 명시해야 한다.
- 자바로 작성한 코드를 호출할 때는 이름 붙인 인자를 사용할 수 없다.
//자바 println(joinToString(list, /*separator*/" ", /*prefix*/" ", /*postfix*/".")) //코틀린 println(joinToString(list, separator = " ", prefix =" ", postfix= "."))
파라미터 이름 바꾸기
메뉴 - Refacotr - Rename , Change Signature
디폴트 파라미터 값
자바에서는 일부 클래스에서 오버로딩한 메소드가 너무 많아진다는 문제가 있다. 코틀린에서는 함수 선언에서 파라미터의 디폴트 값을 지정할 수 있으므로 이런 오버로드 중 상당 수를 피할 수 있다.
모든 함수를 호출할 때 모든 인자를 쓸 수도 있고, 일부를 생략할 수도 있다.
fun <T> joinToString2( 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(joinToString2(list, ", ", "", "")) println(joinToString2(list))//separator, prefix, postfix 생략 println(joinToString2(list, ";")) // separtor를 ";"로 지정, prefix와 postfix 생략
함수를 선언할 때와 같은 순서로 인자를 지정해야 한다.
함수의 디폴트 파라미터 값은 함수를 호출하는 쪽이 아니라 함수 선언 쪽에서 지정된다.
따라서 어떤 클래스 안에 정의된 함수의 디폴트 값을 바꾸고 그 클래스가 포함된 파일을 재컴파일하면 그 함수를 호출하는 코드 중에 값을 지정하지 않은 모든 인자는 자동으로 바뀐 디폴트 값을 적용받는다.
디폴트 값과 자바
자바에는 디폴트 파라미터 값이라는 개념이 없어서 코틀린 함수를 자바에서 호출하는 경우에는 그 코틀린 함수가 디폴트 파마리터 값을 제공하더라도 모든 인자를 명시해야 한다.
@JvmOverloads
함수에 추가시 코틀린 컴파일러가 자동으로 맨 마지막 파라미터로부터 파라미터를 하나씩 생략한 오버로딩한 자바 메소드를 추가해준다. 각각의 오버로딩한 함수들은 시그니처에서 생략된 파라미터에 대해 코틀린 함수의 디폴트 파라미터 값을 사용한다.
정적인 유틸리티 클래스 없애기 : 최상위 함수와 프로퍼티
자바
모든 코드를 클래스의 메소드로 작성해야 한다.
package chapter03.strings public class JoinKt { //join.kt 파일에 해당하는 클래스 pulbic static String joinTostring(...) {...} } >>>호출 import chapter03.strings.JoinKt; ... JoinKt.joinToString(list, ", ","","");
코틀린
함수를 직접 소스 파일의 최상위 수준, 모든 다른 클래스의 밖에 위치시키면 된다.
패키지의 멤버 함수이므로 다른 패키지에서 사용시 그 함수가 정의된 패키지를 임포트 해야 한다.
코틀린 컴파일러가 생성하는 클래스의 이름은 최상위 함수가 들어있던 코틀린 소스 파일의 이름과 대응한다. 코틀린 파일의 모든 최상위함수는 이 클래스의 정적인 메소드가 된다.
package chapter03.strings fun <T> joinToString2( ...) : String {...}
파일에 대응하는 클래스의 이름 변경하기
코틀린 최상위 함수가 포함되는 클래스의 이름을 바꾸고 싶다면 파일에 @JvmName 어노테이션을 추가하라. @JvmName 어노테이션은 파일의 맨 앞, 패키지 이름 선언 이전에 위치해야 한다.
@file:JvmName("StringFunctions") //클래스 이름을 지정하는 어노테이션 package chapter03.strings //@file:JvmName 어노테이션 뒤에 패키지 문이 와야 한다. fun <T> joinToString2( ...) : String {...} >>>자바 호출 import chapter03.strings.StringFunctions; StringFunctions.joinToString(list, ", ","","");
최상위 프로퍼티
프로퍼티의 값이 정적 필드에 저장된다. 최상위 프로퍼티를 활용해 코드에 상수를 추가할 수 있다.
기본적으로 최상위 프로퍼티도 다른 모든 프로퍼티처럼 접근자 메소드를 통해 자바 코드에 노출된다. (val의 경우 게터, var의 경우 게터와 세터가 생긴다).
겉으론 상수처럼 보이는데, 실제로는 게터를 사용해야 한다면 자연스럽지 못하다.
자연스럽게 사용하려면 상수를 public static final 필드로 컴파일 해야 한다. const 변경자를 추가하면 프로퍼티를 public static final 필드로 컴파일하게 만들 수 있다. (단, 원시 타입과 String 타입의 프로퍼티만 const로 지정 가능)
var opCount = 0 // 최상위 프로퍼티를 선언한다. fun performOperation() { opCount++ // 최상위 프로퍼티의 값을 변경한다. //... } fun reportOperationCount(){ println("Operation performed $opCount times") // 최상위 프로퍼티의 값을 읽는다. } //코드에 상수 추가 val UNIX_LINE_SEPARATOR = "\n" // const 변경자 추가 const val UNIX_LINE_SEPARATOR2 ="\n" // 위의 바이트 코드는 자바와 동등하다. public static final String UNIX_LINE_SEPARATOR2 = "\n"
메소드를 다른 클래스에 추가 : 확장 함수와 확장 프로퍼티
기존 코드와 코틀린 코드를 자연스럽게 통합하는 것은 코틀린의 핵심 목표 중 하나다.
확장함수
어떤 클래스의 멤버 메소드인 것처럼 호출할 수 있지만 그 클래스의 밖에 선언된 함수다.
1. 수신 객체 타입 : 확장이 정의될 클래스의 타입 (ex. String)
2. 수신 객체 : 확장 함수가 호출되는 대상이 되는 값 (그 클래스에 속한 인스턴스 객체)(ex. this) 수신 객체 멤버에 this 없이 접근 할 수 있다. 확장 함수 안에서는 클래스 내부에서만 사용할 수 있는 비공개 멤버나 보호된 멤버를 사용할 수 없다.
메소드
클래스의 멤버 메소드 + 확장함수
//문자열의 마지막 문자를 돌려주는 메소드 fun String.lastChar() : Char = this.get(this.length -1) >>>println("Kotlin".lastChar()) n fun String.lastChar2() : Char = get(length -1)
임포트와 확장 함수
확장 함수를 사용하기 위해서는 임포트해야만 한다.
as 키워드
임포트한 클래스나 함수를 다른 이름으로 부를 수 있다. 코틀린 문법상 확장 함수는 반드시 짧은 이름을 써야 한다. 임포트할 때 이름을 바꾸는 것이 확장 함수 이름 충돌을 해결할 수 있는 유일한 방법이다.
//import chapter03.strings.lastChar //import chapter03.strings.* import chapter03.strings.lastChar as last //val c = "Kotlin".lastChar() val c = "Kotlin".last()
자바에서 확장 함수 호출
정적 메소드를 호출하면서 첫 번째 인자로 수신 객체를 넘기기만 하면 된다.
확장 함수를 StringUtil.kt 파일에 정의했다면 다음과 같이 호출한다.
/*자바*/ char c = StringUtilKt.lastChar("Java");
확장 함수로 유틸리티 함수 정의
코틀린 라이브러리가 제공하는 함수와 거의 비슷해졌다.
//joinToString()를 확장으로 정의하기 fun <T> Collection<T>.joinToString( //Collection<T>에 대한 확장 함수를 선언한다. separator : String = ", ",//디폴트 값이 지정된 파라미터들 prefix : String = "", //디폴트 값이 지정된 파라미터들 postfix : String = "" //디폴트 값이 지정된 파라미터들 ) : String { val result = StringBuilder(prefix) for((index, element) in this.withIndex()){//"this"는 수신 객체를 가리킨다. 여기서는 T 타입의 원소로 이루어진 컬렉션이다. if(index > 0) result.append(separator) result.append(element); } result.append(postfix) return result.toString() } >>>val list = listOf(1,2,3) >>>println(list.joinToString(separator ="; ",prefix="(", postfix = ")")) (1; 2; 3)
원소로 이루어진 컬렉션에 대한 확장을 만든다. 그리고 모든 인자에 대한 디폴트 값을 지정한다.
이제 joinToString을 마치 클래스의 멤버인 것처럼 호출할 수 있다.
println(list.joinToString(" ")) // 1 2 3
확장 함수는 정적 메소드 호출에 대한 문법적인 편의이다. 클래스가 아닌 더 구체적인 타입을 수신 객체 타입으로 지정할 수 있다. 문자열의 컬렉션에 대해서만 호출할 수 있는 join 함수 정의 가능하지만 객체의 리스트에 대해 호출할 수 없다.
println(listOf("one", "two", "eight").join(" "))//one two eight println(listOf(1, 2, 8).join(" ")) //(x)
확장 함수는 오버라이드할 수 없다.
정적 - 컴파일 시점
동적 - 실행 시점
코틀린의 메소드 오버라이드도 일반적인 객체지향의 메소드 오버라이드와 마찬가지다. 하지만 확장함수는 오버라이드 할 수 없다.
open class View{ open fun click() = println("View clicked") } class Button:View() { override fun click() = println("Button clicked") } >>> val view :View = Button() >>> view.click()// "view"에 저장된 값의 실제 타입에 따라 호출할 메소드가 결정된다. Button clicked
확장함수를 오버라이드 할 수 없다. 코틀린은 호출될 확장 함수를 정적으로 결정하기 때문이다.
확장 함수를 첫 번째 인자가 수신 객체인 정적 자바 메소드로 컴파일한다는 사실을 기억해야 한다.
//확장함수는 오버라이드 할 수 없다. fun View.showOff() = println("I'm a view!") fun Button.showOff() = println("I'm a button!") >>>view.showOff()//I'm a view! //자바 >>Veiw view = new Button(); >>ExtensionKt.showOff(view); // showOff 함수를 extensions.kt 파일에 정의했다. I'm a view!
확장 함수는 클래스의 일부가 아니다. 확장 함수는 클래스 밖에 선언된다. 이름과 파라미터가 완전히 같은 확장 함수를 기반 클래스와 하위 클래스에 대해 정의해도 실제로는 확장 함수를 호출할 때 수신 객체로 지정한 변수의 정적 타입에 의해 어떤 확장 함수가 호출될지 결정되지, 그 변수에 저장된 객체의 동적인 타입에 의해 확장 함수가 결정되지 않는다.
멤버 함수의 우선순위가 더 높다.
어떤 클래스를 확장한함수와 그 클래스의 멤버 함수의 이름과 시그니처가 같다면 확장 함수가 아니라 멤버 함수가 호출된다.
확장 프로퍼티
확장 프로퍼티를 사용하면 기존 클래스 객체에 대한 프로퍼티 형식의 구문을 사용할 수 있는 API를 추가할 수 있다.
확장 프로퍼티는 아무 상태도 가질 수 없지만 더 짧게 코드를 작성할 수 있어 편한 경우가 있다.
확장 프로퍼티 선언하기
뒷받침하는 필드가 없어서 기본 게터 구현을 제공할 수 없으므로 최소한 게터는 꼭 정의해야 한다.
마찬가지로 초기화 코드에서 계산한 값을 담을 장소가 전혀 없으므로 초기화 코드도 쓸 수 없다.
자바에서 확장 프로퍼티를 사용하고 싶다면 게터나 세터를 명시적으로 호출해야 한다.
//확장 프로퍼티 선언하기 val String.lastChar:Char get() = get(length -1) //변경 가능한 확장 프로퍼티 선언하기 var StringBuilder.lastChar : Char get() = get(length -1)//프로퍼티 ㅔ게터 set(value:Char){ this.setCharAt(length -1, value) //프로퍼티 세터 } >>>println("Kotlin".lastChar) n >>>val sb= StringBuilder("Kotlin?") >>>sb.lastChar = '!' >>>println(sb) Kotlin!
'Kotlin' 카테고리의 다른 글
4장. 클래스, 객체, 인터페이스 - (1) 클래스 계층 정의 (0) 2022.02.24 3장. 함수 정의와 호출 - 2 (0) 2022.02.23 2장. 코틀린 기초 - 클래스와 프로퍼티 (0) 2022.02.21 2장. 코틀린 기초 - 함수와 변수 (0) 2022.02.20 1장. 코틀린이란 무엇이며, 왜 필요한가? (0) 2022.02.17