본문 바로가기

3.구현/Java or Kotlin

Kotlin 배우기1 - 기본

들어가기

Kotlin을 공부하면서 간단하게 정리해보았다. 기본에서 시작해서 나름대로 심화까지 다룰려고 한다.
프로그래밍 언어에 어느정도 익숙한 개발자를 위한 내용으로 Java을 사용했다면 이해하기 더 쉬울거라 생각한다.
가급적 자잘한 설명은 생략하고 압축해서 정리했다.

작성자: ospace114@empal.com, http://ospace.tistory.com/

HelloWorld

시작은 HelloWorld이다.

fun main() {
    println("Hello world!")
}

코드에서 알 수 있듯이 main()이 있어야 실행된다. 그리고 println()을 통해서 문자열을 출력되고 있음을 알 수 있다.
구문 마지막에는 세미콜론(;)은 없다. 물론 사용할 수도 있지만 없어도 문제가 없기에 생략해서 사용했다.

변수와 상수

변수

"var 변수명:자료형" 형태로 변수를 정의한다.

fun main() {
    var str:String
    str = "Hello world!"
    println("str[$str]")
}

자료형을 지정한 변수를 선언해서 값을 할당해서 쓸 수 있다. 참고로 println()에서 문자열 포멧팅도 가능하다. 또한 포매팅에서 중괄호를 사용할 수 있다.(예. ${str})

만약 변수를 초기화하는 경우 자료형 추론에 의해 자료형 생략해서 변수 선언할 수 있다.

fun main() {
    var str = "Hello world!"
    println("str[${str}]")
}

기본 변수는 non-null 타입으로 null 할당시 에러가 발생한다.
null을 할당하려면 물음표(?) 기호를 사용해서 nullabe하게 선언해야한다.

var str1:String = null // 컴파일 에러
var str2:String? = null // nullable 지정

상수

상수는 "val 변수명:자료형"으로 정의할 수 있다.

val str = "Hello world!"
str = "Hello!" // Val cannot be reassigned

자료형

제공되는 기본 자료형은 다음과 같다.

  • Byte, UByte: 1바이트 (-2^7 ~ 2^7-1, 0 ~ 2^8-1)
  • Short, UShort: 2바이트 (-2^15 ~ 2^15-1, 0 ~ 2^16-1)
  • Int, UInt: 4바이트 (-2^31 ~ 2^31-1, 0 ~ 2^32-1)
  • Long, ULong: 8바이트 (-2^63 ~ 2^63-1, 0 ~ 2^64-1)
  • Float: 4바이트
  • Double: 8바이트
  • Boolean
  • Char: 1바이트, 2바이트(Unicode)

초기화 값의 타입을 추론해서 변수 타입 결정한다.

val one = 1 // Int
val onePositive = 1u // UInt
val threeBillion = 3000000000 // Long
val oneLone = 1L // Long
val oneLonePositive = 1uL // ULong
val pi = 3.14 // Double
val piFloat = 3.14f // Float
val oneMillion = 1_000_000 // undersocre more readable

조건문

조건문에는 if와 when이 있다.

if

먼저 if문 사용예이다.

fun main() {
    var str:String? = "";

    if (null == str) {
        println("str is null.")
    } else if (str.isEmpty()) {
        println("str is empty.")
    } else {
        println("str is $str.")
    }
}

if 문을 조건 할당하는 형태로 사용할 수 있다.

fun main() {
    var str:String? = null;

    var msg = if (null == str) {
        "str is null."
    } else if (str.isEmpty()) {
        "str is empty."
    } else {
        "str is $str."
    }
    println(msg)
}

var res = if (str == "foo") {
    "It is Foo."
} else if (str == null) {
    "It is Bar."
} else {
    "Unknown."
}

when

if문의 조건 할당을 when 사용해서 처리할 수 있다. when은 switch라고 보면 된다.

fun main() {
    val num = 1

    when(num) {
      1 -> println("one")
      2,3,5,7,11 -> println("prime")
      in 10..19 -> println("teen")
      else -> println("normal")
    }

    var str:String? = "";

    var msg = when  {
        null == str -> "str is null."
        str.isEmpty() -> "str is empty."
        else -> "str is $str."
    }
    println(msg)
}

반복문

반복문에는 while과 for가 있다.

while

while문 예제를 보자.

fun main() {
    var i = 0
    while (i < 10) {
        print("$i, ")
        ++i
    }
    println()

    var j = 0
    do {
        print("$j, ")
        ++j
    } while(j < 10)
    println()
}

for

for 예제를 보자.

fun main() {
    for (i in 0..10) print("$i, ")
    println()
    for (i in 0 until 10) print("$i, ")
    println()
    for (i in 10 downTo 1) print("$i, ")
    println()
    for (i in 10 downTo 1 step 2) print("$i, ")
    println()
    var strs = arrayListOf("Hello", "World")
    for(s in strs) print("$s ")
    println()
    for((i, v) in strs.withIndex()) print("[$i] $v, ")
    println()
}

