# 11장 그 밖의 코틀린 기능

## 코틀린 버전 알아내기

KotlinVersion 클래스 동반 객체의 CURRENT 속성을 사용해 현재 버전을 알수 있다.

```kotlin
fun main(args: Array<String>) {
	println("The current Kotlin version is $(KotlinVersion.CURRENT}")
}
```

## 반복적으로 람다 실행하기

특정 구문을 임의의 횟수만큼 반복하고 싶을 경우에는 repeat 함수를 사용하면 된다.

repeat 함수는 코틀린 표준 라이브러리에 들어 있다. repeat 함수는 반복할 횟수를 나타내는 정수와 실행할 (Int) → Unit 형식의 함수, 이 두가지를 인자로 받는 inline 함수다.

```kotlin
@kotlin.internal.InlineOnly
public inline fun repeat(times: Int, action: (Int) -> Unit) {
	contract { callsInPlcase(action) }
	
	for (index in 0 until times) {
		action(index)
	}
}
```

```kotlin
fun main(args: Array<String>) {
	repeat(10) {
		println("Counting: $it")
	}
}
```

## 완벽한 when 강제하기

코틀린 컴파일러가 when 문에서 가능한 모든 절을 가지도록 강제 하도록 체크하고 싶다면 제네릭 타입에 exhaustive라는 간단한 확장 속성을 추가하고 when block에 이를 연결하면 된다.

```kotlin
fun printMethod(n: Int) {
	when (n % 3) {
		0 -> println("$n % 3 == 0")
		1 -> println("$n % 3 == 1")
		2 -> println("$n % 3 == 2")
	}
}

fun printMethod(n: Int) : Int {
	return when (n % 3) {
		0 -> 0
		1 -> 1
		2 -> 2
	}
}
```

when 표현식이 값을 리턴하지 않는다면 코틀린은 when 식이 완벽하길 요구하지 않는다. 따라서 위와 같은 경우에도 정상적으로 컴파일 될 것이다.

#### 그러나 모든 케이스를 고려한 when 절을 만들려면 어떻게 해야 할까?

* 등호를 사용하면 된다.

  등호는 할당이 있다는 의미로, 코틀린은 완벽한 조건식을 요구하게 된다.

  ```
  fun printMethod(n: Int) = when ( n % 3) {
  		0 -> println("$n % 3 == 0")
  		1 -> println("$n % 3 == 1")
  		2 -> println("$n % 3 == 2")
  	}
  ```
* exhaustive라는 확장 속성을 사용하면 된다.

  ```
  fun printMethod(n: Int) {
  	when ( n % 3) {
  		0 -> println("$n % 3 == 0")
  		1 -> println("$n % 3 == 1")
  		2 -> println("$n % 3 == 2")
  		else -> println("not found")
  	}.exhaustive
  }
  ```

## 정규표현식과 함께 replace 함수 사용하기

replace 함수는 일치하는 모든 문자열 또는 정규표현식에 일치하는 모든 문자여을 제공한 값으로 대체한다. String에 정의된 replace 함수는 대소문자 구분 여부를 선택 인자로 받고 이 인자의 기본값은 대소문자를 무시한다.

아래 테스트는 replace 함수의 차이를 보여준다.

```kotlin
@Test
fun `test`() {
	assertAll(
		{ assertEquals("one*two*", "one.two.".replace(".", "*")) }, // true
		{ assertEquals("********", "one.two.".replace(".".toRegex(), "*")) } // true
	)
}
```

첫 번째 테스트 케이스는 마침표를 별표로 교체하는 반면, 두 번째 예제는 마침표를 모든 단일 글자를 의미하는 정규표현시그로 취급한다.

> 자바 개발자가 빠지기 쉬운 함정

* replace 함수는 첫 번째 항목이 아니라 발생하는 모든 항목을 교체한다.
* 첫 번째 인자로 문자열은 정규표현식으로 해석하지 않는다.

## 실행 가능한 클래스 만들기

클래스에서 단일 함수를 간단하게 호출하고 싶을 경우 invoke 연산자 함수를 재정의하라

