KotlinInAction 46

중위 호출 연쇄: 테스트 프레임워크의 should

중위 호출 연쇄: 테스트 프레임워크의 should 코틀린 테스트 DSL에서 중위 호출을 어떻게 활용하는지 살펴보자 s should startwith("kot") //DSL 단언문 표현 infix fun T.should(matcher: Matcher) = matcher.test(this) //should 함수 구현 s에 들어있는 값이"kot"로 시작하지 않으면 단언문 실패 should 함수 앞에 infix 변경자를 붙여 중위 호출 함수를 정의한다. should 함수는 Matcher 인스턴스를 요구하며, Matcher는 값에 대한 단언문을 표현하는 제네릭 인터페이스다. startwith는 Matcher를 구현하며, 문자열 시작을 검사한다. interface Matcher{ fun test(value:T) }..

invoke 관례를 사용한 더 유연한 블록 중첩

invoke 관례: 함수처럼 호출할 수 있는 객체 invoke 관례는 괄호를 사용한다(operator 변경자가 붙은 invoke 메서드 정의가 들어있는 클래스를 호출할 수 있다) 클래스 안에서 invoke 메서드 정의 class Greeter(val greeting: String) { operator fun invoke(name: String){ //Greeter 안에 invoke 메서드를 정의 println("$greeting, $name") } } fun main() { val bavarianGreeter = Greeter("Servus") bavarianGreeter("Dmitry") //Greeter 인스턴스를 함수처럼 호출한다 } bavarianGreeter("Dmitry")는 내부적으로 bav..

구조화된 API 구축: DSL에서 수신 객체 지정 DSL 사용

수신 객체 지정 람다와 확장 함수 타입 fun main() { val s = buildString { it.append("Hello, ") //it는 StringBuilder 인스턴스 it.append("World") } println(s) } fun buildString( builderAction: (StringBuilder) -> Unit //함수 타입인 파라미터 정의 ): String{ val sb = StringBuilder() builderAction(sb) //람다 인자로 StringBuilder 인스턴스 넘긴다. return sb.toString() } 람다 본문에 매번 it 사용해 StringBuilder 인스턴스를 참조해야 하는 단점이 있다(it. 을 일일이 넣지 않고 싶다) 람다의 인자..

API에서 DSL로

깔끔한 API를 작성할 수 있도록 코틀린에서 도움을 주는 기능 정리 일반 구문 간결한 구문 사용한 언어 특성 StringUtil.capitalize(s) s.capitalize() 확장 함수 1.to("One") 1 to "One" 중위 호출 set.add(2) set += 2 연산자 오버로딩 map.get("key") map["key"] get 메서드에 대한 관례 file.use({f → f.read()}) file.use{ it.read()} 람다를 괄호 밖으로 빼내는 관례 sb.append("yes") sb.append("no") with(sb){ append("yes") append("no") } 수신 객체 지정 람다 코틀린 DSL은 간결한 구문을 제공하는 기능과 그런 구문을 확장해서 여러 메서드..

리플렉션: 실행 시점에 코틀린 객체 내부 관찰 2

JSON 파싱과 객체 역직렬화 data class Author(val name: String) data class Book(val title: String, val author: Author) fun main(args: Array) { val json = """{"title": "Catch-22", "author":{"name":"J.Heller"}}""" val book = deserialize(json) println(book) } 역직렬화할 객체의 타입을 실체화한 타입 파라미터로 deserialize 함수에 넘겨서 새로운 객체 인스턴스를 얻는다. 제이키드의 JSON 역직렬화기는 3단계로 구현돼 있다. 어휘 분석기로 렉서라고 부른다 어휘 분석기는 여러 문자로 이뤄진 입력 문자열을 토큰의 리스트로 변환..

리플렉션: 실행 시점에 코틀린 객체 내부 관찰

리플렉션 : 실행 시점에 객체의 프로퍼티와 메서드에 접근할 수 있게 해주는 방법. 코틀린에서의 리플렉션 1) 자바가 java.lang.reflect 패키지를 통해 제공하는 표준 리플렉션 2) 코틀린이 kotlin.reflect 패키지를 통해 제공하는 코틀린 리플렉션 API = 자바에는 없는 프로퍼티나, 널이 될 수 있는 타입과 같은 코틀린 고유 개념에 대한 리플렉션을 제공한다.(아직까지는 자바 리플렉션 API를 완벽 대체할 수 있지는 않다) KClass, KCallable, KFunction, KProperty(코틀린 리플렉션) java.lang.Class에 해당하는 KClass를 사용하면 클래스 안에 있는 모든 선언을 열거하고, 각 선언에 접근하거나, 클래스의 상위 클래스를 얻는 등의 작업이 가능하다...

애노테이션 선언과 적용

애노테이션 적용 자바와 마찬가지 방법으로 애노테이션 사용이 가능하다. 애노테이션을 적용하려는 대상 앞에 @ + 애노테이션 이름을 붙이면 된다. 함수나, 클래스 등 여러 다른 코드 구성 요소에 붙일 수 있다. class MyTest { @Test fun testTrue(){ //@Test 어노테이션을 사용해 JUnit 프레임워크에게 이 메서드를 테스트로 호출하라고 지시한다. Assert.assertTrue(true) } } @Deprecated("Use removeAt(index) instead", //더 이상 사용하지 않거나 권장되지 않는 메서드에 붙이는 애노테이션 ReplaceWith("removeAt(index)")) //해당 파라미터를 통해 옛 버전을 대신할 수 있는 패턴을 제시가능 fun remo..

변성: 제네릭과 하위 타입

변성 : List, List 와 같이 기저 타입이 같고 타입 인자가 다른 여러 타입이 서로 어떤 관계가 있는지 설명하는 개념이다. 인자를 함수에 넘기기(변성이 있는 이유) List 타입의 파라미터를 받는 함수에 List을 넘기면 안전할까? String 클래스는 Any를 확장하므로 Any 타입 값을 파라미터로 받는 함수에 String값을 넘겨도 절대로 안전하다. 단, Any와 String이 List 인터페이스의 타입 인자로 들어가는 경우 안정하지 않을 수도 있다 fun main(args: Array) { printContents(listOf("abc", "bac")) } fun printContents(list: List){ println(list.joinToString()) } 이 경우 문자열 리스트도 ..

실행 시 제네릭스의 동작: 소거된 타입 파라미터와 실체화된 타입 파라미터

JVM의 제네릭스는 보통 타입 소거를 사용해 구현된다. 이는 실행 시점에 제네릭 클래스의 인스턴스에 타입 인자 정보가 들어있지 않다는 뜻이다. 타입 검사와 캐스트(실행 시점의 제네릭) 코틀린 제네릭 타입 인자 정보는 런타임에 지워진다. 이는 제네릭 클래스 인스턴스가 인스턴스를 생성할 때 쓰인 타입 인자에 대한 정보를 유지하지 않는다는 뜻이다. 컴파일러는 서로 다른 타입 인자를 가지는 리스트를 서로 다른 타입으로 인식하지만, 실제로는 리스트라는 같은 타입의 객체이다. 그럼에도 리스트에는 각자의 타입 인자만을 가지고 있는데, 이는 컴파일러가 타입 인자를 알고 올바른 타입의 값만 리스트에 넣도록 보장해주기 때문이다 타입 인자를 따로 저장하진 않기 때문에 실행 시점에 타입 인자를 검사할 수 없다 어떤 리스트가 ..

제네릭 타입 파라미터

제네릭스를 사용하면 타입 파라미터를 받는 타입을 정의할 수 있다. 제네릭 타입의 인스턴스를 만들려면 타입 파라미터를 구체적인 타입 인자로 치환해야 한다. 코틀린 컴파일러는 보통 타입과 마찬가지로, 타입 인자 추론이 가능하며, 빈 리스트를 만들어야 할 경우, 타입 인자를 타입 인자를 추론할 근거가 없기 때문에 직접 타입 인자를 명시해야 한다. fun main(args: Array) { val authors = listOf("Dmitry", "Svetlana") //타입인자추론 //val readers: MutableList = mutableListOf() //변수 타입을 지정 val readers = mutableListOf()//변수를 만드는 함수의 인자를 지정 } 제네릭 함수와 프로퍼티 제네릭 함수를 ..