6. 실행 가능한 클래스 만들기
클래스에서 단일 함수를 간단하게 호출하고 싶을 경우 invoke 연산자 함수를 재정의하라
{
"people": [
{ "name" : "jung incheol", "craft": "ISS" },
{ "name" : "park bokum", "craft": "ISS" },
{ "name" : "song joonggi", "craft": "ISS" }
],
"number": 3,
"message": "success"
}
JSON 응답을 보면 3명의 우주 비행사는 현재 국제 우주 정거장에 탑승해 있다.
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)
}
}
val request = AstroRequest()
val result = request.execute()
println(result.message)
이 접근 방식에는 아무 문제가 없지만 AstroRequest 클래스의 목적은 오직 하나이므로 해당 함수의 이름을 invoke로 변경하고 operator 키워드를 추가해 다음과 같이 실행시킬 수 있다.
internal class AstroRequestTest {
val request = AstroRequest()
@Test
internal fun `get people in space`() {
val result = request(2)
asssertThat(result.message, `is`("success"))
}
}
이처럼 invoke 연자라 함수를 제공하고 클래스 레퍼런스에 괄호를 추가하면 클래스 인스턴스를 바로 실행할 수 있다. 원한다면 필요한 인자를 추가한 invoke 함수 중복도 추가할 수 있다.
7. 경과 시간 측정하기
코드 블록이 실행되는 데 걸린 시간을 알고 싶을 경우 measureTimeMillis 또는 mearueNanoTime 함수를 사용한다.
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()
}
}
자바에서는 경과 시간을 구할 때 시작 시간과 종료시간의 차를 별도로 구해주어야 했다.
@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;
}
8. 스레드 시작하기
코드 블록을 동시적 스레드에서 실행시키고 싶을 경우에는 kotlin.concurrent 패키지의 thread 함수를 사용해라
thread 확장함수의 시그니처는 다음과 같다.
fun thread(
start: Boolean = true,
isDaemon: Boolean = false,
contextClassLoader: ClassLoader? = null,
name: String? = null,
priority: Int = -1,
block: () -> Unit
): Thread
start 인자의 기본값이 true이므로 다음 예제처럼 다수의 스레드를 쉽게 생성하고 시작할 수 있다.
(0..5).forEach { n ->
val sleepTime = Random.nextLong(range = 0..1000L)
thread { // thread(isDamon = true) : 스레드 실행이 끝나기 전에 프로그램이 종료됨
Thread.sleep(sleepTime)
println("${Thread.currentThread().name} for $n after ${sleepTime}ms")
}
}
이 코드는 6개의 스레드를 시작하고, 각 스레드는 0부터 1000 사이에서 무작위로 결정된 밀리초 동안 sleep한 다음 해당 스레드의 이름을 출력한다.
thread 함수의 start 파라미터 기본값이 true 이므로 각 스레드마다 start를 호출할 필요가 없다.
또한 thread 확장 함수의 isDaemon 파라미터를 통해 데몬 스레드를 생성할 수 있다. 애플리케이션에서 모든 잔여 스레드가
데몬 스레드라면 애플리케이션은 종료된다. (데몬 스레드 : 주 스레드의 작업을 돕는 보조역할을 수행하는 스레드)
9. TODO로 완성 강제하기
개발자가 특정 함수나 테스트를 강제로 구현할 수 있도록 TODO 함수를 사용해라
개발자는 종종 어떤 시점에 구현을 완료할 준비가 되지 않은 함수를 완성하기 위해 그들 스스로 메모를 남겨 놓는다. 대부분의 언어에서는 다음 예제처럼 TODO 문을 주석에 추가한다.
fun myCleverFunction() {
// TODO: 멋진 구현을 찾는 중
}
코틀린 표준 라이브러리에는 TODO라는 함수가 있는데 이 함수는 다음과 같이 구현되어 있다.
public inline fun TODO(reason: String): Nothing =
throw NotImplementedError("An operation is not implemented: $reason")
효율성을 이유로 소스는 인라인되어 있고 함수가 호출될 때 NotImplementedError를 던진다.
fun main() {
TODO(reason = "none, really")
}
10. Random의 무작위 동작 이해하기
난수를 생성하고 싶다면 Random 클래스에 있는 함수 중 하나를 사용하자.
- Random 클래스의 함수 예시
open fun nextInt(): Int // Int.MIN_VALUE ~ Int.MAX_VALUE open fun nextInt(until: Int): Int // 0~until open fun nextInt(from: Int, until: Int): Int // from ~ until ...
- 반복 가능한 난수 생성기
@Test
fun `Random function produces a seeded generator`() {
val r1 = Random(12345)
val nums1 = (1..10).map {r1.nextInt()}
val r2 = Random(12345)
val nums2 = (1..10).map {r2.nextInt()}
assertEquals(nums1, nums2)
}
// 시드 값이 같은 경우 동일한 순서의 난수 값을 제공
11. 함수 이름에 특수 문자 사용하기
함수 이름을 읽기 쉽게 작성하고 싶을 경우엔 함수 이름을 백틱으로 감싸 읽기 쉽게 만들 수 있다. 하지만 이 기법은 테스트에서만 사용하자
fun `진영 화이팅`() {
println("진영 화이팅")
}
fun main() {
`진영 화이팅`()
}
위 처럼 사용하게 되면 함수 이름에 띄어쓰기도 사용할 수 있고 테스트 메소드의 경우에는 어떤 테스트의 메소드를 실행하였는지 더 명확하게 전달되어서 가독성을 높여준다.
참고: 카멜케이스
12. 자바에게 예외 알리기
코틀린 함수가 자바에서 체크 예외로 예외를 던지는 경우에는 @Throws 애노테이션을 추가하면 알릴 수 있다.
코틀린의 모든 예외는 언체크 예외다. 즉 컴파일러는 개발자에게 해당 예외를 처리할것을 요구하지 않는다. 예외를 붙잡기 위해 코틀린 함수에 try/catch/finally 블록을 추가하는 방법은 아주 쉽지만 강제사항은 아니다.
fun housonWeHaveAProblem() {
throw IOException("File or resource not found")
}
public static void doNothing() {
housonWeHaveAProblem()
}
그래서 자바 사용자는 아래와 같이 문제를 해결하려고 시도할 것이다.
// 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 애노테이션을 코틀린 코드에 추가하는 것이다.
@Throws(IOException::class)
fun housonWeHaveAProblem() {
throw IOException("File or resource not found")
}
이제 자바 컴파일러는 IOEception을 대비해야 한다는 것을 안다. @Throws 애노테이션은 그저 자바/코틀린 통합을 위해서 존재한다.
댓글남기기