Kotlin - start!
개요
https://kotlinlang.org/docs/command-line.html#install-the-compiler
java21
이 나오고 나서 java
문법도 많이 fluent
해졌지만, 대부분이 kotlin
에서 이미 제공하고 있던 기능인데다가, kotlin
만의 ‘이게 된다고’ 싶을 정도의 문법은 아직 제공되지 않는다.
또한 kotlin
만의 nullsafe
한 디자인 구조는 java
버전이 아무리 올라가도 따라할 수 없을것 같다.
kotlin
또한 컴파일 되면 java
바이트코드로 변환되어 JVM 위에서 동작되다 보니 kotlin 과 java 문법은 일대일 매핑되는 경우가 많다.
하지만 kotlin
에는 java
에 없는 문법들이 대거 등장하는데, 이런경우 새로운 java 문법이 생겨나는것이 아니기 때문에 해당 기능을 구현하기 위한 여러 라인의 java 코드가 등장하게 된다.
그런 면에서 kotlin
의 해괴한 문법이 낯설게 느껴질 수 도 있지만, 그로인해 향상되는 가독성을 생각해보면 충분히 java
에서 kotlin
으로 언어를 변경할 만 하다.
설치 및 실행
kotlin 설치 후 간단히 hello world 예제 출력
brew install kotlin
kotlin -version
Kotlin version 1.6.10-release-923 (JRE 11.0.10+8-jvmci-21.0-b06)
vi hello.kr
fun main() {
println("Hello, World!")
}
kotlinc-jvm hello.kt
kotlin HelloKt
Hello, World!
kotlinc-jvm
대신에 kotlinc
(Kotlin compiler) 사용 가능
kotlinc hello.kt -include-runtime -d hello.jar
java -jar hello.jar
Hello, World!
kotlinc
Welcome to Kotlin version 1.6.10 (JRE 11.0.10+8-jvmci-21.0-b06)
Type :help for help, :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 상에서 스크립트 형식으로 실행
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")
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
를 사용하여 동등 비교
data class Product(
val name: String,
val price: Double,
val onSale: Boolean = false
)
fun main() {
val p1 = Product("product1", 2000.0, true)
val p2 = p1.copy(price = 2000.0)
println(p1.equals(p2)) // true
println(p1 === p2) // false
}
삼항연산자
kotlin
에는 ?, :
를 사용한 삼항연산자가 없고 if, else
로 구성할 수 있다.
fun main() {
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
와 동일한 역할을 수행하는 연산자.
val anyString: Any = "test"
val anyInt: Any = 1
val isString = anyString is String
val isInt = anyInt is Int
println(isString) // true
println(isInt) // true
as, as?
연산자는 캐스팅 연산자
val other: Any = 100
val asString: String? = other as? String
println(asString?.reversed()) // null
그냥 as
를 사용하면 ClassCastException
발생 가능성이 있음으로 as?
사용을 권장한다.
사실 as, as?
모두 사용을 권장하지 않고 최대한 스마트 캐스트
사용을 권장한다.
when
switch
에 해당하는 연산자, 다른언어의 swtich
와 다르게 단순 코드블럭을 실행하는 것이 아닌 값을 반환한다(단 else
를 강요)
// 값 반환
fun getName(score: Int): String {
val result: String = when (score) {
0 -> "zero"
1 -> "one"
2, 3 -> "two or three"
else -> "unknown" // else 생략 불가
}
return result
}
in
키워드로 범위를 지정할 수 있다.
fun getName(score: Int): String {
val result: String = when (score) {
in 0..10 -> "zero"
in 10..90 -> "one"
in 90..100 -> "two or three"
else -> "unknown"
}
return result
}
또한 값을 반환하지 않는 명령문으로서 사용할 수 도 있다.
삼항연산자를 확장해 N항연사자 처럼 사용하고, 반복되는 if, else
문을 사용하지 않아도 된다.
// 값 반환 X,
fun checkName(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")
in 100..200 -> println("100 to 2000")
// else -> println("unknown") // else 생략해도 컴파일 가능
}
}
null 조건검사까지 null point 예외처리 없이 수행가능하다.
for
..
을 사용하는 Range
클래스와 자주 같이 사용된다.
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 in 1..10 step 2) {
println(i) // 13579
}
for (i in 10 downTo 1) {
println(i) // 10987654321
}
for (i in 1 until 10) { // 10 은 포함하지 않음
println(i) // 123456789
}
val, var 변수선언
val
은 변경할 수 없는 속성(immutable)
할당된 이후 값을 변경하게 되면 컴파일 에러가 발생, val 변수를 생성시 값을 할당하거나 타입을 명시 해줘야 한다.
java
에서는final
과 같다
val num1 = 42
val num2: Int = 45
val num3: Int
// val num4 에러 발생, 타입 지정 필수
// num1 = 1; 재정의 에러 발생
var
은 변경할 수 있는 속성(mutable)
초기화 후 값을 변경이 가능하다(일반적인 변수선언 방식)
모든상황에서 웬만하면 val
사용을 권장한다.
nullable(?) 변수
코틀린은 기본적으로 필드에 null
사용이 불가능한데 nullable(?)
키워드를 사용하면 필드에 null
값 지정이 가능하다.
java
의Optional
과 유사하다.
물론Optional
보다 세련되게 사용 가능하다.
class Person(
val first: String,
val middle: String?,
val last: String
)
String?
과 String
은 null
허용 여부만 결정하는 것 처럼 보이지만
컴파일시 아예 다른 기능의 변수처럼 동작한다.
// 컴파일 된 후 java 코드 변환
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
...
public final class Person {
@NotNull
private final String first;
@Nullable
private final String middle;
@NotNull
private final String last;
public Person(@NotNull String first, @Nullable String middle, @NotNull String last) {
...
}
...
}
안전호출(?.) 연산자
nullable
키워드를 사용하면 아래와 같이 내부 필드나 함수를 호출할 때 안전호출연산자(?.
) 를 체인형식으로 사용해야한다.
java
의Optional
체인 메서드들과 유사하다.
class Person(
val first: String,
val middle: String?,
val last: String
) {
fun printMiddleNameLength() {
val len: Int? = middle?.length
println(len) // 안전호출 연산자
}
}
fun main() {
val p1 = Person("ko", null, "e")
p1.printMiddleNameLength(); // null
}
컴파일 단계에서 nullpoint 발생 가능한 경우를 원천 차단한다.
스마트 캐스트
스마트 캐스트
는 컴파일 단계에서 nullpoint 이슈가 해결되었다면 더이상 컴파일 에러를 발생시키지 않는다.
예를 들어 아래와 같이 if
문을 통해 해당 변수가 null
이 아님을 확인한다면 if
블럭 내부에선 ?
가 없이 사용 가능하다.
class Person(
val first: String,
val middle: String?,
val last: String
) {
fun printMiddleNameLength() {
if (middle != null) {
val temp: String = middle // smart cast
println(middle.length) // null 출력
}
}
}
fun main() {
val p1 = Person("ko", null, "e")
p1.printMiddleNameLength();
}
p1.middle
이 null
이 아님을 확신할수 있기에 String?
이 아닌 String
타입으로 인식한다.
하지만 val
이 아닌 var
로 선언할경우 이런 스마트 캐스트
가 동작하지 않는다.
언제든 null
로 다시 재정의될 수 있다고 생각하기에 스마트 캐스트
가 동작하지 않는다.
클래스 타입을 확인하는 is
연산자도 스마트 캐스트
할 수 있다.
val other: Any = "is String"
// println(other.reversed()) compile error
if (other is String)
println(other.reversed())
스마트 캐스트
는 when
연산자 사용시에도 적용된다.
단언 연산자(!!)
아래와 같이 var
로 정의된이상 스마트 캐스트
가 동작하지 않으며 String?
타입에서 벗어날 수 없다.
class Person(
var first: String,
var middle: String?,
var last: String
)
하지만 단언연산자(!!
) 를 사용하면 강제로 String
타입으로 변경가능하다.
대신 NullPointException
이 발생할 수 있다.
class Person(
var first: String,
var middle: String?,
var last: String
) {
fun printMiddleNameLength() {
println(middle!!.length) // KotlinNullPointerException 예외 발생
}
}
fun main() {
val p1 = Person("ko", null, "e")
p1.printMiddleNameLength();
}
단언 연산자 사용을 권장하지 않는다.
처음부터 val
로 변수로 정의하거나 var
로 정의하고 안전호출 연산자(?.
) 를 통해 값을 가져오는것을 권장한다.
엘비스 연산자(?:
)
엘비스 연산자(?:
) 역시 nullable
변수를 다루기 위한 연산자.
null 체크하는 삼항연산자와 같은 기능
class Person(
var first: String,
var middle: String?,
var last: String
) {
fun printMiddleNameLength() {
println(middle?.length ?: 0) // null 이라면 0 반환
}
}
fun main() {
val p1 = Person("ko", null, "e")
p1.printMiddleNameLength(); // 0
}
middle?.length
은 Int?
를 반환하고 null
이 아니라면 정상적으로 length
를 반환
null
로 반환되었다면 엘비스 연산자(?:
)를 통해 최종적으로 0
을 반환한다.
?.let
not null
인 경우동작하는 코드블럭를 정의한다 보면 된다.
val email : String? ="test@test.com"
email?.let {
println(it) // test@test.com
}
?.let
키워드로 null
이 아닐경우만 let
블록을 실행할 수 있도록 설정
내부에선 it
키워드를 사용함
타입변환
java
와 다르게 Int -> Long
자동승격하지 않는다
toLong
, toInt
함수를 사용해야 한다.
fun main() {
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)
가 있음
fun main() {
val i: Int = 10
println(i.toString(2)) // 1010
}
and
, or
, xor
, inv(not)
비트 연산자를 제공한다.
fun main() {
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
문자열 연산
$, ${}
문자열 템플릿을 사용해 문자열 사이에 변수나 표현식을 삽입할 수 있다.
만약 $
를 이스케이프 문자로 사용하고 싶으면 백슬레시나 변수명으로 사용할 수 없는 특수문자를 이어서 사용하면 된다.
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...
typealias
kotlin 1.3
버전에 추가
복잡한 타입을 별칭을 통해 간단하게 표기할 수 있다.
typealias StringDelegate = ReadOnlyProperty<Any?, String>
class CustomDelegate : StringDelegate {
override fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "Delegate Value for ${property.name}"
}
}
기타기능
버전출력
버전 출력하기
fun main(args: Array<String>) {
println("The current Kotlin version is ${KotlinVersion.CURRENT}")
// The current Kotlin version is 1.3.50
}
repeat
kotlin
내장 repeat
함수
fun main(args: Array<String>) {
var repeatFunction: (Int) -> (Unit) = {
println("Countung:$it")
}
repeat(5, repeatFunction);
/*
Countung:0
Countung:1
Countung:2
Countung:3
Countung:4
*/
}
TODO
원래는 주석을 통해 TODO
를 작성했지만 kotlin
에선 TODO
함수를 지원하기에 오륲를 강제 발생시킬 수 있다.
fun myCleverFunction() {
// TODO: 멋진 구현을 찾는 중
}
fun completeThis() {
TODO() // throw NotImplementedError
}
Random
fun main() {
println(Random.nextInt()) //in range
println(Random.nextInt(100)) //0 ~ 99
println(Random.nextInt(1, 10)) // 1 ~ 9
}
이외에도 여러가지 타입의 Random 함수 제공
println(Random.nextInt())
println(Random.nextLong())
println(Random.nextBoolean())
println(Random.nextDouble())
println(Random.nextFloat())
시드값 전달이 가능하며 반복가능한 난수생성기를 만들 수 있음
val seed: Int = 10
println(Random(seed).nextInt()) // // -129340023
println(Random(10).nextInt()) // -129340023
java 호환
java
호환을 위한 @JvmOverloads
어노테이션에 대해 알아보면
class Person(
val first: String,
val middle: String?,
val last: String
) {
@JvmOverloads
fun getDesc(first: String, age: Int = 0, desc: String? = null) =
"first name $first, desc ${desc ?: "None"}, and age " + NumberFormat.getCurrencyInstance().format(age)
}
fun main() {
val p1 = Person("hello", null, "world")
p1.getDesc("test");
p1.getDesc("test", 10);
p1.getDesc("test", 10, "world");
}
@JvmOverloads
어노테이션을 추가하면 컴파일 과정에서 java
에서도 kotlin
처럼 사용할 수 있도록 함수가 자동 셍성됨
// java code
public static void main(String[] args) {
Person p = new Person("hello", null, "world");
System.out.println(p.getDesc("hello"));
System.out.println(p.getDesc("hello", 5));
System.out.println(p.getDesc("hello", 5, "world"));
}
예외처리
kotlin
의 예외는 모두 unchecked
예외이다.
try
, catch
, finally
블록이 있긴하지만 강제하지 않는다.
fun throwIoError() {
throw IOException("File or resource not found")
}
java
의 경우 IOException
의 경우 try
, catch
를 요구하지만
kotlin
에선 별도의 처리없이 컴파일 가능하다.
하지만 java
코드에서 kotlin
의 함수를 불러올 때 try
, catch
를 사용해 처리하고싶어도 에러가 발생한다.
// java code
public static void main(String[] args) {
try {
useThrowsClause();
} catch (IOException e) { // 예외 'java.io.IOException'은(는) 해당 try 블록에서 한 번도 던져지지 않습니다
System.out.println("error invoked");
e.printStackTrace();
}
}
java
코드에 예외가 발생함을 알리는 어노테이션을 추가하여 호환시킬 수 있다.
@Throws(IOException::class)
fun throwIoError() {
throw IOException("File or resource not found")
}