..연산자는 범위 생성하며 "1..10"은 1에서 10까지 범위를 의미한다.
in은 범위 조건에 맞는지 확인한다.

함수

기본형

"fun 함수명(인자): 리턴형 { 구문 }" 형태로 정의된다.
간단한 함수 예를 보자.

fun say(name:String): String {
    return "Hello $name!"
}

fun main() {
    println(say("Foo"))
}

인자는 "인자명:자료형"으로 선언한다. 만약 함수에 리턴이 없다면 리턴형을 생략할 수 있다.
인자 기본값을 지정할수 있다.

fun say(name:String = "world"): String {
    return "Hello $name!"
}

fun main() {
    println(say())
}

함수 표현을 더 단순화할 수 있다.

fun say(name:String = "world"): String = "Hello $name!"

fun main() {
    println(say())
}

람다식

람다식은 "{ 인자1: 자료형, 인자2: 자료형 -> 구문 }"으로 정의한다.
별도 return 을 사용할 수 없고 마지막 값이 리턴된다.

val say1: (String)->String = { arg:String -> "Hello $arg!" }
val say2: (String)->String = { arg -> "Hello $arg!" }
val say3: (String)->String = { "Hello $it!" }

fun main() {
    println(say1("Foo1"))
    println(say2("Foo2"))
    println(say3("Foo3"))
}

자료형은 자료형 추론에 의해 생략할 수 도 있다.
또한 람다식 인자가 1개 인 경우는 생략하고 it으로 대체해서 사용할 수 있다.

고차함수

고차함수는 함수를 인자로 사용하는 함수이다.

fun say(name:String = "world", handler:(String)->String) {
    println(handler("Hello " + name))
}

fun main() {
    say("Foo1", { "\"$it!\"" })
    say("Foo2") { "\"$it!\"" }
    say { "\"$it!\"" }
}

마지막 함수 객체 인자는 함수 호출 외부로 뺄 수 있다.
또한 함수 호출시 인수가 없다면 괄호를 생략해서 사용할 수 있다.

:: 키워드를 사용해서 일반 함수를 함수 객체로 사용할 수 있다.

fun say(name:String = "world", handler:(String)->String) {
    println(handler("Hello " + name))
}

fun doublemark(str:String): String = "\"$str\""

fun main() {
    say("Foo", ::doublemark)
}

일반함수를 함수 객체로 사용하는 경우 호출 외부로 뺄 수 없다.

클래스

기본형

"class 클래스명 { }" 형태로 기본 클래스 정의하며 속성은 일반 변수 처럼 정의한다. 기본 가시성은 public이다.

class Greeting {
    var msg = "Hello world!"
    fun say() = println(msg)
}

fun main() {
    var greeting = Greeting()
    println(greeting.msg)
    greeting.say()
}

msg라는 속성 한 개가 정의되고 외부에서 접근할 수 있다. 메소드도 일반 함수 처럼 정의하면 된다.
생성자를 사용해서 내부 속성을 초기하해보자.

class Greeting {
    var msg = "Hello"
    var name:String = ""

    constructor(name: String) {
        this.name = name
    }

    fun say() = println("$msg $name!")
}

fun main() {
    var greeting = Greeting("Foo")
    greeting.say()
}

속성을 다른 방식으로 정의할 수 있다.

class Greeting(var name: String) {
    var msg = "Hello"
    fun say() = println("$msg $name!")
}

fun main() {
    var greeting = Greeting("Foo")
    println(greeting.name)
    greeting.say()
}

클래스명에서 괄호 안에 변수 선언하듯이 속성을 정의할 수 있다.
그리고 인스탄스할 때에 값을 받아서 초기화도 된다.

init로 블럭 정의해서도 초기화활 수 있다.

class Greeting(val name: String) {
    var msg = "Hello"
    private var result:String
    init {
        result = msg + " " + name + "!"
    }
    fun say() = println(result)
}

fun main() {
    var greeting = Greeting("Foo")
    greeting.say()
}

getter/setter

속성 정의시 get()과 set()을 사용해서 getter/setter을 정의할 수 있다.
별도 getter/setter가 없으면 자동으로 생성한다.

class Greeting() {
    var msg = "Hello"
    var name = "world"
        get() = field
        set(value) {
            result = msg + " " + value + "!"
            field = value
        }
    private var result = "Hello world!"

    fun say() = println(result)
}

fun main() {
    var greeting = Greeting()
    greeting.name = "Foo"
    greeting.say()
}

result 속성의 가시성을 private을 해서 외부에서 접근하지 못하도록 했다.
사용할 수 있는 가시성 종류는 아래와 같다.

  • public (기본): 공개
  • internal: 같은 모듈
  • protected: 하위 클래스
  • private: 같은 클래스

setter/getter와 같이 활용해서 속성을 외부에서 읽기 전용으로 만들 수 있다.

class Greeting {
    var msg = "Hello"
    var name = "world"
    private set
}

fun main() {
    var greeting = Greeting()
    greeting.name = "Foo" // cannot assign to 'name'
}