```kotlin
{
	"people": [
		{ "name" : "jung incheol", "craft": "ISS" },
		{ "name" : "park bokum", "craft": "ISS" },
		{ "name" : "song joonggi", "craft": "ISS" }
	],
	"number": 3,
	"message": "success"
}
```

JSON 응답을 보면 3명의 우주 비행사는 현재 국제 우주 정거장에 탑승해 있다.

```kotlin
class AstroRequst {
	companion object {
		private const val ASTRO_URL =
			"<http://api.open-notify.org/astros.json>"
	}

//	fun execute(): AstroResult {
	operator fun invoke(): AstroResult {
		val responseString = URL(ASTRO_URL).readText()
		return Gson().fromJson(responseString,
			AstroResult::class.java)
	}
	
	operator fun invoke(int num): AstroResult {
		val responseString = URL(ASTRO_URL).readText()
		return Gson().fromJson(responseString,
			AstroResult::class.java)
	}
	
	operator fun invoke(String str): AstroResult {
		val responseString = URL(ASTRO_URL).readText()
		return Gson().fromJson(responseString,
			AstroResult::class.java)
	}
}
```

```kotlin
val request = AstroRequest()
val result = request.execute()
println(result.message)
```

이 접근 방식에는 아무 문제가 없지만 AstroRequest 클래스의 목적은 오직 하나이므로 해당 함수의 이름을 invoke로 변경하고 operator 키워드를 추가해 다음과 같이 실행시킬 수 있다.

```kotlin
internal class AstroRequestTest {
	val request = AstroRequest()

	@Test
	internal fun `get people in space`() {
		val result = request(2)
		asssertThat(result.message, `is`("success"))
	}
}
```

이처럼 invoke 연자라 함수를 제공하고 클래스 레퍼런스에 괄호를 추가하면 클래스 인스턴스를 바로 실행할 수 있다. 원한다면 필요한 인자를 추가한 invoke 함수 중복도 추가할 수 있다.

## 경과 시간 측정하기

코드 블록이 실행되는 데 걸린 시간을 알고 싶을 경우 measureTimeMillis 또는 mearueNanoTime 함수를 사용한다.

```kotlin
fun doubleIt(x: Int): Int {
	Thread.sleep(100L)
	println("doubling $x with on thread %{Thread.currentThread().name}")
	return x * 2
}

fun main() {
	println("${Runtime.getRuntime().availableProcessors()} processors")
	var time = measureTimeMillis {
		IntStream.rangeClosed(1, 6)
			.map { doubleIt(it) }
			.sum()
	}
}
```

자바에서는 경과 시간을 구할 때 시작 시간과 종료시간의 차를 별도로 구해주어야 했다.

```kotlin
@Around("execution(* *.*.*(..))")
	public Object callMethodAround(ProceedingJoinPoint joinPoint) throws Throwable {
		long start = System.currentTimeMillis();
		Object retVal = joinPoint.proceed();
		long elapsed = System.currentTimeMillis() - start;

		Signature sig = joinPoint.getSignature();

		logger.info("info : "+sig.getDeclaringTypeName() + "#" + sig.getName() + "\\t" + elapsed + " ms");
		
		return retVal;
	}
```

## 스레드 시작하기

코드 블록을 동시적 스레드에서 실행시키고 싶을 경우에는 kotlin.concurrent 패키지의 thread 함수를 사용해라

thread 확장함수의 시그니처는 다음과 같다.

```kotlin
fun thread(
	start: Boolean = true,
	isDaemon: Boolean = false,
	contextClassLoader: ClassLoader? = null, 
	name: String? = null,
	priority: Int = -1,
	block: () -> Unit
): Thread
```

start 인자의 기본값이 true이므로 다음 예제처럼 다수의 스레드를 쉽게 생성하고 시작할 수 있다.

```kotlin
(0..5).forEach { n ->
	val sleepTime = Random.nextLong(range = 0..1000L)
	thread {
		Thread.sleep(sleepTime)
		println("${Thread.currentThread().name} for $n after ${sleepTime}ms")
	}
}
```

