Kotlin: what and why
Kotlin’s primary traits
- Target: 서버, 안드로이드, JVM
- 타임 추론을 바탕으로 한 정적 타입
- Functional, OOP 둘다 지원
- 자바 코드 쉽게 활용 가능
코틀린 설계 철학
- Pragmatic 실용적인
- 프로덕션 용이고, 다른 언어들에서 가져온 익숙한 기능들을 도입
- 특정 프로그래밍 패러다임을 강요하지 않음
- IDE 제공
- Concise 간략한
- Boilerplate 제거, 풍부한 라이브러리 제공
- 람다 제공, 특수문자 연산자 오버로딩 미제공
- Safe 안전한
- JVM 위이므로, 타입사용, 동적 메모리 관리 면에서 안전함
- null을 명시적으로 표현, 컴파일 시에 null에러 조기 차단
- 런타임 타입 체크가 더 값싸고, 쉽게 가능
- Interoperable 상호 운용 가능한
- Java-to-Kotlin 정적 변환, 자동 리펙토링 가능
- Java의 라이브러리 활용 그대로 가능
- Collections 라이브러리는 자바에 완전 의존
코틀린 툴 사용
kotlinc
로 컴파일,kt
파일을class
로 변환- 컴파일 시, 의존성 지닌 jar을 포함시함
- 실제 프로젝트에서는 Maven, Gradle, Ant 등의 빌드 툴을 사용함
Kotlin Basics
기초 요소들: 함수
1 |
|
기초 요소들: val, var
1 |
|
string template
1 |
|
Classes and properties
- Java에서는 private 변수에 getter와 setter를 선언해 사용
- Kotlin에서는 property에 기본적으로 getter setter가 선언됨
- primary 생성자 인자에
val
,var
붙이면 자동으로 프로퍼티 생성됨 - 필요하면 getter, setter 바꿔서 선언할 수 있음
private set
으로 선언 가능
- primary 생성자 인자에
- backing field: 프로퍼티뒤에 숨겨진 필드
- Kotlin에서는 필드에 직접 접근이 불가능함(프로퍼티는 필드가 아님)
- setter에서 값을 대입할 때,
field
를 통해서 backing field에 대입
1 |
|
디렉토리, 패키지 관리
- 클래스 별로 파일을 나누는 java
- Kotlin에서는 꼭 그럴필요는 없음
- 패키지를 하나의 파일로 해도 되고
- 패키지를 폴더로 만들어도 무방하다.
when과 enum
enum class
: Java enum의 확장enum class
는 프로퍼티를 가질 수 있음enum class
의 첫 문장으로 enum constant선언 가능- constant 선언 후에 세미콜론 찍어야함(Kotlin 내 유일한 세미콜론 사용)
- 세미 콜론 이후에 멤버 함수를 선언 가능
when
: switch의 확장- 조건과 결과가
->
로 대응되어서 나열됨 - 각 조건 끝에 세미콜론이 생략된 것이라 생각하는게 편함
- When에 argument를 준 경우 다음과 같은 형태가 가능
값 -> 값 or {람다}
값 -> 값 or {람다}
값,값 -> 값 or {람다}
ArbitaryObject -> 값 or {람다}
is Type -> 값 or {람다}
else -> 값 or {람다}
- When에 argument를 주지 않은 경우
Boolean -> 값 or {람다}
- 조건과 결과가
Smart cast
- 타입 캐스팅은
as
, 타입체크는is
- explicit casting:
e as Num
- smart casting:
if (e is Sum) { return e.right }
- 타입을 체크한 이후에는 캐스팅이 필요없다.
- 값이 변하는 경우는 스마트 캐스팅을 적용할 수 없다.
when is Type
구문에서도 스마트 캐스팅이 적용됨
반복문, in 연산자
- while은 Java랑 같음
- for는 range based만 있음
in
연산자를 사용1..5
: 1, 2, 3, 4, 51 until 5
: 1, 2, 3, 41 until 5 step 2
: 1, 35 downTo 1
: 5, 4, 3, 2, 15 downTo 1 step 2
: 5, 3, 1
- map을 반복할 경우
for ((k, v) in treeMap)
in
연산자- 반복문 외에 포함을 체크할 때 사용 가능,
- 문자:
c in 'a'..'z'
- 문자열:
"Kotlin" in "Java".."Scala"
예외처리
- Java와 달리 new 필요없음, 호출할 예외 명시 필요 없음
- throw는 expression의 일부가 될 수 있음
- try를 expression으로 쓸 수 있음, 람다 형태
- 중괄호는 생략 불가
- try, catch, finally 사용 가능
- try-with-resource 구문은 없음
1 |
|
Defining and calling functions
함수 호출을 더 쉽게 만드는 기능들
- Named arguments
- 단 Java7 이전 코드들에서는 지원 안됨
- Default parameters
- overloading 수를 줄여줌
- 기본 인자는 Callee에 포함됨. 빌드시에 유의할 것
@JvmOverloaded
- Java에서 호출하기 위해, 모든 가능한 Signature를 오버로딩 해줌
- Default param 지정시 개수만큼 오버로딩된 함수가 생김
- 최상위 함수
- 함수를 최상위(package 아래) 레벨에 class 없이 선언할 수 있음
- 컴파일시 파일 이름 클래스가 자동생성(
join.kt
=class JoinKt
) - Java에서 import시 Filename class를 써줘야함
- 최상위 property
- Java에서 getter/setter로 접근 가능
const
로 선언시, Java에서static final
취급, 직접 접근 가능
Extension functions
- 기존 라이브러리에 함수 및 프로퍼티 추가 가능
- Java나 다른 JVM 코드(java class로 컴파일 가능한)도 확장 가능
- 확장 대상을 Reciever type이라고 함
- 명시적 import가 필요
- extension 자체를 명시적으로 import 해야함
- Reciever를 import와 무관
- (참고)
as
로 import된 대상의 이름 변경 가능
- 가시성이 제한됨
- private, protected 멤버는 접근 불가
- 다른 extension function은 호출 가능
- Java에서는 Reciever를 첫 인자로 받는 정적함수로 취급됨
- Java에서의 Util함수를 extension으로 사용하면 유용함
- Util함수는 상태를 저장하지 않는 함수들
- 오버로딩이 불가능함
- 실제로는 정적함수에 불과함
- 즉 런타임 타입에 무관하게, 컴파일 타임에 호출될 함수가 결정됨
- 멤버 함수와 충돌시, 무조건 멤버함수가 우선시됨
1 |
|
Extension properties
- 상태를 가지지 않는 조건 하에서 확장 가능
- Java 코드 상으로는 정적 getter/setter가 추가되는 것
- getter가 무조건 있어야함 (backing field가 없으므로)
val
로 선언하면, getter만 있으면 됨var
로 선언하면, getter/setter 둘다 있어야함
1 |
|
Java Collections 확장과 그 원리들
- Java에 비해 Collections의 함수가 확장되어 있음
- Factory:
hashSetOf, arrayListOf, hashMapOf
- Utility:
last(), max()
- Factory:
- 가변 인자
vararg
- Java의
int...
대신 타입 앞에vararg
붙임 *args
: 배열을 가변인자로 이용 가능- Collection의 생성자들에 사용
- Java의
- map 선언시의
to
1.to("one")
==1 to "one"
- infix로 선언된 함수는 위 표기 가능
- destructuring declaration
- C++에서의 unstructured binding
component1()
등의 형태로 선언된 메소드가 있으면 사용 가능
1 |
|
정규식 및 문자열 처리
- 정규식 타입 따로 존재
- Java는 정규식 타입이 따로 없어서 매우 혼란스러움
- Kotlin은 정규식 타입이 따로 존재,
String.toRegex
로 생성
- 정규식 타입의 기능들
- match, find등 일반적 컨셉 제공
destructured
를 통해서 분리 선언 가능
"""
: 여러줄 문자열trimMargin(".")
: 들여쓰기 처리- escape 문자는 지원 안함
- string template은 가능
1 |
|
로컬 함수 및 클로저
- 함수 내에서 함수를 선언하는것이 허용됨
- 클로저 역시 지원함, 람다/로컬함수 내에서 변수를 수정하는게 가능
Classes, objects, and interfaces
Interface
- Java8과 유사
- 함수들은
open
이 기본, 구현체에서overide
필요 - 인터페이스 default 함수 사용 가능
default
키워드 필요 없음- 다중 상속 충돌 시 컴파일 에러 - 명시적 선언 필요
- 프로퍼티도 선언 가능
- 구현시 override 붙여서 구현 해주어야함
- 프로퍼티의 getter/setter도 default 함수처럼 구현부를 가질 수 있음
1 |
|
Class
- 제어자:
final
이 기본,open
은 명시적으로 부여- 자바의 경우는
open
이 기본 open class
: 상속 가능abstract class
: 인스턴스화 불가능open fun
: override 가능override fun
: overriding 함수, open으로 간주final override fun
: overriding 함수, 명시적 final 부여abstract fun
: subclass에서 구현이 강제됨, open으로 간주
- 자바의 경우는
- 접근 제어자:
public
기본- 자바의 경우는 package-priaate가 기본
- Kotlin은 package 대신 module을 접근 제어의 단위로 사용
- class 접근 제어
public class
: 전체 공개internal class
: 모듈 내 공개private class
: 파일 내 공개
- 멤버 함수 접근 제어
public member
: 전체 공개internal member
: 모듈 내 공개protected member
: 서브클래스에 공개, 자바와 달리 package내 공개 아님private member
: 클래스 내 공개
inner, nested, sealed class
- inner vs nested class
- Kotlin은 Serialize문제를 피하기 위해 nested class를 기본값으로 씀
- inner 클래스가 가진 Outer 인스턴스 참조가 Serialize에 문제를 일으킴
- sealed class
- enum 대신, 클래스 계층 구조를 when(switch) 구문의 인자로 쓰게 해줌
- nested class 대상이 제한(sealed)됨
- 컴파일러가
else
의 빠짐을 체크하는 등 실수를 줄여줌
1 |
|
class 초기화
- Primary 생성자와
init
blockconstructor
키워드로 primary 생성자 선언 가능, 키워드 생략 가능init
block은 primary 생성자를 위한 실행 block임- primary 생성자 인자에,
val
,var
을 부여해 프로퍼티 생성 가능
constructor
의 존재 의의 =private constructor
- companion 오브젝트(정적 멤버) 내의 유틸리티 함수에서 생성 가능
- Factory 혹은 Singleton 패턴을 강제하고 싶을때 사용
1 |
|
- secondary constructor
- 여러 개의 생성자가 필요할 때 사용
- primary가 없으면
- this를 통한 명시적 호출 필요 없음
- 상속 받았을 경우 secondary에서 super 호출해야함
- primary와 함께 쓰면
- 상속 받았을 경우 primary에서는 반드시 부모 생성자 호출해야함
- secondary는 this를 명시적 호출 필요
1 |
|
data class
- Kotlin 클래스는 아래 3가지를 Any로부터 상속받아 가지고 있음
toString
,equals
,hashCode
- 필드들을 고려해서 class에 4가지 기능을 추가 혹은 재정의 해줌
toString
,equals
,hashCode
,copy
- hash 및 동일성 계산에는 Primary 생성자의 프로퍼티만 고려
- immutable:
val
property만 사용하는게 바람직함- read-only이어야만 HashMap의 키로 사용할 수 있음
- FP의 side-effect 제거에도 유용
- 변경이 필요할 경우
copy
를 사용하여 해결 가능
Class delecation: “by” keyword
- decorator pattern
- D가 A 인스턴스를 멤버로 들고, 해당 객체의 기능을 확장시키는 패턴
- A 인스턴스의 멤버 함수는, D의 멤버함수로 모두 등록됨
- 위의 패턴을 boilerplate 없이
by
키워드로 해결해줌 - why delegation(composition)
- 객체 지향에서 상속이 가지는 유연성 부족의 문제를 해결하기 위해
- 합성 혹은 위임을 사용
- 합성 혹은 위임은, 캡슐화를 유지한채로 인터페이스만 사용함
- 상속(is-a) 관계를 합성(has-a) 관계로
class DelegatingCollection<T>(
innerList: Collection<T> = ArrayList<T>()
) : Collection<T> by innerList {}
“object” 키워드
- singleton:
object
class
대신object
로 만들면 됨- 생성자 선언 불가, 정의하는 즉시 인스턴스 생김
- thread-safe creation
- call은 thread-safe하지 않으므로, 대비책을 구현해줘야함
companion object
- 클래스의 정적 멤버들을 담는 오브젝트, 싱글톤과 속성 비슷
- 클래스당 한 개만 선언 가능
- 클래스의 private 멤버에도 접근 가능
- private 생성자와 함께 Factory를 강제하는데 쓰일 수 있음
- 상속되지 않음
- 이름을 가질 수 있음
- 인터페이스를 구현할 수도 있음
- 확장도 가능:
fun ClassName.Companion.func() = ...
- 익명 내부 객체:
object {}
- 자바의 익명 클래스와 비슷한 기능
- 함수 내에서 클래스를 정의하게 됨
- Java와 다르게 익명 객체가 여러개의 Interface를 구현하는건 안됨
- Closure 사용 가능함
- 또 Java와 다르게 Closure로 참조하는 변수들의 값을 고칠 수 있음
Programming with lambdas
- why?
- verbosity를 낮추기 위한 방안
- 익명 클래스를 대체
- curly brace를 통해서 람다 표현
- 5단계로 단계적 축약 가능 (코드 참조)
- 람다는 변수에 대입할 수 있음
- member reference는 Java8과 유사
- top-level 함수도 참조 가능
- 인스턴스에 대해서도 참조 가능
- mutable 변수들에 closure로서 접근 가능
- 익명 object와 동일
- Java에서는 final로 취급되어야했음
1 |
|
Functional APIs for collections
- Collection 자체에서 제공하는 API들
filter
: 해당 Collection에 속한것만 남김map
: Collection을 돌면서 매핑하고, ArrayList로 묶어서 리턴flatMap
:map
하되, 대상 컬렉션의 원소를 까서 ArrayList로 묶어서 리턴groupBy
: 그룹화 한 Map 리턴all
,any
: Boolean 리턴find
,count
: Element / 개수 리턴
- Sequence
- Java8의 Stream의 하위호환, 병렬실행 기능은 제공 안함
- Lazy, 파이프라이닝 등 Stream의 기능과 완전히 동일하다
- Stream의 특징, 기능들은 Modern Java in Action에서 더 자세하게 공부함
Functional interface
- SAM 인터페이스는 Java에서 함수(람다)를 대입받기 위해 사용
- Java와의 호환을 위해서 지원
fun interface
로 선언, 람다와의 자동 변환을 지원해줌- Java와 달리
interface
에는 추상함수가 1개이더라도 람다를 대입 불가- Kotlin에서는 함수를 위한 타입이 존재함
- 그러므로 interface를 람다를 대입받기 위한 용도로 사용할 필요 없음
with, apply 함수
- 확장(Extension) 함수와 유사한 기능을 하도록 람다를 사용
- 단 클래스가 아니라, 객체를 대상으로 호출
- 확장처럼 람다에서, this를 쓰거나, 내부 함수들을 호출할 수 있다
with
: Reciever와 람다를 받아 실행 결과를 반환apply
: Reciever의 멤버함수, 람다를 받아 실행 후, Reciever를 재반환run
,also
,let
등이 유사한 기능을 함, 참고
1 |
|
The Kotlin type system
Nullability
- 모든 타입은 기본적으로 null을 참조할 수 없음
String?
과 같이 물음표로 null 참조 가능을 표시- nullable 타입에
.
접근시 컴파일 에러 발생 - 대입 가능 여부
String?
은String
에 대입 불가능<T>
제너릭에는 Nullable 대입 가능,<T: Any?>
로 간주<T: Any>
로 명시한 경우에는 Nullable 대입 불가
- 스마트 캐스트처럼 검사 한 이후에는, Not-null 타입으로 변경됨
- if문으로 명시적으로 검사하거나
?.
연산자로 접근해야 접근이 허용됨
?.
safe call operator: 대상이 null이면, null을 값으로 내보냄?:
elvis operator: 대상이 null이면 다른 값을 내보냄- 값을 내보내지 않고, 리턴하거나, 예외를 던질수도 있음
as?
safe cast: 타입 변경시 null이면 null을 내보냄!!
not-null assert: null이면 강제 예외 발생let
을 이용해서 null체크의 가독성을 높일 수 있음- 한개 변수에 대한 null 체크를 수행할 때 가장 유용함
1 |
|
lateinit
멤버를 나중에 초기화 할 수 있게 한다.- nullable 타입의 사용을 줄여줌
- 초기화 전에 접근하면 예외가 발생한다.
- Nullable 타입을 명시적으로 확장할 수 있다.
- 함수 스스로가 “null을 책임지겠다” 라고 말하는 의미
- Java 코드 실행시 호환성: annotation 있는 경우
@Nullable
,@NotNull
->Type?
,Type
- Java 코드 실행시 호환성: annotation 없는 경우
Type
으로도 받을 수 있지만, 예외 발생 가능성이 생김Type?
으로 받으면 Null 관련 확인을 꼭해줘야함- 타입 없이 받으면,
Type?
,Type
둘 다로 사용 가능한 Platform Type .
,.?
모두로 사용 가능하고, Warning도 나오지 않음- 참고로
Type
에.?
를 쓰면 Warning이 나옴
1 |
|
Primitive types
- Kotlin은 value type이 없음
Int
는 컴파일러가 최대한int
로 최적화Int?
의 경우는, null 가능성 때문에Integer
가 될수도 있음- Generic으로 쓰이는 경우는 무조건 Boxed됨
- Type casting
- 정수, 실수의 암시적 타입변환이 없음
- Java가 Boxed Type을 다르게 취급하는 것과 같은 맥락
- 무조건 변환 함수를 불러줘야함.
toLong
,toInt
1L
과1
은 다르게 취급
- 다만 Literal 간의 연산에서는 암시적 타입 변환을 지원
1 |
|
Special types
Any
,Any?
모든 타입의 부모. the root typesUnit
: void를 일부 대체- “의미 없는 기본 단위를 자동으로 리턴함” 이라는 의미를 가짐
- Unit 인스턴스는 싱글톤임
- void 대비 Unit은 Generic에 사용할 수 있다는 장점이 있음
- Java의
Void
도 유사하지만 리턴을 해야한다는 제한이 있음
Nothing
: void를 일부 대체- “아예 리턴하지 않음”을 의미
- 컴파일러가 리턴 실수를 적극적으로 감지해 줄 수 있음
1 |
|
Nullability and collections
- Nullable 타입을 Generic에도 쓸 수 있다.
List<Int?>
와List<Int>?
는 다르다.Collection
은 기본적으로 read-only- 즉 값 변경, 추가를 위한 메소드를 제공해주지 않는다.
- 하지만 데이터가 immutable이라는것은 아님, 컴파일러가 보호해주지않음
- Java는 read-only가 없기 때문에, Java단에서 값이 변경될 수 있음
- Thread safe하지 않다.
listOf
,setOf
,mapOf
MutableCollection
- Collection을 상속받아 만든다.
- 값 변경, 추가, 제거를 위한 메소드들을 제공해준다.
- List:
mutableListOf
,arrayListOf
- Set:
mutableSetOf
,hashSetOf
,linkedSetOf
,sortedSetOf
- Map:
mutableMapOf
,hashMapOf
,linkedMapOf
,sortedMapOf
Array
자바의[]
에 해당- 팩토리 함수로 생성하거나, 생성자+람다로 생성
- spread operator로 가변인자에 활용 가능
filter
,map
등의 추가 기능을 Kotlin 단에서 제공
- Primitive의 배열은 따로 타입을 할당해 최적화 가능
intArrayOf(0, 0, 0, 0, 0)
- boxing 오버헤드가 없어서 훨씬 빠르다
1 |
|