오늘은 코틀린 인 액션 3장을 공부했다.
Java에서는 일부 클래스에서 오버로딩한 메서드가 너무 많다는 문제가 있다.
하지만 코틀린에서느 함수 선언에서 파라미터의 디폴트값을 지정할 수 있어 오버로딩을 상당수 줄일 수 있다.
fun <T> joinToString(
collection: Collection<T>,
separator: String = ", ", // 디폴트값 지정.
prefix: String = "",
postfix: String = ""
): String
/*
joinToString(list, ", ", "", "")
joinToString(list)
joinToString(list, "; ")
맨 위처럼 쓸것을 아래처럼 써도 빈 값들은 기본값들이 채워주게 된다.
*/
자바에서는 모든 필드와 메서드가 클래스 안에 작성되어야 하지만, 코틀린에서는 클래스 밖에 함수와 필드를 쓸 수 있다.
package strings
fun joinToString(...): String{...}
즉, 코틀린 파일의 모든 최상위 함수는 코틀린 소스 파일의 이름을 한 클래스 안에 static 메서드가 된다.
package strings;
public class JoinKt {
public static String joinToString(...) {...}
}
함수 뿐만 아니라 프로퍼티(자바에서 필드)도 같은 방법으로 최상위 프로퍼티로 클래스 밖에 정의가 가능하다. 해당 프로퍼티는 코틀린 소스파일 이름의 클래스 안에 정적 필드로 저장된다.
최상위에 선언한 변수나 클래스의 멤버 변수는 선언과 동시에 초기화를 해야한다.
함수 내부의 변수는 선언만 해도 된다.
확장함수는 어떤 클래스의 멤버 메서드인 것처럼 호출할 수 있지만 그 클래스 밖에 선언된 함수이다.
새로운 함수를 마치 해당 클래스의 멤버인 것처럼 사용할 수 있다.
기존 자바 api를 재작성하지 않고도 코틀린의 기능을 이용할 수 있게 해주며, 기존 자바 프로젝트와의 호환에 기여한다.
fun receiverType.functionName(parameters): ReturnType {
// 함수 본문
}
// 추가하려는 함수 이름 앞에 그 함수가 확장할 클래스의 이름을 덧붙이기만 하면 된다.
fun String.printLength() {
println("Length of the string : ${this.length}")
}
fun main(){
"Kotlin".printLength()
// 출력 : Length of the string : 6
}
수신 객체 타입(receiver type) : 확장할 클래스의 이름.
수신 객체(receiver object) : 확장 함수가 호출되는 대상이 되는 값(객체).
위의 예시에서 this와 "Kotlin"
메서드 호출은 다른 일반 클래스 멤버 호출방식과 같다.
확장함수 안에서는 클래스 내부에서만 사용할 수 있는 private, protected 멤버를 사용할 수 없다. 캡슐화를 깨지는 못한다.
확장함수를 정의했다해도 자동으로 프로젝트 안의 모든 소스코드에서 사용할 수는 없다.
당연히 다른 클래스와 함수처럼 import를 해야 사용할 수 있다.
import strings.lastChar as last
처럼 as
를 사용해 import한 클래스나 함수를 다른 이름으로 부를 수 있다. 이름 충돌을 막기 위함이다.
코틀린 문법상 확장함수는 짧은 이름을 쓰는걸 권장한다.
char c = StringUtilKt.lastChar("Java");
// StringUtil.Kt 파일에 확장함수를 정의했을 경우. "Java"가 수신 객체.
Java에서 위의 확장함수를 사용하기 위해선 최상위 함수처럼 확장함수가 들어있는 파일 이름을 사용하면 된다. 그리고 첫번째 파라미터로 사용하려는 수신 객체를 넘기면 된다.
확장함수는 오버라이드할 수 없다. 클래스의 일부가 아니라 클래스 밖에 선언되기 때문이다.
틀린에서 기존 클래스에 새로운 프로퍼티를 추가하기 위한 메커니즘
해당 클래스의 인스턴스에 마치 기존에 정의된 것처럼 새로운 프로퍼티를 사용할 수 있다.
val String.customLength: Int
get() = length + 10
실제로는 클래스에 새로운 멤버를 추가하는 것이 아니
라, 해당 클래스의 인스턴스에 대해 정적인 함수로 호출된다. 확장 프로퍼티에서는 backing field(프로퍼티의 실제 값을 저장하는 데 사용되는 내부 변수)를 직접 정의할 수 없다.코틀린은 자체적인 컬렉션 라이브러리는 제공하지 않는다.
대신 자바의 표준 컬렉션을 활용하는데, 자바와 상호작용하기 쉽게 하기 위해서이다.
구현체Of(요소)로 간단하게 만들 수 있다. map은 key to value로 만들 수 있다.
val set = hashSetOf(1,7,42)
val list = arrayListOf(1,7,42)
val map = hashMapOf(1 to "one", 7 to "seven", 42 to "fourty-two")
println(list.max()) // 42, 컬렉션의 최대값을 반환해주는 메서드
println(list.last()) // 7. index 가장 마지막 원소를 반환하는 메서드
println(list) // [1, 42, 7]. toString()을 명시하지 않아도 호출해준다.
함수에 임의 개수의 인자를 전달할 수 있게 해준다.
함수를 선언할 때 정확한 매개변수의 개수를 지정하지 않고 여러 개의 인자를 전달할 수 있다.
함수를 호출할 때 원하는 개수 만큼 값을 인자로 넘기면 컴파일러가 그 값을 넣어준다.
fun printNumbers(vararg numbers: Int) {
for (number in numbers) {
print(number)
}
}
fun main() {
printNumbers(1, 2, 3, 4, 5)
}
위의 함수에서 파라미터 앞에 vararg 변경자를 붙여 가변 인자 함수를 작성한다.
스프레드 연산자 *
코틀린에서 배열이나 리스트 등의 컬렉션을 풀어서 각각의 요소로 전개하거나, 가변 인자 함수에 값을 전달할 때 사용되는 연산자
val array = arrayOf(1, 2, 3)
val expandedArray = arrayOf(*array, 4, 5)
// *array는 1,2,3을 풀어서 전개한 것
printNumbers(*array) // array의 요소를 풀어서 가변인자함수에 전달.
중위 호출을 사용하면 함수를 일반적인 점 표기법(infix notation)으로 호출할 수 있다.
to
infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)
// 일반적 사용
val pair1 = 1.to("one")
// 중위 호출 사용. to를 연산자처럼 사용 가능.
val pair2 = 2 to "two"
다른 함수 내부에서 정의되고 사용되는 함수. 함수 안에 또 다른 함수를 정의한 것
fun calculateAverage(numbers: List<Double>): Double {
// 로컬 함수 정의
fun sum(): Double {
var total = 0.0
for (number in numbers) {
total += number
}
return total
}
fun count() = numbers.size
// 로컬 함수 호출하여 평균 계산
return sum() / count()
}
fun main() {
val numberList = listOf(1.0, 2.0, 3.0, 4.0, 5.0)
val average = calculateAverage(numberList)
println("Average: $average")
}
위의 sum()과 count()는 calculateAverage() 안에서만 사용할 수 있다.
외부에서는 사용할 수 없으므로 main에서는 호출할 수 없다.
로컬함수는 소속된 바깥 함수의 파라미터에 직접 접근할 수 있다.
너무 중첩이 많아지면 가독성이 안좋아지므로 일반적으로는 한단계 이하의 로컬함수가 권장된다.