이 코드는 6개의 스레드를 시작하고, 각 스레드는 0부터 1000 사이에서 무작위로 결정된 밀리초 동안 sleep한 다음 해당 스레드의 이름을 출력한다.

thread 함수의 start 파라미터 기본값이 true 이므로 각 스레드마다 start를 호출할 필요가 없다.

## TODO로 완성 강제하기

개발자가 특정 함수나 테스트를 강제로 구현할 수 있도록 TODO 함수를 사용해라

개발자는 종종 어떤 시점에 구현을 완료할 준비가 되지 않은 함수를 완성하기 위해 그들 스스로 메모를 남겨 놓는다. 대부분의 언어에서는 다음 예제처럼 TODO 문을 주석에 추가한다.

```kotlin
fun myCleverFunction() {
	// TODO: 멋진 구현을 찾는 중
}
```

코틀린 표준 라이브러리에는 TODO라는 함수가 있는데 이 함수는 다음과 같이 구현되어 있다.

```kotlin
public inline fun TODO(reason: String): Nothing = 
	throw NotImplementedError("An operation is not implemented: $reason")
```

효율성을 이유로 소스는 인라인되어 있고 함수가 호출될 때 NotImplementedError를 던진다.

```kotlin
fun main() {
	TODO(reason = "none, really")
}
```

## 함수 이름에 특수 문자 사용하기

함수 이름을 읽기 쉽게 작성하고 싶을 경우엔 함수 이름을 백틱으로 감싸 읽기 쉽게 만들 수 있다. 하지만 이 기법은 테스트에서만 사용하자

```kotlin
fun `인철 짱`() {
	println("인철 짱")
}

fun main() {
	`인철 짱`()
}
```

위 처럼 사용하게 되면 함수 이름에 띄어쓰기도 사용할 수 있고 테스트 메소드의 경우에는 어떤 테스트의 메소드를 실행하였는지 더 명확하게 전달되어서 가독성을 높여준다.

## 자바에게 예외 알리기

코틀린 함수가 자바에서 체크 예외로 예외를 던지는 경우에는 @Throws 애노테이션을 추가하면 알릴 수 있다.

코틀린의 모든 예외는 언체크 예외다. 즉 컴파일러는 개발자에게 해당 예외를 처리할것을 요구하지 않는다. 예외를 붙잡기 위해 코틀린 함수에 try/catch/finally 블록을 추가하는 방법은 아주 쉽지만 강제사항은 아니다.

```kotlin
fun housonWeHaveAProblem() {
	throw IOException("File or resource not found")
}

public static void doNothing() {
	housonWeHaveAProblem()
}
```

그래서 자바 사용자는 아래와 같이 문제를 해결하려고 시도할 것이다.

```kotlin
// solution 1 : try-catch로 잡기
public static void useTryCatchBlock() {
	try {
		housonWeHaveAProblem();
	} catch (IOException e) {
		e.printStackTrace();
	}
}

// solution 2 : throws 하여 예외처리 위임하기
public static void useThrowsCluause() throws IOException {
	housonWeHaveAProblem();
}
```

그러나 둘 중 어느 방식도 원하는 대로 동작하지 않는다. 명시적으로 try/catch 블록 추가를 시도하면 자바는 catch 블록 안에 명시된 IOException이 연관된 try 블록 내에서 해당 예외를 절대 던지지 않는다고 생각하기 때문에 코드가 컴파일되지 않는다. 두 번째 throws 절을 추가하는 경우에는 코드가 컴파일되지만 IDE는 '불필요한' 코드가 있다고 경고할 것이다.

두 가지 해결방법을 동작하게 만드는 방법은 다음과 같이 @Throws 애노테이션을 코틀린 코드에 추가하는 것이다.

```kotlin
@Throws(IOException::class)
fun housonWeHaveAProblem() {
	throw IOException("File or resource not found")
} 
```

이제 자바 컴파일러는 IOEception을 대비해야 한다는 것을 안다. @Throws 애노테이션은 그저 자바/코틀린 통합을 위해서 존재한다.
