Kotlin In Action
상위 문서: {{ item.title }} -
kia study 요약
- Kotlin In Action 스터디 정리
Kotlin In Action 스터디 정리
- 요약
* 매주 월요일 점심 시간
* 참석 4명
* 책일 읽어오고 읽어온 내용에 대해서 공유하기
지은이의 말 & 옮긴이의 말
- 자바를 대신하기 위한 요구사항 - C#으로 ㄷ개발하는 닷넷팀 개발자들이 부러웠다. Java 진영에서는 찾을 수 없었다. 그래서 만듦
- 정적 타입의 지정
- Java 코드와 완전히 호환되는 언어가 필요함
- 그 언어르 루이한 도구 개발이 쉬워야 했다.
- 배우기 쉽고 뜻을 파악하기 쉬운 언어가 필요
-
코틀린 언얼르 설계하는 과정에 관여하고 코틀린 언어의 특성이 왜 현재의 모습이 되었는지에 대해 자신있게 설명 할 수 잇는 사람들이 쓴 책이다.
- 코틀린이 제공하는 여러 기능으로 살펴보면 정말 자바를 개선하기 위해 많은 부분에서 고민을 했다는 느낌이 많이 든다.
- 대규모 개발과 유지 보수성, 기존 자바 시스템과의 ㅇ호환성을 등을 고려 해 본다면 가장 강력한 자바 대체재라고 볼 수 있다.
1장. 코틀린이 무엇이며 왜 필요 한가?
- 새로운 예약어:
it
, 람다의 파라미터가 1개 일때 it 으로 축약 가능하다.
data class Person(val name: String, val age: Int? = null)
fun main(args: Array<String>) {
val persons = listOf(Person("영희"), Person("철수", age = 29))
val oldest = persons.maxBy{ it.age ?: 0}
println("oldest: $oldest")
}
7장 연산자 오버로딩과 기타 관례
산술연산자 오버로딩
- 기존 자바 클래스에 대해 확장 함수를 구현하면서 관례에 따라 이름을 붙이면 기존 자바 코드를 바구지 않아도 새로운 기능을 쉽게 부여할 수 있다.k
+
연산자 오버로딩의 예data class Point(val x: Int, val y: Int) { operator fun plus(other: Point): Point { return Point(x + other.x, y + other.y) } }
operator
키워드- 코틀린은 직접 연산자를 만들수 없고, 언어에서 미리 정해둔 연산자만 오버로딩 할 수 있으며, 관레에 따르기 위해 클래스에서 정의해야 하는 이름이 연산자별로 정해져 있다.
- 비트 연산자에도 있다. 310page 참고
| 식 | 함수 이름 | | __ | _______ | | a * b | times | | a / b | div | | a % b | mod | | a + b | plus | | a - b | minus |
- 우선순위는 언제나 표준 숫자 타입에 대한 연산자 우선순위와 같다.
- 주의! 코틀린 연산자가 자동으로 교환 법칙을 지원하지는 않음에 유의하라.
복합대입연산자 오버로딩
- Compound assignment: ex) +=
plusAssign
minusAssign
plus
와plusAssign
을 동시에 정의하지 말라.- += 는 plus 와 plusAssign 양쪽으로 컴파일 될 수 있다.
- 코틀린 표준 라이브러리는 컬렉션에 대해 두가지 접근 방법을 함께 제공한다.
+/-
항상 새로운 컬렉션을 반환+=
-=
연산자는 항상 변경 가능한 컬렉션에 작용해 메모리에 있는 객체 상태를 변화시킨다. mutable collection 에 대해서는 복사본을 생성해서 전달한다.
단항 연산자 오버로딩
- unary operator overloading
식 | 함수 이름 |
__ | _____ |
+a | unaryPlus |
-a | unaryMinus |
!a | not |
++a, a++ | inc |
–a, a– | dec |
비교연산자 오버로딩
- 자바의 equals 나 compareTo 를 사용한 코드보다 더 간결하며 이해하기 쉽다.
- identity equals: ===
- 오버로딩 할 수 없다.
- Any에서 상속박은 equals 가 확장 함수보다 우선순위가 높기 때문에 equals를 확장 함수로 정희 할수는 없다는 사실에 유의하라.
컬렉션과 범위에 대해 쓸 수 있는 관례
- index operator
[]
: keyword -> get / setin
: membership test 라 한다. / keyword ->contains
..
: rangeTo- 범위연산자는 우선순위가 낮아서 범위의 메소드를 호출하려면 범위를 괄호로 둘러싸야 한다.
(0..n).forEach { print(it) }
- 범위연산자는 우선순위가 낮아서 범위의 메소드를 호출하려면 범위를 괄호로 둘러싸야 한다.
for (x in list)
: operator iterator 내부에선 Iterator interface 를 구현
구조 분해 선언과 component 함수
- destructuring decalartion
- 구조 분해를 사용하면 복합적인 값을 분해해서 여러 다른 변수를 한거번에 초기화할 수 있다.
val p = Point(10, 20)
val (x, y) = p
- operator componentN 이라는 함수가 호출됨
- 위 예제에서 x 에는 component1, y 에는 component2
- data class 의 주 생성자에 들어있는 프로퍼티에 대해서는 컴파일러가 자동으로 componentN 함수를 만들어 준다.
위임 프로퍼티
8장 고차 함수: 파라미터와 반환 값으로 람다 사용
고차함수 (lambda)
- 고차함수: 다른 함수를 인자로 받꺼나 함수를 반환하는 함수다
- function type
- example
val sum = { x: Int, y: Int -> x + y }
val action = { println(42) }
- null 이 될 수 있는 함수타입도 정의 가능하다.
- null 이 반환될 수 있는 타입:
var canReturn Null: (Int, Int) -> Int? = {x, y -> null }
- null 이 될 수 있는 타입:
var funOrNull: ((Int, Int) -> Int)? = null
- null 이 반환될 수 있는 타입:
- Java 에서 코틀린 함수 타입 사용
- 컴파일된 코드안에서 함수 타입은 일반 인터페이스로 바뀐다. FuntionN 형태
- Funtion0
, Funtion1<R1, R> 등 invoke method 에 함수의 본문이 들어감
- 함수 파라미터의 디폴트 함수도 설정 가능
inline 함수: 람다의 부가 비용 없애기
- keyword:
inline
,noinline
- inline: 컴파일 시점에 함수를 풀어서 코드내에 녹여지는 것.
- 코틀린이 보통 람다를 무명 크래스로 컴파일하지만 그렇다고 람다 식을 사용할 때마다 새로운 클래스가 만들어지지는 않는다.
-
람다가 변수를 포획하면 람다가 생성되는 시점마다 새로운 무명 클래스 객체가 생긴다. (주의)
- inline 할 수 있는 경우:
- 인라인 함수의 본문에서 람다 식을 바로 호출 하는 경우
- 람다 식을 인자로 전달받아 바로 호출하는 경우
- Kotlin Collection 표준함수인 filter 는 기본적으로 inline 함수 이다. 하지만 asSequence 를 통한 filter 는 람다를 저장해야하기 때문에 inline 함수가 아니다.
- 따라서, 크기가 작은 컬렉션의 경우 asSequence 를 사용함면 성능이 더 느릴 수 가 있다.
함수를 인라인으로 선언해야 하는 경우
-
JVM 은
JIT Compiler
가 이미 강력한 인라이닝을 지원하고 있음. - inline 선언시 이점
-
- 함수 호출 비용을 줄일 수 있을 뿐 아니라 람다를 표현하는 클래스와 람다 인스턴스에 해당하는 객체를 만들 필요도 없어진다.
-
- JVM은 함수 호출과 람다를 인라이닝해 줄 정도로 똑똑하지 못하다.
-
- 사용할 수 없는 몇가지 기능을 사용할 수 있다. (ex. non-local 반환)
-
non-local return
- 람다로부터만 반환되는 게 아니라 그 람다를 호출하는 함수가 실행을 끝내고 반환된다.
- inline 함수인 경우에만 가능
- 무명함수는 기본적으로
local return
- 레이블을 이용하여 return 하고자하는 function 을 지정할 수 있음,
- 간단하게는
return
은 가장 가까은fun
구문을 빠져다온다고 보면 된다.
fun lookForAlice(people: List<Person>) {
people.forEach {
if (it.name == "Alice") {
println("Found!")
reutrn // non-local return
}
}
}
9장. 제네릭스
- 제네릭 타입 파라미터
- 타입 파라미터의 한정
- 클래스 인터페이스의 변성
제네릭 타입 파라미터
- Kotlin 은 처음부터 제네릭을 도입했기 때문에 raw type 을 지원하지 않는다. 제네릭 타입의 타입 인자를 항상 정의 해야 한다.
- start projection
*
을 사용하면 할수는 있다.
- start projection
fun <T> List<T>.slice(indices: IntRange): List<T>
___ 파라미터 선언부
___ 타입 파라미터가 수신 객체와 반환 타입에 쓰인다.
- Type Parameter constraint (타입 파라미터 제약)
- Upper bound (상한): 제네릭 타입을 인스턴스화할 때 사용하는 타입 인자는 반드시 그 상한 타입이거나 그 상한 타입의 하위 타입이어야 한다.
fun <T : Number> List<T>.sum() : T
fun <T: Comparable<T>> max(first: T, second: T): T {
return if (first > second) first else second
}
-
드물지만 둘 이상의 제약을 사용할 수 도 있음 ->
where T : CharSequence, T : Appendable
` -
Null 가능성을 제외한 아무런 제약도 필요 없다면 Any? 대신 Any 를 상한으로 사용하라. Default Upper Bound is Any?
실행시 제네릭스의 동작: 소거된 타입 파라미터와 실체화된 타입 파라미터
- reify (실체화): 함수를 inline 으로 만들면 타입 인자가 지워지지 않게 할 수 있다.
- 타입소거가 가능한게, 타입인자를 알고 올바른 타입의 값만 각 리스트에 넣도록 컴파일 타임에서 보장해 준다.
>>> fun <T> isA(value: Any) = value is T
Error: Cannot check for instance of erased type : T
- 실체화한 타입파라미터를 사용한다면,
inline fun < reified T> isA(value: Any) = value is T // 컴파일 가능
- 따라서 자바에서는 사용이 불가능 하다.
Variance (변성)
- 타입과 클래스의 차이,
- 자바에서는 둘의 차이가 없다.
var x: String
과var x: String?
둘의 차이는 클래스는 같지만 타입은 다르다.- 코틀린은 적어도 둘이상의 타입을 구성할 수 있다는 뜻이다.
- 제네릭에서는 올바른 타입을 얻을려면 제네릭 타입의 타입 파라미터를 구체적인 타입 인자로 바꿔줘야 한다.
- 무수히 많은 타입을 만들을 낼 수 있다.
- sub type & super type
- 어떤 값의 타입이 변수 타입의 하위 타입인 경우에만 값으 ㄹ변수에 대입하게 허용한다.
- invariant (무공변)
- 제니릭타입을 인스턴스화 할때, 타입 인자로 서로 다른 타입이 들어가고 인스턴스 타입 사이의 하위 타입 관계가 성립하지 않을때
- covaiant (공변적)
- A가 B의 하위타입이면 List 는 List 의 하위타입인 클래스나 인터페이스
- 공변성
interface Producer<out T> { // class 가 T에 대해 공변적이라고 선언
fun produce() : T
}
- 타입 안정서을 보장하기 위해 공변적 파라미터는 항상 아웃 위치에만 있어야 한다.
- T 값을 생산할 수는 있지만 소비할 수는 없다
- 그래야 쓰는쪽에서 받아서 쓸떄, 타입이 안전하게 된다.
interface Transformer<T> {
fun transform(t: T): T
__ ___
in 위치 out 위치
}
- 단, 생성자 파라미터는 인아웃 어느쪽도 아니다.
10 장 애노테이션과 리플렉션
애노테이션 선언과 적용
- 애노테이션 인자로는 원시 타입의 값, 문자열, enum, 클래스 참조, 다른 애노테이션 클래스, 그리고 이 요소들
- 애노테이션 인자는 컴파일 타임에 알 수 있어야 한다.
- 프로퍼티를 사용할려면
const
를 붙여 주어야 함.
- 프로퍼티를 사용할려면
const val TEST_TIMEOUT = 100L
@Test(timeout = TEST_TIMEOUT) fun testMethod() { ... }
애노테이션 대상
- 자바에서는 그냥 원하는 위치에 붙이면된다.
- use-site target(사용자 시점 대상)
@get:Rule
- property: 프로퍼티 전체
- field 프로퍼티에 의해 생성되는 필드
- get 프로퍼티 게터
- set 프로퍼티 세터
- receiver 확장 함수나 프로퍼티의 수신 객체 파라미터
- param 생성자 파라미터
- setparam 세터 파라미터
- delegate 위임 프포퍼티의 위임 인스턴스를 탐아둔 필드
- file 파일 안에 선언된 최상위 함수와 프로퍼티를 담아두는 클래스 ->
@file:JvmName("StringFunctions")
애노테이션 선언
annotation class JsonName(val name: String)
- 코틸린 annotation 에서는 anme 이라는 프로퍼티를 사용했지만, 자바 애노테이션에서는 value 라는 매소드를 썻다는 점.
- value 의 경우 이름붙은 인자구문을 안적어줘도 됨
-
메타 애노테이션: 애노테이션 클래스에다 붙일 수 있음
- 코틀린에서는 기본적으로
@Retention
의 경우 RUNTIME 이다. java 는 아님
리플렉션
- Json 직렬화 라이브러리는 어떤 객체든 JSON 으로 변환 할 수 있어야 하고, 실행 시점이 되기 전까지는 라이브러리가 직렬화할 프로퍼티나 클래스에 대한 정보를 알 수 없다. 이런경우에 리플렉션을 사용
class Person(val name: String, val age: Int)
import kotlin.reflect.full.*
val person = Person("Alice", 29)
val kClass = person.javaClass.kotlin
println(kClass.simpleName)