java21 이 나오고 나서 java 문법도 많이 fluent 해졌지만, 대부분이 kotlin 에서 이미 제공하고 있던 기능인데다가, kotlin 만의 ‘이게 된다고’ 싶을 정도의 문법은 아직 제공되지 않는다.
또한 kotlin 만의 nullsafe 한 디자인 구조는 java 버전이 아무리 올라가도 따라할 수 없을것 같다.
kotlin 또한 컴파일 되면 java 바이트코드로 변환되어 JVM 위에서 동작되다 보니 kotlin 과 java 문법은 일대일 매핑되는 경우가 많다. 하지만 kotlin 에는 java 에 없는 문법들이 대거 등장하는데, 이런경우 새로운 java 문법이 생겨나는것이 아니기 때문에 해당 기능을 구현하기 위한 여러 라인의 java 코드가 등장하게 된다.
그런 면에서 kotlin 의 해괴한 문법이 낯설게 느껴질 수 도 있지만, 그로인해 향상되는 가독성을 생각해보면 충분히 java 에서 kotlin 으로 언어를 변경할 만 하다.
설치 및 실행
kotlin 설치 후 간단히 hello world 예제 출력
1 2 3
brew install kotlin kotlin -version Kotlin version 1.6.10-release-923 (JRE 11.0.10+8-jvmci-21.0-b06)
Welcome to Kotlin version 1.6.10 (JRE 11.0.10+8-jvmci-21.0-b06) Type :helpforhelp, :quit for quit >>> println("hello world") hello world >>> var name = "test" >>> println("hello $name") hello test >>> :help Available commands: :help show this help :quit exit the interpreter :dump bytecode dump classes to terminal :load <file> load script from specified file >>> :quit
kotlin 스크립트
아래처럼 main 이 없는 코드를 나열하고 JVM 상에서 스크립트 형식으로 실행
1 2 3 4 5 6 7
import java.time.*
val instant = Instant.now() val southPole = instant.atZone(ZoneId.of("Antarctica/South_Pole")) val dst = southPole.zone.rules.isDaylightSavings(instant) println("It is ${southPole.toLocalTime()} (UTC${southPole.offset}) at the south Pole") println("The south Pole ${if (dst) "is" else "is not"} on Daylight Savings Time")
1 2 3 4
kotlinc -script southpole.kts
It is 20:50:43.486781 (UTC+13:00) at the south Pole The south Pole is on Daylight Savings Time
간단하게 함수형 코드를 테스트하기 좋다.
연산자
동등 연산자(===)
=== 연산자는 참조가 같은지를 확인한다.
equals, hasCode, toString, copy(얕은복사) 가 미리 재정의되는 data class 를 사용하여 동등 비교
1 2 3 4 5 6 7 8 9 10 11 12
dataclassProduct( val name: String, val price: Double, val onSale: Boolean = false )
kotlin 에는 ?, : 를 사용한 삼항연산자가 없고 if, else 로 구성할 수 있다.
1 2 3 4 5 6 7 8 9 10 11
funmain() { val num = 11 val result1 = if (num > 10) "it's true"else"it's false" val result2 = if (num > 10) { 5 + 5 } else { 100 + 100 } println(result1) // it's true println(result2) // 10 }
is, !is, as, as?
is, !is 연산자는 java 의 instanceof 와 동일한 역할을 수행하는 연산자.
1 2 3 4 5 6
val anyString: Any = "test" val anyInt: Any = 1 val isString = anyString is String val isInt = anyInt isInt println(isString) // true println(isInt) // true
as, as? 연산자는 캐스팅 연산자
1 2 3 4
val other: Any = 100
val asString: String? = other as? String println(asString?.reversed()) // null
그냥 as 를 사용하면 ClassCastException 발생 가능성이 있음으로 as? 사용을 권장한다. 사실 as, as? 모두 사용을 권장하지 않고 최대한 스마트 캐스트 사용을 권장한다.
when
switch 에 해당하는 연산자, 다른언어의 swtich 와 다르게 단순 코드블럭을 실행하는 것이 아닌 값을 반환한다(단 else 를 강요)
1 2 3 4 5 6 7 8 9 10
// 값 반환 fungetName(score: Int): String { val result: String = when (score) { 0 -> "zero" 1 -> "one" 2, 3 -> "two or three" else -> "unknown"// else 생략 불가 } return result }
in 키워드로 범위를 지정할 수 있다.
1 2 3 4 5 6 7 8 9
fungetName(score: Int): String { val result: String = when (score) { in0..10 -> "zero" in10..90 -> "one" in90..100 -> "two or three" else -> "unknown" } return result }
또한 값을 반환하지 않는 명령문으로서 사용할 수 도 있다. 삼항연산자를 확장해 N항연사자 처럼 사용하고, 반복되는 if, else 문을 사용하지 않아도 된다.
1 2 3 4 5 6 7 8 9 10 11 12 13
// 값 반환 X, funcheckName(score: Any?) { when (score) { null -> println("null") 0 -> println("zero") 1 -> println("one") 2, 3 -> println("two or three") "zero" -> println("zero string") in listOf("0", "1", "2") -> println("string number 1, 2, 3") in100..200 -> println("100 to 2000") // else -> println("unknown") // else 생략해도 컴파일 가능 } }
null 조건검사까지 null point 예외처리 없이 수행가능하다.
for
.. 을 사용하는 Range 클래스와 자주 같이 사용된다.
1 2
val oneToFive: IntRange = 1..5 val aToE: CharRange = 'a'..'e'
val list = listOf("a", "b", "c", "d") for ((index, name) in list.withIndex()) { println("${index}:${name}") // 0:a // 1:b // 2:c // 3:d }
val array = arrayOf(1, 2, 3) for (i in array) { print(i) // 123 } for (i in1..10 step 2) { println(i) // 13579 } for (i in10 downTo 1) { println(i) // 10987654321 } for (i in1 until 10) { // 10 은 포함하지 않음 println(i) // 123456789 }
val, var 변수선언
val은 변경할 수 없는 속성(immutable) 할당된 이후 값을 변경하게 되면 컴파일 에러가 발생, val 변수를 생성시 값을 할당하거나 타입을 명시 해줘야 한다.
java 에서는 final과 같다
1 2 3 4 5
val num1 = 42 val num2: Int = 45 val num3: Int // val num4 에러 발생, 타입 지정 필수 // num1 = 1; 재정의 에러 발생
var은 변경할 수 있는 속성(mutable) 초기화 후 값을 변경이 가능하다(일반적인 변수선언 방식)
모든상황에서 웬만하면 val 사용을 권장한다.
nullable(?) 변수
코틀린은 기본적으로 필드에 null 사용이 불가능한데 nullable(?) 키워드를 사용하면 필드에 null 값 지정이 가능하다.
java 의 Optional 과 유사하다. 물론 Optional 보다 세련되게 사용 가능하다.
1 2 3 4 5
classPerson( val first: String, val middle: String?, val last: String )
String? 과 String 은 null 허용 여부만 결정하는 것 처럼 보이지만 컴파일시 아예 다른 기능의 변수처럼 동작한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
// 컴파일 된 후 java 코드 변환 import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; ...
middle?.length 은 Int? 를 반환하고 null 이 아니라면 정상적으로 length 를 반환
null 로 반환되었다면 엘비스 연산자(?:)를 통해 최종적으로 0을 반환한다.
타입변환
java 와 다르게 Int -> Long 자동승격하지 않는다 toLong, toInt 함수를 사용해야 한다.
1 2 3 4 5
funmain() { val i: Int = 3 // val l: Long = i // error 발생 val l: Long = i.toLong(); }
아래와 같은 타입변환 함수 지원
toByte
toChar
toShort
toInt
toLong
toFloat
toDouble
비트연산
Byte, Short, Int, Long 에 한하여 진법출력 함수 toString(radix: Int) 가 있음
1 2 3 4
funmain() { val i: Int = 10 println(i.toString(2)) // 1010 }
and, or, xor, inv(not) 비트 연산자를 제공한다.
1 2 3 4 5 6 7 8
funmain() { var b1: Int = 0b0001_1111 var b2: Int = 0b0001_0000 println(b1 and b2) // 16 println(b1 or b2) // 31 println(b1 xor b2) // 15 println(b2.inv()) // -17 }
시프트연산으로 아래 3가지 제공
shl: signed left shift
shr: signed right shift
ushr: unsigned left shift
문자열 연산
$, ${} 문자열 템플릿을 사용해 문자열 사이에 변수나 표현식을 삽입할 수 있다. 만약 $ 를 이스케이프 문자로 사용하고 싶으면 백슬레시나 변수명으로 사용할 수 없는 특수문자를 이어서 사용하면 된다.
1 2 3 4 5 6
val price = 12.25 val taxRate = 0.08 // The amount 12.25 after tax comes to $13.23 val output = "The amount $price after tax comes to $${price * (1 + taxRate)}" // The amount is in US$, that's right in $only val disclaimer = "The amount is in US$, that's right in \$only"
쌍따옴표를 연속으로 3개 사용하면 멀티라인 문자열 생성이 가능하다. 만약 block 구문안에서 멀티라인 문자열을 정의할 경우 auto indent 로 로 인해 앞에 margin 이 발생하는데, 파이프 특수문자와 trimMargin 을 통해 제거 가능하다.
if (true) { val memo = """Test1, a quick reminder about the party we have scheduled next Tuesday at the 'Low Ceremony Cafe' at Noon. | Please plan to...""" .trimIndent() println(memo) } if (true) { val memo = """Test2, a quick reminder about the |party we have scheduled next Tuesday at |the 'Low Ceremony Cafe' at Noon. | Please plan to...""" .trimMargin() println(memo) }
// Test1, a quick reminder about the // party we have scheduled next Tuesday at // the 'Low Ceremony Cafe' at Noon. | Please plan to... // Test2, a quick reminder about the // party we have scheduled next Tuesday at // the 'Low Ceremony Cafe' at Noon. | Please plan to...