[include(틀:상위 문서, top1=Kotlin)] [include(틀:프로그래밍 언어 문법)] [목차] [[분류:프로그래밍 언어 문법]] == 개요 == [[Kotlin]](코틀린)의 문법을 설명하는 문서다. == 기본 == 코틀린의 코드는 객체 지향을 원칙으로 하며, [[Java|자바]]와 '''100% 연계되는 문법'''을 사용하고 있다.[* 실제로 [[IntelliJ IDEA]]나 [[안드로이드 스튜디오]] 등에서 자바 코드를 넣으면 자동으로 코틀린으로 변환해 준다.] .kt 또는 .kts의 저장 형식을 가진다. 위에서도 서술 했듯이 자바와 굉장히 비슷한 문법 구조를 가지고 있어 자바를 한번 배워 본 사람이라면 코틀린을 어렵지 않게 배울 수 있다. 또한 코드가 '''매우 간결하여''' 쉽게 배울 수 있다. 그럼에도 불구하고 [[안드로이드(운영체제)|안드로이드]] 앱을 만들 때도 기능상의 제한이 없다. 함수 선언 방법 또한 자바와 동일하다. == 편집 지침 == 소스 코드로 예시를 들 때 ||\{{{#!syntax kotlin (소스코드) }}}|| 문법을 활용하여 소스코드를 써 주시기 바랍니다. 또한 메인함수에는 매개변수가 필요한 경우 통일을 위해 '''array 대신 args를 사용하시기 바랍니다.''' 예시: {{{#!syntax kotlin package HelloWorld; fun main(args: Array) { println("Hello world!") } }}} == 메인 메서드 == {{{#!syntax kotlin package HelloWorld; fun main() { // TODO } }}} 코틀린의 경우에는 메인메서드의 길이가 상당히 짧다. [[C언어]], [[Java|자바]]등과 비교하면 짧다는거지 메인함수가 없는 [[Python|파이썬]]과 비교하면 곤란하다. 당장 패키지명만 선언하고 메인함수를 작성한 뒤 바로 코딩에 들어가도 무방하다. {{{#!syntax kotlin package HelloWorld; fun main(args: Array) { // TODO } }}} [[Kotlin]] 1.3 버전부터는 args를 붙일 필요는 없게 되었으나 매개변수가 필요한 경우 '''args''' 나 '''array'''를 붙이며 둘 다 사용법은 같다. 똑같이 작동하지만 JDK 환경인지, Native로 작동하는지 등에 따라서 다르다. == 타입 == * 정수: Long/ULong[* 8바이트 부호/무부호 정수] > Int/UInt[* 4바이트 부호/무부호 정수] > Short/UShort[* 2바이트 부호/무부호 정수] > Byte/UByte[* 1바이트 부호/무부호 정수] * 실수: Double[* IEEE 754 배정밀도 부동소수점] > Float[* IEEE 754 단정밀도 부동소수점] * 문자: Char * 문자열: String == 변수 선언 == {{{#!syntax kotlin package HelloWorld; fun main() { var a1: Int = 1 var a2 = 1 var b: String = "1" val c: Double = 3.141592 println(a1) // OK println(b) // OK println(c) // OK a1 = a1 ++ // OK b = b + "2" // OK c = c ++ // ERROR } }}} 타입을 적어줘도, 적어주지 않아도 된다. var이 아니라 val로 쓰게 된다면 c = c ++과 같이 그 값을 바꾸지 못한다. 기본적으로는 val을 쓰는게 좋은 습관이며 권장된다. 하지만 자바에서 final을 잘 쓰지 않듯이 어차피 대부분 var로 바꿀거 코틀린에서도 var로 코딩 하는 사람도 적지 않다. 다만 val을 블록 안에 쓰면 블록 범위 안에서만 동작하므로 run {} 같은 블록 안에 val을 써서 val의 동작 범위를 정해줄 수도 있다. === 특이한 경우: null check === {{{#!syntax java package HelloWorld; fun main() { var a: Int? = 1 var b: Int? = 1 println(a !! + b !!) var c: Int? = null // OK var d: Int = null // ERROR } }}} 기본적으로 ?를 타입 뒤에 붙이면 null을 사용할 수 있다. 하지만 둘 다 null 선언이 된 상태에서 값을 수정하거나 출력하려 하면 null check 에러가 뜨는 경우가 있다. 이런 경우에는 !!를 붙여주면 해결되는 경우도 있다. === 특이한 경우: 형변환 하기 === {{{#!syntax kotlin package HelloWorld; fun main() { val a: Int = 1 val b: String = "1" println(a + b) // ERROR println(a + b.toInt()) // OK val c: Int = 1 val d: String = "1" println(c + d!!.toInt()) // OK } }}} String과 Int를 더하려면 오류가 나기 때문에 '''.to타입()'''을 붙여 타입을 변경할 수 있다. null check를 하는 경우 '''d!!.toInt()'''로 해주면 된다. == 함수 == {{{#!syntax java package HelloWorld; fun main() { // TODO var Method: Method = Method() Method.Method1() // OK Method.Method2() // OK } class Method() { fun Method1() { println("Hello") } open fun Method2() { println("Hello") } } }}} 함수에 대한 기본개념이 있다면 어떻게 사용하는지 문법만 배우면 된다. 계속 Method라는 얘기가 나오는데 '''클래스의 이름을 Method로 할 필요는 없다.''' === 확장 함수 === 이미 선언되어있는 객체나 클래스 하위의 함수를 재정의하거나 새로 정의할 수도 있다. {{{#!syntax java package HelloWorld fun String.sayHello() { println("Hello, $this") // this는 객체 String을 가리킴 } fun main() { "NamuWiki".sayHello() // "Hello, NamuWiki" 출력 } }}} === infix 함수 === infix 키워드를 이용해서 .과 ()를 쓰지 않아도 되는 함수를 만들 수 있다. infix 함수의 조건으로는 * 확장 함수 또는 클래스 함수여야 한다 * 매개 변수 1개여야 한다 * 매개 변수는 기본 값이 없으면서 vararg 매개변수가 없어야 한다 {{{#!syntax java package HelloWorld; import java.net.* class Human(var name: String, var age: Int, var location: String) { fun travel(location: String) { this.location = location } override fun toString(): String { return "${name}, ${age}세, ${location} 거주" } infix fun eat(food: String) = println("${this.name}님이 ${food}를 먹었습니다.") } fun main() { Human("홍길동", 30, "서울") eat "피자" // 홍길동님이 피자를 먹었습니다. Human("홍길동", 30, "서울") browse URL("https://namu.wiki/") // 홍길동님이 https://namu.wiki/를 검색했습니다. } infix fun Human.browse(url: URL) = println("${this.name}님이 ${url}를 검색했습니다.") }}} == 스코프 함수 == === let === '''let이 있는 버전''' {{{#!syntax java package HelloWorld; class Human(var name: String, var age: Int, var location: String) { fun travel(location: String) { this.location = location } override fun toString(): String { return "${name}, ${age}세, ${location} 거주" } } fun main() { Human("홍길동", 30, "서울").let { println(it) it.travel("부산") println(it) } } }}} '''let이 없는 버전''' {{{#!syntax java package HelloWorld; class Human(var name: String, var age: Int, var location: String) { fun travel(location: String) { this.location = location } override fun toString(): String { return "${name}, ${age}세, ${location} 거주" } } fun main() { val human = Human("홍길동", 30, "서울") println(human) human.travel("부산") println(human) } }}} '''let을 사용하는 경우''' * null이 가능한 오브젝트가 null이 아닐 때 코드를 실행하게 할 때 {{{#!folding [ 예시 ] {{{#!syntax java package HelloWorld; val humans = ArrayList() class Human(var name: String, var age: Int, var location: String) { fun travel(location: String) { this.location = location } override fun toString(): String { return "${name}, ${age}세, ${location} 거주" } } fun main() { Human("홍길동", 30, "서울").let { humans.add(it) } getHuman("홍길동")?.let { println(it.name) // 만약에 getHuman("홍길동")의 값이 null이라면 람다식을 실행되지 않는다. } } fun getHuman(name: String): Human? { return humans.firstOrNull { it.name == name } } }}} }}} * 특정 변수를 제한적인 블록에서만 접근하게 만들 때 === run === === also === === with === === apply === == 입력 받기 == === readLine === {{{#!syntax java package HelloWorld fun main() { println("이름을 입력하세요") var name: String? = readLine() println("${name}님 안녕하세요!") } }}} 한 줄을 입력받는다. readln()으로도 가능하다. === Scanner === {{{#!syntax java package HelloWorld import java.util.* fun main() { val scanner = Scanner(System.`in`) println("이름을 입력하세요") var name: String = scanner.nextLine() println("나이를 입력하세요") var age: Int = scanner.nextInt() println("이름 : ${name}, 나이 : ${age}") } }}} [[Java|자바]]와 다른점이 System.`in`인데, Kotlin에서 in은 예약어이기 [[Java|자바]]에서 쓰는 in은 ``으로 감싸서 사용한다. next타입()으로 다른 타입들도 사용할 수 있다. == 출력하기 == {{{#!syntax java package HelloWorld; fun main() { print("안녕") // 1 println("하세요") // 2 var Hello: String = "안녕하세요" println(Hello) // 3 var World1 : String = "안녕" var World2 : String = "하세요" println("${World1 + World2}") println(World1 + World2) // 4 } }}} 네가지 경우 모두 '''안녕하세요'''가 출력된다. print인 경우는 줄바꿈을 하지 않는다. C언어를 예로 들자면 ''' printf("hello world!\\n");''' 에서 \\n이 생략된 격이다. 이 말은 즉슨 println의 경우에는 \\n이 자동으로 삽입되어 있다는 뜻이다. 물론 \\n 사용이 불가능 한 것은 아니다. 다만 println이 쓰이는데 \\n까지 같이 쓴다면 두줄 줄 바꿈이 된다. == 배열 == === Array === Array를 만들 때는 이와 같이 하면 된다 {{{#!syntax java package HelloWorld; fun main() { val doubleArray: Array = arrayOf(1.0, 1.5, 2.0, 3.0) } }}} 위의 경우 arrayOf 안에 있는 값을 가져가서 Array가 그 값들의 type을 가지게 된다. === List === == 반복문 == === for문 === {{{#!syntax java package HelloWorld fun main () { for (i in 0 .. 5) { println("안녕하세요") } for (i in 1 .. 6) { println("안녕하세요") } for (i in 1 until break) { println("안녕하세요") } } }}} 첫번째와 두번째 경우 둘 다 안녕하세요가 6번 출력된다. 반쯤 람다식이라고 볼 수 있는데, 기존 [[Java]]와 절차지향 언어인 [[C언어]]에서 쓰였던 var i = 0; i < 10; i ++ 같은 문법 대신 눈물나게 간결한 문법을 제공한다. 세번째 경우인 until의 경우에는 저 자체로는 굉장히 불완전한 코드이다. until을 이용해서 break까지, 변수 a의 값이 5가 될때 까지등 여러가지 조건을 내걸어 반복문을 사용 할 수 있다. 이중 반복문을 사용하려는 경우 첫번째 반복문에 변수로 i를 지정해주었다면 두번째 반복문에는 i를 사용하면 안된다. 코드 안에서 내부적으로 i를 여러번 반복하고 그 반복 안에 새로운 i가 있는 형식이기 때문이다. 여러 IDE에서 실행을 해보면 빨간색 밑줄 또는 노란색 밑줄이 그어지면서 this variable is already defined같은 오류가 뜬다. === while문 === == 제어문 == === if[else] === {{{if (expression) statement1 [else statement2]}}} () 속의 조건식(expression)이 참이 되면 statement1을, 거짓이면 statement2를 실행하는 [[구조]]로 되어 있다. else 이하는 생략 가능하며 else 뒤에 if를 다시 사용하여 if ... else if ... else if ... else 와 같이 사용할 수도 있다. {{{#!syntax java package HelloWorld; fun main() { var a = 1 var b = 1 if(a == b) { // TODO } else { // TODO } } }}} === when === [[Java|자바]]의 [[C언어/문법#switch ... case ...|switch 문]]이랑 비슷하지만 더 많은 [[기능]]을 가졌다. {{{#!syntax java package HelloWorld; fun main() { var a = 1 when(a) { 1 -> println("a는 1입니다") 5 -> println("a는 5입니다") 7, 9 -> println("a는 7 아니면 9입니다") in 10..100 -> println("a는 10에서 100까지 중에 있습니다") else -> println("a는 그 외입니다") } // "a는 1입니다" 가 출력됨. } }}} 또한 when 자체를 값으로 사용할 수 있다. 밑에 있는 코드는 위랑 같은 결과를 보여준다. {{{#!syntax java package HelloWorld; fun main() { var a = 1 println(when(a) { 1 -> "a는 1입니다" 5 -> "a는 5입니다" 7, 9 -> "a는 7 아니면 9입니다" in 10..100 -> "a는 10에서 100까지 중에 있습니다" else -> "a는 그 외입니다" }) } }}} == 상속 == 한 객체가 다른 객체를 상속할 때에는 다음과 같은 형태를 띈다. {{{#!syntax java package kotlin open class ParentClass() class ChildClass: ParentClass() }}} 코틀린은 다른 프로그래밍 언어나 자바와 달리 더 이상 상속하지 못하는 final이 클래스의 기본값이다 따라서 이 클래스를 상속가능하게 하고싶다면 open[* 선택적 상속]이나 abstract[* 무조건 상속]를 붙여서 다른 클래스가 해당 클래스를 상속할 수 있도록 해주어야 한다 한 객체가 인터페이스를 구현할 때에는 다음과 같은 형태를 띈다. {{{#!syntax java package kotlin interface SampleInterface() class ChildClass: SampleInterface }}} == 람다식 == === 기본 === 다음과 같이 활용한다. * 함수형 {{{#!syntax kotlin package Kotlin fun main(){ fun fn(a:Int, b:Int):Int {return a+b} println(fn(1,2)) } }}} * 람다식 {{{#!syntax kotlin package Kotlin fun main(){ val fn = {a:Int, b:Int -> a+b} println(fn(1,2)) } }}} === 고차함수 === 코틀린은 인터페이스를 통해서 함수를 인자로 넘겨주던 Java와 다르게 아주 간편하게 함수를 주고받거나 반환할 수 있다. * 함수를 인자로 받기 {{{#!syntax kotlin package Kotlin fun printer(a: Int, b: Int, f: (Int, Int) -> Int) { println("$a 와 $b 를 함수에 -> ${f(a, b)}") } }}} * 함수를 반환하기 {{{#!syntax java package Kotlin fun getPrintingFunction(str: String): () -> Unit { return { println(str) } } }}} 또한, 함수(람다식)을 인자로 넘겨주는 함수를 사용할 경우, 함수의 내용을 소괄호 밖에 표시하는것이 권장된다. {{{#!syntax java printer(3, 5, {i, j -> i + j}) // 작동 printer(3, 5) {i, j -> i + j } // 작동 }}} 이와 앞서 언급한 클래스 내 함수 삽입을 이용하면 context를 만들 수 있다. {{{#!syntax java package HelloWorld; object NamuWiki { fun getDocument(name: String): String { // TODO } } fun namuWiki(lambda: NamuWiki.() -> T) : T { // lambda는 Namuwiki 하위 함수 return NamuWiki.lambda() } fun main() { val kotlin: String = namuWiki { // returns result of getDocuent() getDocument("Kotlin/문법") // getDocument under context namuWiki } getDocument("Kotlin/문법") // ERROR: function getDocument doesn't exist println(kotlin) } }}}