아래의 글은 드미트리 제메로프, 스베트라나 이사코바 저/오현석역,『Kotlin in Action』,에이콘출판사(2017)의 내용을 기반으로 작성하였습니다.
DSL을 이용하겨 코틀린 다운 API 설계
수신 객체 지정 람다 : 코드 블록에서 이름(변수)이 가리키는 대상을 결정
invoke 관례를 이용하여 람다와 프로퍼티 대입을 유연하게
API가 깔끔하다 > 1. 이름, 개념, 코드의 목적이 잘 드러나야함 2. 코드가 간결해야함
코틀린의 간결함
Java 구문
Kotlin 구문
기법
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() }
람다를 괄호 밖으로 빼내는 관례
sp.append("a") sp.append("b")
with (sb) { append("a") append("b") }
수신 객체 지정 람다
general purpose programming language : 명령적 : 각 단계를 순서대로 정확히 기술 : C
domain specific language : 선언적 : 원하는 결과를 기술 : 세부 실행은 엔진 : SQL (데이터베이스 조작), REPL (문자열 조작) : 스스로 제공하는 기능을 제한함으로써 오히려 더 효율적으로 자신의 목표를 달성 : DSL을 범용 언어로 만든 호스트 어플리케이션과 함께 조합하기 어렵다
이러한 문제를 해결하면서 DSL의 다른 이점을 살리는 방법으로 internal DSL 개념 사용, 범용 언어로 작성된 프로그램의 일부, DSL의 핵심 장점을 유지하면서 주 언어를 특별한 방법으로 사용하는 것
fun buildString (
builderAction: (StringBuiler) -> Unit // 함수 타입인 파라미터 정의
): String {
val sb = StringBuilder()
builderAction(sb)
return sb.toString()
}
>>> val s = buildString {
it.append("hello, ")
it.append("world !")
// it은 StringBuilder 인스턴스 지정
}
it을 빼려면
람다를 수신 객체 지정 람다 (lambda with a receiver ) 로 변경
람다의 인자 중 하나에게 수신 객체라는 상태를 부여하면,
이름과 마침표를 명시하지 않아도 그 인자를 멤버로 바로 사용할 수 있음
fun buildString (
builderAction: StringBuiler.() -> Unit // 수신 객체가 있는 함수 타입의 파라미터 선언
): String {
val sb = StringBuilder()
sb.builderAction() // StrinbBuilder 인스턴스를 람다의 수시 객체로 넘긴다.
return sb.toString()
}
>>> val s = buildString {
this.append("hello, ") //this 키워드는 StringBuilder 자신
append("world !") // this가 생략되도 묵시적으로 StringBuilder 인스턴스가 수신 객체로 취급
// it은 StringBuilder 인스턴스 지정
}
파라미터 타입 선언시 일반 함수 타입 선언 대신 확장 함수 타입 extension function type을 사용
확장함수 타입 선언은 람다의 파라미터 목록에 있던 수신 객체 타입을 마라미터 목록을 여는 괄호 앞으로 빼면서 중간에 마침표(.)를 붙인 형태
(StringBuilder) -> Unit을
StringBuilder.() -> Unit으로 변경
앞에오는 (StringBuilder)를 수신객체 타입, 람다에 전다되는 그런 타입의 객체를 수신 객체
string.(Int, Int) -> Unit
수신객체타입 (파마리터 타임) 반환타입
val appendWeather : StringBuilder.() -> Unit = { this.append("How is weather?")}
>> val stringBuilder = StringBuilder("Hi! ")
>>> stringBuilder.appendWeather()
>>> println(stringBuilder)
Hi! How is Weather!
>>> println(buildString(appendWeather))
How is weather?
fun buildString(builderAction: StringBuilder.() -> Unit): String =
StringBuilder().apply(builderAction).toString()
inline fun <T> T.apply(block: T.() -> Unit): T {
block()
return this
}
inline fun <T> T.with(receiver: T, block: T -> R): R = receiver.block()
>>> val map = mutableMapOf(1 to "one")
>>> map.apply { this[2] = "two" }
>>> with (map) { this[3] = "three" }
>>> print(map)
{1 = "one", 2 = "two", 3 = "three"}
fun createSimpleTable() = createHtml().
table {
tr {
td { + "cell"
}
}
}
}
open class Tag(val name: String) {
private val children = mutableListOf<Tag>()
protected fun <T: Tag> doInit(child: T, init: T.() -> Unit) {
child.init()
children.add(child)
}
override fun toString() = "<$name>${children.joinToString("")}</$name>"
}
fun table(init: TABLE.() -> Unit) = TABLE().apply(init)
class TABLE: Tag(table") {
fun tr(init : TR.() -> Unit) = doInit(TR(), init)
}
class TR: Tag("tr") {
fun td(init: TD.() -> Unit) = doInit(TD(), init)
}
class TD: Tag
fun createTable() =
table {
tr {
td {
}
}
}
>>> println(createTable())
<table><tr><td></td></tr></table>
fdsa
코틀린 빌더 : 추상화와 재사용 : TagConsumer
fun StringBuilder.dropdown(
block: DIV.() -> Unit
) : String = div("dropdown", block)
invoke 관례
get, set
foo[bar] foo.get(bar)로 변환
invoke는 () 괄호 사용
class Greeter(val greeting: String) {
operator fun invoke(name: String) {
println("$greeting, $name!")
}
}
>>> val usGreeter = Greeter("Hi")
>>> usGreeter("Colin")
Hi, Colin!
data class Issue {
val id: String, val project: String, val type: String,
val priority: String, val description: String
}
class ImportantIssuePredicate(val project: String) : (Issue) - Boolean {
override fun invoke(issue: Issue): Boolean {
return issue.project == project && issue.isImportant()
}
private fun Issue.isImportant(): Boolean {
return type == "Bug" && (priority == "Major" || priority == "Critical")
}
}
val i1 = Issue("IDEA-15111", "IDEA", "Bug", "Major", "Save settings failed")
val i2 = Issue("IDEA-12444", "Kotlin", "Feature", "Normal", "converter")
val predicate = ImportantIssuePredicate("IDEA")
for ( issue in listOf(i1, i2).filter(predicate) {
print(issue.id)
}
a shoudl startWith("kot")
infix fun <T> T.should(matcher: Matcher<T>) = matcher.test(this)
"kotlin" should start with "kot"
"kotlin".shuld(start).with("kot)
object start
infix fun String.should(x: start): StartWrapper = StartWrapper(this)
class StartWrapper(val value: String) {
infix fun with(prefix: String) =
if (!value.startWith(prefix))
throw AssertionError("String does not start with $prefix: $value")
else
Unit
}
StartWrapper, EndWrapper, HaveWrapper
import java.time.Period
import java.time.LocalDate
val Int.days: Period
get() = Period.ofDays(this)
val Period.ago: LocalDate
get() = LocalDate.now() - this
val Period.fromNow: LocalDate
get() = LocalDate.now() + this
>>> println(1.days.ago)
2016-08-16
>>> println(1.days.fromNow)
2016-08-18
inteface Company {
val name: String
}
data class CompanyImpl(override val name: String): Company
data class Person(
val name: String,
@DeserializedIntefface(CompanyImpl::class) val company: Company
)
annotaion class DeserializedInterface(val targetClass: KClass<out Any>)
annotation class CustomerSerializer {
val serializerClass: KClass<out ValueSerializer<*>>
}
아래의 글은 드미트리 제메로프, 스베트라나 이사코바 저/오현석역,『Kotlin in Action』,에이콘출판사(2017)의 내용을 기반으로 작성하였습니다.
고차 함수 : high order function : 다른 함수를 인자로 받거나 함수를 반환하는 함수 예 list.filter(x > 0)
고차 함수 정의 : 함수 타입 : 함수 문법 : (Int, String) -> Unit : 파라미터 타입 -> 반환타입
val sum : (Int, Int) -> Int = { x, y -> x + y }
val action: () -> Unit = { println(42) }
// 반환 타입 null 될 수 있음
val canReturnNull: (Int, Int) -> Int? = { x, y => null }
// 널이 될 수 있는 함수 타입 변수 정의
var funOrNull: ((Int, Int) -> Int)? = null
인자로 받은 함수 호출
fun String.filter(predicate: (Char) -> Boolean): String
fun String.filter(predicate: (Char) -> Boolean) : String {
val sb = StringBuilder()
// length = String 내 length
for (index in 0 until length) {
val element = get(index)
if (predicate(element) sb.append(element)
}
return sb.toString()
}
>>> println("ab1c".filter{ it in 'a'..'z'}
abc
fun <T> Collection<T>.joinToString (
seperator: String = "",
prefix: String = "",
postfix: Sring = ""
) : String {
val result = StringBuilder(prefix)
for ((index, element) in this.withIndex()) {
if (index > 0) result.append(prefix)
result.append(element)
}
result.append(postfix)
return result.toString()
}
fun <T> Collection<T>.joinToString (
seperator: String = "",
prefix: String = "",
postfix: Sring = "",
tranform: (T) -> String = { it.toString() }
) : String {
val result = StringBuilder(prefix)
for ((index, element) in this.withIndex()) {
if (index > 0) result.append(prefix)
result.append(transform(element))
}
result.append(postfix)
return result.toString()
}
>>> val letters = listOf("Alpha", "Beta")
>>> println(letters.joinToString())
Alpha, Beta
>>> println(letters.joinToString { it.toLowerCase() } )
alpha, beta
>>> println(letters.joinToString(seperator = "! ", postfix = "!", transform = { it.toUpperCase() }))
ALPHA! BETA!
fun <T> Collection<T>.joinToString (
seperator: String = "",
prefix: String = "",
postfix: Sring = "",
tranform: ((T) -> String)? = null
) : String {
val result = StringBuilder(prefix)
for ((index, element) in this.withIndex()) {
if (index > 0) result.append(prefix)
val str = transform?.invoke()
?: element.toString()
result.append(str)
}
result.append(postfix)
return result.toString()
}
함수에서 함수 반환
enum class Delivery { STANDARD, EXPRESS }
class Order(val itemCount: Int)
fun getShippingCostCalculator(delivery: Delivery): (Order) -> Double {
if (delivery == Delivery.EXPRESS) {
return { order -> 2.0 * order.itemCount }
}
return { order -> 1.5 * order.itemCount }
}
data class Person{
val firtName: String,
val lastName: String,
val phoneNumber: String?
}
class ContactListFilters {
var prefix: String = ""
var onlyWithPhoneNumber: Boolean = false
fun getPredicate(): (Person) -> Boolean {
val startWithPrefix = { p: Person ->
p.firstName.startWith(prefix) || p.lastName.startWith(prefix)
}
if (!onlyWithPhoneNumber) {
return startWithPrefix
}
return { startWithPrefix(it) && it.phoneNumber != null}
}
}
data class SiteVisit(
val path: String,
val duration: Double,
val os: OS
)
enum class OS { WINDOW, LINUX, MAC, IOS, ANDROID }
val log = listOf(
SiteVisit("/", 34.0, OS.WINDOWS),
SiteVisit("/", 22.0, OS.MAC),
SiteVisit("/login", 12.0, OS.WINDOWS),
SiteVisit("/signup", 8.0, OS.IOS),
SiteVisit("/", 16.3, OS.ANDROID),
)
fun List<SiteVisit>.averageDurationFor(predicate: (SiteVisit) -> Boolean) =
filter(predicate).map(SiteVisit::duration).average()
>>> println(log.averageDurationFor { it.os == OS.IOS && it.path = "/signup" })
함수 inline, noline
use 함수
withLock함수
non-local변환 : 자신을 둘러싸고 있는 블록보다 더 바깥에 있는 다른 블록을 반환하게 만드는 return문 non-local : 인라인 함수인 경우
return이 바깥쪽 함수를 반환시킨다
인라인 되지 않는 함수에 전달되는 람다안에서 return을 사용할 수 없다.
로컬 return : 람다의 실행을 끝내고 람다를 호출했던 코드이 실행을 계속 이어간다.
return으로 실행을 끝내고 싶은 람다 식 앞에 레이블 lable@ return뒤에 그 레이블 추가
람다식에는 레이블이 2개 이상 붙을 수 없다.
무명함수는 : 코드 블록을 함수에 넘길 때 사용할 수 있는 다른 방법이다.
fun lookForAlice(people: List<Persion>) {
// returned
people.forEach(fun(person) {
if (person.name == "Alice") return
}
}
//returned
fun lookForAlice(people: List<Person>) {
people.forEach {
if (it.name == "Alice") return
}
}
아래의 글은 드미트리 제메로프, 스베트라나 이사코바 저/오현석역,『Kotlin in Action』,에이콘출판사(2017)의 내용을 기반으로 작성하였습니다.
자바 : 언어 기능을 타입에 의존하는 관례
코틀린 : 함수 이름에 의존하는 관례
자바 : + : primative type에서 사용, String : String concat 연산
코틀린 : + : plus란 이름으로 된 연산자 + 로 대체됨, operator 키워드 붙이면 됨, 확장함수로도 정의 가능
* 관례로 쓰는 이름을 붙였는데, operator 연산자가 없으면 operator modifier is required 라고 에러
a + b - > a.plus(b) 가 됨
교환 법칙이 성립이 안됨 예를 들어 p * 1.5와 1.5 * p는 다름, p.times가 있냐와 Double.times가 있냐의 차이
오버로딩 가능한 이항 산술 연산자
expression
operator convention
a * b
times
a / b
div
a % b
mod (1.1 rem)
a + b
plus
a - b
minus
data class Point(val x: Int, val y: Int) {
operator fun plus(other: Point): Point {
return Point(x + other.x, y + other.y)
}
}
>>> val p1 = Point(1, 1)
>>> val p2 = Point(2, 3)
>>> println(p1 + p2)
Point(x = 3, y = 4)
operator fun Point.plus(other: Point): Point {
return Point(x + other.x, y + other.y)
}
비트연산자
operator convention
정의
자바
shl
왼쪽 시프트
<<
shr
오른쪽 시프트
>>
ushr
오른쪽 시프트 0 부호 비트
>>>
and
곱
&
or
합
|
xor
배타
^
inv
반전
~
복합대입연산자
expression
operator convention
+=
plusAssign
-=
minusAssign
*=
timesAssign
/=
remAssign
단항연산자오버로딩
expression
operator convention
+a
unaryPlus
-a
unaryMinus
!a
not
++a, a++
inc
--a, a--
dec
operator fun BigDecimal.inc() = this + BigDecimal.ONE
>>> val num = BigDecimal.ZERO
>>> println(num++)
0
>>> println(num)
1
>>> println(++num)
2
비교연산자
expression
java
==
equals
===
==
!=
!equals
<
p1.compareTo(p2) < 0
>
p1.compareTo(p2) > 0
<=
p1.compareTo(p2) <= 0
>=
p1.compareTo(p2) >= 0
Comparable에 compareTo가 operator가 붙어 있어서 하위에서는 붙일 필요가 없다.
Comparable 인터페이스를 구현하는 모든 자바 클래스는 코틀린에서 간결한 연산자 구문으로 비교 가능
컬렉션
expression
operator convention
val x = map[0]
get
map[1] = 42
set
in
contains
...
rangeTo
for (x in list) { ... }
iterator
val now = LocalDate.now()
val vacation = now...now.plusDays(10)
>>> println(now.plusWeeks(1) in vacation)
true
operator fun CharSequence.iterator(): CharInterator: Interator
>> for(c in "abc") {}
operator fun ClosedRange<LocalDate>.iterator(): Interator<LocalDate> =
object : Iterator<LocalDate> {
val current = start
override fun hasNext() = current <= endInclusive
override fun next() = current.apply {
currnet = plusDays(1)
}
}
>>> val newYear = LocalDate.ofYearDay(2017, 1)
>>> val daysOff = newYear.minusDays(1)..newYear
>>> for (dayOff in dayOff) { println(dayOff) }
2016-12-31
2017-01-01
구조분해선언 : componentN 함수
val (a, b) = p > val a = p.component1() val b = p.component2()
* data 클래스는 componentN 함수를 자동으로 만들어 준다. 코틀린 표준 라이브러리는 맨앞의 5개 원소에 대한 componentN을 제공
data class NameComponents(val name: String, val extention: String)
fun splitNameExt(filename: String): NameCompoents {
val result = filename.split('.', limit = 2)
return NameComponents(result[0], result[1])
}
>>> val (name, ext) = splitNameExt("file.txt")
>> println(name)
file
>>> println(ext)
txt
fun splitNameExt2(filename: String): Namecomponents {
var (name, extention) = file.split('.', limit = 2)
return NameComponents(name, extention)
}
for (entry in map.entries) {
val key = entry.component1()
val value = entry.compoent2()
}
위임프로퍼티 : 연산 위임
class Delegate {
operator fun getValue(...) { ... }
operator fun setValue(..., value: Type) { ... }
}
class Foo {
var p: Type by Delegate()
}
>>> var foo = Foo()
>>> var oldValue = foo.p // delete.getValue 호출
>>> foo.p = newValue // delete.setValue 호출
backing 프로퍼티 : lazy지원, lazy다음에 로딩할 인자는 람다
class Person(val name: String) {
val email by lazy { loadEmails(this) }
}
위임프로퍼티 실제 구현 : 연산 수행 값 get, set 만으로도 특정 메소드 get set이 되도록 위임가능하고 위임안에서 다른 작업도 수행 가능
open class PropertyChangeAware {
protected val changeSupport = PropertyChangeSupport(this)
fun addPropertyChangeListner(listener: PropertyChangeListenser) {
changeSupport.addPropertyChangeListener(listener)
}
fun removePropertyChangeListener(listener: PropertyChangeListener) {
changeSupport.removePropertyChangeLisner(listener)
}
}
class ObservableProperty
(var propValue: Int, val changeSupport: PropertyChangeSupport) {
operator fun getValue(p: Person, prop: KProperty<*>): Int = propVAlue
operator fun setValue(p: Person, prop: KProperty<*>, newValue: Int) {
val oldValue = propValue
propValue = newValue
changeSupport.firePropertyChange(prop.name, oldValue, newValue)
}
}
class Person
(val name: String, age: Int salary: Int) {
} : PropertyChangeAware() {
private val observer = {
prop: KProperty<*>, oldValue: Int, newValue: Int ->
changeSupport.firePropertyChange(prop.name, oldValue, newValue)
}
var age: Int by ObservableProperty(age, changeSupport)
var salary: Int by ObservableProperty(salary, changeSupport)
}
val x = c.prop
// val x = <delegate>.getValue(c, <property>)
c.prop = x
// <delegate>setValue(c, <property>, x)
아래의 글은 드미트리 제메로프, 스베트라나 이사코바 저/오현석역,『Kotlin in Action』,에이콘출판사(2017)의 내용을 기반으로 작성하였습니다.
널 가능성과 컬렉션
List<Int?> : 리스트 자체는 널이 아님, Int는 널일 수 있음.
List<Int>? : 리스트 자체는 널일 수 있음. Int는 널 아님
List<Int?>? : 리스트 자체가 널일 수 있고, Int도 널 일수 있음
List<Int> : 리스트도 널 아니고, Int도 널 아님.
읽기 전용과 변경 가능한 컬렉션
kotlin.collections.Collection : 컬렉션에 연산 수행하지만 읽기 전용이고, 수정을 가 할 수 없음
kotlin.collections.MutableCollection: 컬렉션에 수정 (원소 추가, 삭제 등) 가능
코틀린 컬렉션 : 읽기전용, 변경 가능
읽기전용 인터페이스 : Interable
변경가능 인터페이스 : MutableIterable
kotlin
Java
컬렉션 생성 함수
type
Read Only
Mutable
List
listOf
mutableListOf, arrayListOf
Set
setOf
mutableSetOf, hashSetOf, linkedSetOf, sortedSetOf
Map
mapOf
mutableMapOf, hashMapOf, linkedMapOf, sortedMapOf
java와 혼용할때 생기는 문제
kotlin에서는 listOf생성은 readonly라고 하였는데 변경되어 있음을 아래의 코드로 증명
// java code
public class CollectionUtils {
public static List<String> uppercaseAll(List<String> items) {
for (int i = 0; i < items.size(); i++){
items.set(i, items.get(i).toUpperCase());
}
}
}
// kotlin code
fun printlnUppercase(list: List<String) {
println(CollectionUtils.uppercaseAll(list)
println(list.first())
}
>>> var list = listOf("a", "b", "c")
>>> printlnUppercase(list)
[A, B, C]
A
아래의 글은 드미트리 제메로프, 스베트라나 이사코바 저/오현석역,『Kotlin in Action』,에이콘출판사(2017)의 내용을 기반으로 작성하였습니다.
코틀린은 자바의 primative type을 사용할 수 없다. wrapper type만 사용한다.
그렇다면 항상 객체 타입만 사용한다는 것인데 계산일 경우 비효율 아닌가 > 실행 시점에 코틀린의 타입은 가장 효율적인 방식으로 표현한다. 대부분의 Int타입은 int 타입으로 컴파일 함. 이런 컴파일이 불가능한 경우는 collection들의 generic을 사용하는 경우. primative type은 null을 가질 수 없는데, Int?이면 이건 null을 허용한다는 의미인데 이러한 경우에는 컴파일시 int로 컴파일 하지 않는다. 코틀린은 큰숫자로 자동 변환 하지 않는다 ( Int -> Long ). 명시적으로 변환해야한다. val l : Long = i.toLong()
java의 primative type으로 매핑될 수 있는 타입
정수 : Byte, Short, Int, Long
부동소수점 수 : Float, Double
문자 : Char
불리언 : Boolean
원시 타입 리터럴
L접미사가 붙은 Long 타입 리터럴 : 123L
표준 부동소수점 표기법을 사용한 Double 타입 리터럴 : 0.12, 2.0, 1.2e10, 1.2e-10
f나 F 접미사가 붙은 Float 타입 리터럴 : 123.4f, .456F, 1e3F
0x나 0X 접두사가 붙은 16진 리터럴 : 0xCAFEBABE, 0xbcdL
0b나 0B 접두사가 붙은 2진 리터럴 : 0b000000101
숫자 중간에 _를 붙일 수 있음 1_234L
특별한 타입 :
Any, Unit, Nothing
Any : 최상위 타입, Any는 널이 될 수 없음
Unit : void 타입, 함수형 프로그래밍에서 Unit은 단 하나의 인스턴스만 갖는 타입을 의미
Nothing :
interface Processor<T> {
fun process(): T
}
class NoResultProcessor : Processor<Unit> {
override fun process() {
}
}
fun fail(messsage: String) : Nothing {
throw IllegalStateException(message)
}
val address = company.address ?: fail("No address")
A data type constrains the values that anexpression, such as a variable or a function, might take. This data type defines the operations that can be done on the data, the meaning of the data, and the way values of that type can be stored.
데이터 타입은 컴파일러나 인터프리터가 프로그래머에게 데이터를 어떻게 다루워야하는지를 알려주는 부가 정보를 포함한다. 데이터 타입은 수행할 수 있느 변수나 함수의 제약을 표현한다. 데이터 타입은 데이터에 수행할 수있는 명령과 데이터의 의미와 저장하는 방식에 대해서 정의한다.
fun printAllCaps(s: String?) {
val allCaps: String? = s?.toUpperCase()
printin(allCaps)
}
class Employee(val name: String val manager: String?)
fun managerName(employee: Employee) : String? = employee.manager?.name
class Address(val streeAddress: String, val zipCode: Int,
val city: String, val country: String)
class Company(val name: Stirng, val address: Address?)
class Person(val name: String, val company: Company?)
fun Person.countyName(): String {
val country = this.company?.address?.country
return if (countrey != null) country else "Unknown"
}
엘비스 연산자 : ?: : null coalescing
코들린에서는 return이나 throw 등의 연산도 식이다.
엘비스 연산자의 우향에 return throw 등의 연산을 넣을 수 있음
fun foo(s: String?) {
val t: String = s ?: ""
}
s ?: ""
if s != null
return s
else
""
fun strLenSafe(s: String?): Int = s?.length ?: 0
fun Person.countryName() = company?.address?.country ?: "Unknown"
안전한 캐스트 : as?
class Person(val firstName: String, val lastName: String) {
override fun equals(o: Any?): Boolean {
val otherPerson = o as? Person ?: return false
return otherPerson.firstName == firstName &&
otherPerson.lastName == lastName
}
override fun hashCode(): Int =
firstName.hashCode() * 37 + lastName.hashCode()
}
널아님 체크 : !!
fun ignoreNulls(s: String?) {
val sNotNull: String = s!!
println(sNotnull.length)
}
let 함수
자신의 수신 객체를 인자로 전달받은 람다에게 넘긴다
let 함수도 널이 될 수 있는 타입의 값에 대해 호출할 수 있지만 let은 this가 널인지 검사하지 않는다.
fun sendEmailTo(email: String) {...}
if (email != null) sendEmailTo(email)
fun sendEmailTo(email: String) {
println("Sending email to $email")
}
var email: String? = "yole@example.com"
email?.let { sendEmailTo(it) }
email = null
email?.let { sendEmailTo(it) }
// 2줄
val person: Person? = getTheBestPersonInTheWorld()
if (person != null) sendEmailTo(person.email)
// 한줄로 줄일 수 있음
getTheBestPersonInTheWorld()?.let { sendEmailTo(it.email) }
나중에 초기화할 프로퍼티
코틀린에서 클래스 안의 널이 될 수 없는 프로퍼티는 생성자 안에서 초기화 해야함
lateinit 키워드는 나중에 초기화 가능, 나중에 초기화 하는 프로퍼티는 var로 해야함. val은 final이므로 생성자 안에서 초기화 해야함.
널이 될 수 있는 타입 확장
널이 될수 있는 확장 함수를 정의하면 null 값을 다루는 좋은 도구로 활용
코틀린에서는 널이 될 수 있는 타입의 확장 함수 안에는 this가 널이 될 수 있다는 점이 자바와 다르다
let 함수도 널이 될 수 있는 타입의 값에 대해 호출할 수 있지만 let은 this가 널인지 검사하지 않는다.
fun verifyUserInput(input: String) {
if (input.isNullOrBlank()) {
println("Please fill in the required fields")
}
}
fun String?.isNullOrBlank(): Boolean = this == null || this.isBlank()
val person: Person ? = ...
person.let { sendEmailTo(it) }
>> ERROR
person?.let { sendEmailTo(it) }
타입 파라미터 널 가능성
널 가능성과 자바
@Nullable + Type = Type?
@NotNull + Type = Type
널 인식 가능한 annotation package :
javax.annotation
android.support.annotation
org.jetbrains.annotation
이런 널 가능성 annotation 이 소스코드에 없는 경우 = 코틀린 플랫폼 타입
플랫폼 타입
코틀린이 널 관련 정보를 알수 없는 타입 : 그 타입은 널이 될 수 있는 타입으로 처리해도 되고 널이 될 수 없는 타입으로 처리해도 된다.
결국 알수 없으므로 에러 발생의 책임을 구현자에게 위임
코틀린 컴파일러는 public 함수의 널이 아닌 타입인 파라미터와 receiver에 대한 널 검사를 자동 추가
왜 플랫폼 타입을 도입했는가?
즉 > 모든 자바 타입을 널이 될 수 있는 타입으로 다루면 더 안전하지 않을까? 즉 널 체크를 하면 되지 않을까?
그럼 null이 아닌 값에도 null 체크를 하게 된다.
ArrayList<String?> 이면 이 값의 원소에 접근할 때마다 널 검사 수행 - > 안정성보다 검사 비용이 더 많이 호출됨 - > 비효율적
자바코드에서 가져온 타입만 플랫폼 타입이 됨
String! 타입은 자바에서 자바 코드에서 온 타입
상속에 대해서도 널 가능성
자바 클래스나 인터페이스를 코틀린에서 구현할 경우 널 가능성을 제대로 처리하는 일이 중요
구현 메소드를 다른 코틀린 코드가 호출가능하므로 코틀린 컴파일러는 널이 될 수 없는 타입에 선언한 모든 파라미터에 널 아님을 체크하는 단언문을 만들어줌
// java
interface StringProcessor {
void process(String value)
}
// kotlin
class StringPrinter: StringProcessor {
override fun process(value: String) {
println(value)
}
}
class NullableStringPrinter: StringProcessor {
override fun process(value: String?) {
if (value != null)
println(value)
}
}
postponeComputation( 1000,
object : Runnable {
override fun run() {
doSomething()
}
}
)
postponeComputation(1000) { doSomething() }
val runnable = Runnable { doSomething() }
fun handleComputation() {
postponeComputation(1000, runnable)
}
fun handleComputation(id: String) {
postponecomputation(1000) { doSomething() }
}
SAM Single Abstract Method : SAM 생성자
람다를 함수형 인터페이스로 명시적으로 변경
val listener = OnClickListener { view ->
val text = when (view.id) {
R.id.button1 -> "First Button"
R.id.button2 -> "Second Button"
else -> "Unknown"
}
}
button1.setOnClickListener(listener)
button2.setOnClickListener(listener)
수신 객체 지정 람다 lambda with receiver : with, apply
with : 어떤 객체의 이름을 반복하지 않고도 그 객체에 대해 다양한 연산을 수행
with 파라미터가 2개 있는 함수, 첫번째 파라미터는 객체, 두번째 함수는 람다
with(stringBuilder, { ... })
첫번째 파라미터 객체 : 두번째 받을 람다의 수신 객체, 두번째 파라미터 : 람다
this@OuterClass.toString()
fun alphabet() : String {
val result = StringBuilder()
for (letter in 'A'...'Z') {
result.append(letter)
}
result.append("\n Jisoo know alphabet");
return result.toString()
}
fun alphabet() : String {
val stringbuilder = StringBuilder()
return with(stringbuilder) {
for (letter in 'A'...'Z') {
this.append(letter)
}
this.append("\n Jisoo know the alphabet")
}
this.toString()
}
fun alphabet() = with(StringBuilder()) {
for (letter in 'A'...'Z') {
append(letter)
}
toString()
}
apply : with와 동일한 방식인데 자신에게 전달된 객체(수신 객체)를 반환
apply는 확장함수로 정의되어 있음
apply 함수는 객체의 인스터스를 만들면서 즉시 프로퍼티 일부를 초기화 적용
java에서는 Builder객체가 이렇게 담당
fun alphabet() = StringBuilder().apply {
for (letter in 'A'...'Z') {
append(letter)
}
append("\n Hi JiSoo")
}.toString()
fun alphabet() = buildString {
for (letter in 'A'...'Z') {
append(letter)
}
append("\n Hi JiSoo")
}
아래의 글은 드미트리 제메로프, 스베트라나 이사코바 저/오현석역,『Kotlin in Action』,에이콘출판사(2017)의 내용을 기반으로 작성하였습니다.
람다 : 다른 함수에 넘길 수 있는 작은 코드 조각
Alambda expressionis a short block of code which takes in parameters and returns a value
Lambdasarecode blocksenclosed in curly braces
람다 식은 주로 컬렉션을 다룸
코틀린에는 함수 호출시 맨뒤에 있는 인자가 람다 식이라면 그 람다를 괄호 밖으로 빼낼수 있음
람다 파라미터 이름을 디폴트 이름 it
val sum = { x: Int, y: Int -> x + y }
println(sum(1, 2))
{ println(42) }()
run { println(42) }
val peopleList = listOf(Person("Alice", 29), Person("Bob", 31))
peopleList.maxBy(it.age)
peopleList.maxBy(Person::age)
peopleList.maxBy({p: Person -> p.age})
peopleList.maxBy() {p: Person -> p.age}
peopleList.maxBy {p: Person -> p.age}
peopleList.maxBy { p -> p.age }
val nameList = peopleList.joinToString( seperator = " ",
transform = { p: Person -> p.name})
peopleList.joinToString(" ") { p.Person -> p.name }
val sum = { x: Int, y: Int ->
println("computing sum")
x + y
}
fun printWith(messageC: Collection<String>, prefix: String) {
messageC.forEach {
println(" $prefix $it ")
}
}
fun printCount(responses: Collection<String>) {
var clientErrors = 0
var serverErrors = 0
responses.forEach {
if (it.startWith("4")) {
clientErrors++
} else if (it.startWith("5")) {
serverErrors++
}
}
println("$clientErrors $serverErrors")
}
// 변경 가능한 복사
class Ref<T>(var value: T)
// val 변경 불가능
val counter = Ref(0)
// 변경 불가능한 변수지만 내부 필드 값은 변경 가능
// list 의 내부 값 변경과 동일
val inc = { counter.value++}
// var 변경 가능
var counter = 0
var inc = { counter++ }
// var 변경 가능 함수
var counter = Ref(0) 클래스 인스턴스에 넣음.
멤버 참조 : member reference
자바의 method reference
최상위 함수 참조
확장 함수 참조
생성자 참조 : constructor reference
peopleList.maxBy(Person::age)
// memer reference
fun salute() = println("salute")
run(::salute)
// constructor reference
data class Person(val name: String, val age: Int)
val createPerson = ::Person
val p = createPerson("Alice", 26)
// 확장 함수 reference
fun Person.isAdult() = age > 21
val predicate = Person::isAdult
// bound memer reference
val p = Person("Colin", 34)
val personAgeFunction = Person::age
println(personAgeFunction(p))
val personAgeFunction= p::age
println(personAgeFunction())
코틀린이 보통 람다를 무명 클래스로 컴파일 하지만 그렇다고 람다 식을 사용할 때 마다 새로운 클래스가 만들어지지 않는다
아래의 글은 드미트리 제메로프, 스베트라나 이사코바 저/오현석 역, 『Kotlin in Action』,에이콘출판사(2017)의 내용을 기반으로 작성하였습니다.
클래스와 인터페이스
1. 인터페이스에 프로퍼티 선언이 들어갈 수 있다.
2. sealed는 클래스 상속을 제한한다. ( when으로 분기 타는데 상속 받으면 else 에서 처리 될 수도 있음, 이를 막자)
3. 기본 선언은 public final 임
4. 인터페이스에 구현이 있는 메소드 (java 8의 디폴트 메서드와 비슷)
5. 클래스 이름 뒤에 : 인터페이스 혹은 클래스 ( extends와 implements가 별도 없다.)
6. override 변경자 반드시 사용
7. 디폴트 메서드 default 안 붙여도 됨
8. open final abstract 변경자 ( modifier )
9. 상속을 허용 하려면 open을 붙여야 한다.
10. visibility modifier : public , private, protected, internal (자바의 기본인 패키지 전용은 없음), internal : 한꺼번에 컴파일 되는 코틀린 파일
11. 최상위 선언 ( 클래스 함수 프로퍼티 )에 private 가능
12. protected 오직 상속 클래스에서만 접근 ( 자바는 같은 패키지 안에서 접근 가능)
13. 코틀린 nested class : class A, inner class : inter class A ( 자바 nested class : static class A, inner class : class A (클래스안에)
* fragile base class : 하위 클래스가 상속할 경우 상위 클래스의 정확한 규칙을 이해 못하고 구현 할 경우 문제가 발생할 수 있음. effective java에서는 확실하지 않으면 상속 못하게 하던지 설명을 다 넣던지.
interface Clickable {
fun click()
fun showOff() = println("clickable.");
}
interface Focusable {
fun setFocus(value: Boolean) = println("focus { if (value) "on" else "off"}.")
fun showOff() = println("focusable.");
}
class Button : Clickable, Focusable {
override fun click() = println("clicked.")
override fun showOff() {
super<Clickable>.showOff()
super<Focusable>.showOff()
}
}
open class RichButton: Clickable {
fun diable() {}
open fun animate() {}
final override fun click() {}
}
abstract class Animated {
abstract fun animate()
}
class Outer {
inner class Inner {
fun getOuterReference(): Outer = this@Outer
}
}
sealed class Expr {
class Num(val value: Int) : Expr()
class Sum(val left: Expr, val right: Expr) : Expr()
}
fun eval(e: Expr) : Int =
when(e) {
is Expr.Num -> e.value
is Expr.Sum -> e.left + e.right
// else 가 필요 없음 sealed 키워드
// 추후에 Expr에 추가되면 컴파일 오류나고 여기에도 추가해야됨.
}
생성자 관련
14. 기반 클래스 이름 위에는 꼭 빈 괄호가 들어간다.
15. 싱글턴 클래스 > 비공개 생성자 private constructor
16. 부 생성자가 필요한 이유는 자바 상호 운영성
class User(val nickname: String)
class User constructor(_nickname: String) {
val nickname: String
init {
nickname = _nickname
}
}
class User(_nickname: String) {
val nickname = _nickname
}
class User(val nickname: String, val isSubscribed: Boolean = true)
open class Button // 인자가 없는 디폴트 생성자가 만들어진다.
class RadioButton: Button() // Button 클래스의 생성자를 호출
class Secretive private constructor() {}
open class View {
constructor(ctx: Context) {
}
constructor(ctx: Context, attr: AttributeSet) {
}
}
class MyButton: View {
constructor(ctx: Context) : super(ctx) {
}
constructor(ctx: Context, attr : AttributeSet) : super(ctx, attr) {
}
}
class MyButton2: View {
constructor(ctx: Context) : this(ctx, MYSTYLE) {
}
constructor(ctx: Context, attr : AttributeSet) : super(ctx, attr) {
}
}
인터페이스에 선언된 프로퍼티
interface User {)
val nickname : String
}
class PrivateUser(override val nickname: String) : User
class SubscribeUser(val email: String) : User {
override val nickname: String
// 이 값 불리때 마다 실행
get() = email.substringBefore('@')
}
class FacebookUser(val accountId: Int) : User {
// 방식 초기화시 한번 실행
override val nickname = getFacebookName(accountId)
// getFackbookName은 다른 곳에 정의 된 것으로 가정
}
어떤 값을 저장하되 그 값을 변경하거나 읽을 때 정해진 로직을 실행하는 프로퍼티
field라는 특별한 식별자를 통해서 뒷받침할 필드에 접근 가능
게터에서는 field 값을 읽을 수만 있고, 세터에서는 field 값을 읽고 쓰기 가능
class User(val name: String) {
var address: String = "unknown"
set(value: String) {
println("address changed ${field} -> ${value}")
field = value
}
}
class LengthCounter {
var counter: Int = 0
private set
fun addWord(word: String) {
counter += word.length
}
}
class User(val id: Int, val name: String, val address: String)
fun saveUser(user: User) {
if (user.name.isEmpty())
throw IllegalArgumentException("Can't save : name empty : userId => ${user.id} ")
if (user.address.isEmpty())
throw IllegalArgumentException(Can't save : address empty")
// save to db
}
fun saveUser(user: User) {
fun validate(user: User, value: String, fieldName: String) {
if (value.isEmpty()
throw IllegalArgumentException("Can't save : ${fieldName} empty : userId => ${user.id} ")
}
validate(user, user.name, "name")
validate(user, user.address, "address")
// save to db
}
fun User.validateBeforeSave() {
fun validate(value: String, fieldName: String) {
if (value.isEmpty()
throw IllegalArgumentException("Can't save : ${fieldName} empty : userId => ${id} ")
}
validate(user.name, "name")
validate(user.address, "address")
}
// 더 간단해 짐
fun saveUser(user: User) {
user.validateBeforeSave()
// save to db
}
val binaryCharMap = TreeMap<Char, String>()
for (c in 'A'..'F') {
val binary = Integer.toBinaryString(c.toInt())
binaryCharMap[c] = binary
// java style binaryCharMap.put(c, binary)
}
for ((ch, bi) in binaryCharMap) {
println("C ${ch} : Binary ${bi}"
}
val list = arrayListOf("a", "b", "c")
for((idx, e) in list.withIndex()) {
println("Index ${idx} Element ${e}")
}
in으로 범위 검사
fun isLetter(c: Char) = c in 'a'..'z' || c in 'A'..'Z'
fun isNotDigit(c: Char) = c !in '0'..'9'
fun recongnize(c:char) =
when (c) {
in '0'..'9' -> "digit"
in 'a'..'z', in 'A'..'Z' -> 'Alpabet'
else -> "Unknon char ${c}"
}
"Kotlin" in "Java".."Scala"
// "Java" <= "Scala" && "Kotlin" <= "Scala"
>>> printlin("Kotlin" in "Java".."Scala")
>>> true
아래의 글은 드미트리 제메로프, 스베트라나 이사코바 저/오현석역,『Kotlin in Action』,에이콘출판사(2017)의 내용을 기반으로 작성하였습니다.
( 1 + 2 ) + 4 같은 산술식을 계산하는 함수
interface Expr
class Num(val value: Int): Expr
class Sum(val left: Num, val right: Num): Expr
// (1 + 2) + 4
Sum(Sum(Num(1), Num(4)), Num(4))
1. 식은 트리 구조로 저장됨, Num은 노드에서 말단, Sum 은 자식 둘 있는 중간 노드, Sum의 두 자식은 덧셈의 인자
2. Expr 인터페이스 구현
3. class 뒤의 : Expr은 인터페이스 구현 한다는 의미 ( fun의 뒤의 : returnType 은 리턴값이다. class 뒤의 인터페이스 구현은 좀 이상하지 않는가 라고 생각했는데, java에서도 a implements Expr, b implements Expr , class Sum 의 메소드는 public Expr sum(Int a, Int b) 뭐 이런깃으로 표현을 하는데 즉, interface 구현은 즉 상위 구현체의 리턴으로 upcasting 된 것을 의미하므로 return type이자 interface의 구현이기 때문이라고 표현할 수 있으니 결과론 적으로 return type을 inteface로 표현했다는 것은 inteface의 구현이라고 생각할 수 있음, 놀랍군.)
4. 스마트 캐스트는 타입 검사 뒤에 변경 될 수 없는 변수에만 적용 가능하다 ( p 148), 즉 final 이여야하는데, 프로퍼티는 기본 final 임
// java style
fun eval(e: Expr): Int =
if (e is Num) {
val n = e is Num
return n.value
}
if (e is Sum) {
return eval(e.right) + eval(e.left)
}
throw IllegalArgumentException("Unknown expression")
fun eval(e: Expr): Int =
if (e is Num) {
e.value
} else if (e is Sum) {
eval(e.right) + eval(e.left)
} else {
throw IllegalArgumentException("Unknown expression")
}
eval(sum(Num(1), Num(2)))
// use when expression
fun eval(e:Expr): Int =
when(e) {
is Num -> e.value
is Sum -> eval(e.right) + eval(e.left)
else -> throw IllegalArgumentException("Unknown expression")
}
fun evalWithLogging(e: Expr): Int =
when(e) {
is Num -> {
println("Num: ${e.value}")
e.value
}
is Sum -> {
val left = evalWithLogging(e.left)
val right = evalWithLogging(e.right)
print("sum: ${left} + ${right}")
left + right
}
else -> throw IllegalArgumentException("Unknown expression")
}
아래의 글은 드미트리 제메로프, 스베트라나 이사코바 저/오현석역,『Kotlin in Action』,에이콘출판사(2017)의 내용을 기반으로 작성하였습니다.
변수 variable
함수 function
클래스 class
프로퍼티 property
스마트 캐스트 smart cast : 타입 검사, 타입 캐스트, 타입 강제 변환
타입 추론 : type inference
문자열 템플릿 : string template : 변수를 문자열 안에서 사용
when : java의 switch : 코틀린에서는 enum class : 자바는 그냥 enum
코틀린의 if는 값을 만들어내기때문에 java의 3항 연산자 a > b ? a : b 가 if (a > b) a else b 로 됨 그래서 3항 연산자가 별도로 없다.
if 문에 식이 하나면 중괄호 생략 그리고 결과값, 하나 이상의 식이면 블럭으로 감싸고 마지막이 결과값이 여야함.
식이 본문인 함수는 블록을 본문으로 가질수 없고
* if (a > b ) a else b
블록이 본문인 함수는 내부에 return 문이 반드시 있어야 한다.
* if (a > b) {
print(echo)
return a }
else
b
함수 선언 : fun
함수를 최상위 수준에 정의할 수 있다. (자바는 클래스 안에 선언 > 자바로 변환하면 어떻게 나올라나...)
; 세미 콜론을 끝에 붙이지 않아도 된다.
구문 statement와 표현식 expression의 구분
statement는 if는 표현식이다.
표현식은 값을 만들어내며 ( return 값이 있다로 설명) 다른 식의 하위 요소로 계산에 참여할 수 있다.
구문은 자신을 둘러싸고 있는 가장 안쪽 블록의 최상위 요소로 존재하여 아무런 값을 만들어내지 않는다. (return 값이 없다.)
자바는 모든 제어 구조가 구문 ( return 이 없으므로 명시적으로 return 해줘야함)
코틀린은 루프를 제외한 대부분의 제어 구조가 식 ( return이 됨)
대입식, 비교식
식이 본문인 함수 : fun max( a: Int, b: Int): Int = if (a > b) a else b (if 도 식임)
반환타입 생략 (추론 가능) fun max(a: Int, b: Int) = if (a>b) a else b
변수
val :변하지 않은 값
var : 변하는 값
val question = "질문"
val answer = 42
val ansswer2: Int = 42
val yearstoCompute = 7.5e6
val answer: Int
answer = 42
val 참조 자체는 불편이더라도 참조가 가리키는 객제 내부의 값은 변경 가능
val languageList = arrayListOf("Java")
languageList.add("Kotlin")
var answer = 42
answer = "답" // 컴파일 오류
문자열 템플릿
$변수
${구문}
Hello $name!
$name님 반가워요! // 컴파일 오류
${name}님 반가워요! // 잘됨
변수명을 감싸는 습관
컴파일된 코드는 StringBuilder를 사용
클래스
코틀린의 기본 가시성은 public 임, ( 프로퍼티에 is를 붙일지 말지가 고민이네, Boolean으로 선언했을 경우 즉 person.isMarried 가 맞는지 person.married가 맞는지 naming convention을 찾아봐야 겠다)
커스텀 getter setter도 가능 (오버라이딩인가...)
// example of value object : 값 객체
// Java version
public class Person {
private final String name;
public Person(String inputName) {
this.name = inputName
}
public String getName() {
return this.name;
}
}
// Kotlin version
class Person(val name: String)
//
class Person(val name: String, var isMarried: Boolean)
// name 읽기 전용, isMarried 읽기쓰기
val person = Person("Bob", true)
person.getName() // 가능
person.name // 가능 내부적으로 getName으로 변환
person.isMarried = false // setMarried(false) 동일
class Rectangle(val height: Int, val width: Int) {
val isSquare: Boolean
get() {
return height == width
}
}
프로퍼티
자바에서는 필드와 접근자를 한데 묶어 프로퍼티 property 라고 함
val 프로퍼티 : 읽기 전용
var 프로퍼티 : 변경 가능
커스텀 접근자
val isSquare: Boolean
get() {
return height == width
}
get() = height == width
set(setvalue: Boolean) {
isSquare = setvalue
}
import class 및 import static method 가 import로 됨
star import하면 해당 패키지 안의 클래스, 최상위 함수 프로퍼티 모드 로딩됨
패키지 구조와 디렉터리 구조가 맞지 않아도 된다.하지만 자바의 규칙을 따르자. 나중에 자바로 변환히 문제가 생길수도 있다.
enum: enum은 class 앞에 있을 때는 특별한 의미를 지니지만 다른 곳에서는 이름에 사용할 수 있다. 소프트 키워드
반면 class는 키워드다 클래스를 표현하는 변수를 사용할때는 claszz나 aClass 같은 이름을 사용해야함.
break를 넣지 않아도 됨 (그럼 반대로 2개 가 같은 갑 매핑은? 아 좋네 되네 )
fun getMnemonic(color: Color) =
when (color) {
Color.RED -> "Richard"
Color.YELLOW -> "Of"
Color.BLUE -> "Battle"
}
getMnemonic(Color.BLUE)
fun getMnemonic(color : Color) =
when(color) {
Color.RED, Color.ORANGE -> "warm"
Color.GREEN, Color.BLUE -> "cold"
}
fun mix(c1: Color, c2: Color) =
when(setOf(c1, c2)) {
setOf(RED, YELLOW) -> ORANGE
setOf(BLUE, YELLOW) -> GREEN
else -> throw Exception("MisMatch color")
}
// 인자 없는 when
fun mixOptimized(c1: Color, c2: Color) =
when {
(c1 == RED && c2 == YELLOW) || (c1 == YELLOW && c2 == RED) -> ORANGE
(c1 == BLUE && c2 == YELLO) -> GREEN
else -> throw Exception("Notmatched color)
}
아래의 글은 드미트리 제메로프, 스베트라나 이사코바 저/오현석역,『Kotlin in Action』,에이콘출판사(2017)의 내용을 기반으로 작성하였습니다.
kotlin은 저자가 밝혔듯이 러시아 상트페테르부르크 근처의 섬 이름이다.
위치는 북유럽 서쪽이다.
코틀린 섬
코틀린
1. 개발언어, jetbrain에서 개발하였음
2. JVM 기반 언어
3. 자바코드 및 라이브러리와 interoperability
4. 자바가 사용되고 있는 모든 용도에 적합하면서도 더 간결하고 생산적이며 안전한 대체 언어를 제공하는 것
5. javascript로도 코틀린 컴파일 가능, 코틀린 코드를 브라우저나 노드에서 실행할 수 있다.
6. 정적타입 : 컴파일 타임에 데이터의 타입이 정해짐, 컴파일 때 오류를 검출 할 수 있음. ( 타입 선언 생략 가능, 타입 추론 엔진)ene
7. class, inteface, generics 지원
8. function type 지원
9. annotation 지원
10. 함수형 및 함수타입
코틀린 프로그래밍
1. 서버프로그래밍 : 웹 어플리케이션, api 어플리케이션, RPC 프로토콜 지원 (기존 자바 라이브러리 혹은 프레임워크 통합 가능)
2.안드로이드 프로그래밍 : 모바일 어플리케이션 개발,
참고로 코틀린 프로그래밍을 한다고 해서 서버 프로그래밍과 안드로이드로 앱 개발을 동시에 할 수 있을 거라는 착각은 하지 말자.
물론 같은 코드로 쓰여져 있으니 읽을수는 있지만 이해한다고는 볼수 없기 때문이다.
사용해야하는 라이브러리가 다르며, 그리고 서버 프로그래밍과 클라이언트 UI 프로그래밍은 라이프사이클 관리가 다르며 데이터에 접근법이 다르므로 서로 다른 분야의 전문가이다. 하지만 둘이서 만나서 같은 코틀린으로 되어 있다면 이전보다는 서로를 이해하기가 좀 더 쉬울 것이라는 긍정적인 측면이 있다고 생각한다.
예제
fun main(args: Array<String>) {
val personList = listOf(Person("Colin"), Person("Jina", 20))
val oldest = personList.maxBy { it.age ?: 0}
println("Hello, world! $oldest")
}
코틀린 특징
1. 코드가 짧아진다.
2. 연산자 오버로딩이 된다. (연산자 정의는 허용하지 않는다. cf. 스칼라는 연산자 정의가 가능하다.)
3. .kt라는 확장자 컴파일 하면 .class가 된다.
4. .class된 파일을 .kt로 디컴파일이 될까? (즉, .java > .class > .kt 가 가능할까? 좀 더 테스트 해봐야 겠지만 검색해봤을때는 가능한 것으로 보인다.)
5. 코틀린 대화형 쉘 : REPL ( ㅎ 이것도 요즘 언어의 유행일세 )
6. jave to kotlin 변환기 ( 자바코드 > 코틀린 변환 ㅎㅎㅎ 역시 다 있네 intellij code > convert java to kotlin , tool > kotlin > show kotlin bytecode > decompile )