직접 값을 할당하려면 에러가 발생한다. 별도 setter 함수를 만들어서 사용하면 된다.

class Greeting {
    var msg = "Hello"
    var name = "world"
        private set

    fun setName(name:String) { this.name = name }
    fun say() = println("$msg $name!")
}

fun main() {
    var greeting = Greeting()
    greeting.setName("Foo")
    println(greeting.name)
    greeting.say()

}

상속

“class 자식클래스 : 부모클래스” 형태로 상속은 “:”를 사용해서 표현한다.

open class Greeting(var msg:String) {
    fun say() = println("$msg!")
}

class MorningGreeting : Greeting {
    constructor() : super("Good morning") {
    }
}

fun main() {
    var greeting = MorningGreeting()
    greeting.say()
}

클래스는 기본 final로 되어 있기 때문에 바로 상속할 수 없다. open으로 선언해줘야 상속가능한 클래스가 된다.

사용할 수 있는 상속 제어 변경자는 다음과 같다.

  • final(기본): 상속 불가
  • open: 상속 가능
  • abstract: 반드시 상속 해야함
  • override: 부모 메소드/속성 오버라이드

부모 생성자를 호출할 때에 super()를 사용할수도 있고, 아래 처럼 부모 클래스를 바로 호출할 수도 있다.

open class Greeting(var msg:String) {
    fun say() = println("$msg!")
}

class MorningGreeting : Greeting("Good morning") {
}

fun main() {
    var greeting = MorningGreeting()
    greeting.say()
}

특수클래스

Any

클래스 계층구조에서 루트로 모든 클래스의 슈퍼클래스이다. 명시적으로 지정하지 않지만, Any을 상속하고 있다.

public open class Any {
    public open operator fun equals(other: Any?): Boolean
    public open fun hashCode(): Int
    public open fun toString(): String
}

Nothing

Nothing은 인스탄스가 안되는 클래스로 값이 존재하지 않다는 의미로 사용된다. 예를 들어 함수 리턴타입이 Nothing이라면 절대 리턴하지 않는다라는 의미로 리턴하는 경우 예외가 발생한다.

public class Nothing private constructor()

인터페이스

“interface 인터페이스명 { }” 형태로 표현한다.

interface Sayable {
    fun say()
}

class Greeting(var name:String) : Sayable {
    override fun say() = println("Hello $name!")
}

fun main() {
    var greeting = Greeting("Foo")
    greeting.say()
}

인터페이스는 기본 메소드 정의와 프로퍼티를 추가할 수 있다.

interface Sayable {
    var msg:String
    fun say() = println(msg)
}

class Greeting(var name:String) : Sayable {
    override var msg = "Hello " + name
}

fun main() {
    var greeting = Greeting("Foo")
    greeting.say()
}

인터페이스는 다중 구현이 가능하다.

interface Sayable {
    fun say()
}

interface Hello : Sayable {
    override fun say() = print("Hello")
}

interface World : Sayable {
    override fun say() = print("World")
}

class Message : Hello, World {
    override fun say() {
        super<Hello>.say()
        print(" ")
        super<World>.say()
        println("!")
    }
}

fun main() {
    var message = Message()
    message.say()
}

다중 구현된 인터페이스 메소드가 동일할 경우 super<인터페이스명>.메소드() 형태로 해당 인터페이스의 메소드를 호출할 수 있다.

가시성

클래스, 객체, 인터페이스, 함수 등에 가시성 수정자(visibility modifier)를 지정할 수 있다. 사용할 수 있는 가시성 종류는 아래와 같다.

  • public (기본): 공개
  • internal: 같은 모듈
  • protected: 하위 클래스
  • private: 같은 클래스, 같은 파일

아무것도 지정하지 않았다면 기본으로 public으로 적용된다. private으로 정의되었다면 선언된 범위 영역 내에서만 접근할 수 있다. 이 말은 클래스에 필드나 메소드에 private이라면 해당 클래스내에서만 사용할 수 있고, 일반 함수가 private이라면 선언된 파일 내에서만 접근할 수 있다. internal은 같은 모듈에서만 사용가능하다고 하는데 모듈이 뭘까? 모듈이 패키지와 혼용할 수 있는데 서브프로젝트라고 보면 된다. 즉, 외부 라이브러리로 되어 있어서 이를 사용할 경우라고 보면 된다. protected은 최상위 층에서는 쓸 수 없고 클래스 내에서만 사용할 수 있다. 지정된 영역의 범위가 클래스 < 파일 < 모듈 < 전체 순으로 보면 된다.

참고

[1] online kotlin builder, https://play.kotlinlang.org/

반응형

'3.구현 > Java or Kotlin' 카테고리의 다른 글

[spring] Spring Framework에서 DB연동 테스트  (0) 2023.10.26
Kotlin 배우기2 - 심화  (0) 2023.10.18
Reactive Programming 맛보기  (0) 2023.09.14
[Java] RTP 서버 간단히 구현하기  (0) 2021.01.12
[java] enum 사용하기  (0) 2016.11.11