[Android] Kotlin 언어의 특징과 문법

김희정·2023년 2월 22일
1

App

목록 보기
2/2
post-thumbnail

💎 들어가며

이번 포스팅에서는 코틀린 언어에 대해 포스팅해볼까 합니다. 안드로이드 앱 개발을 코틀린으로 할 것이기 때문에 코틀린 문법 정리 용도 입니다.

코틀린은 요즘 많이 쓰는 프로그래밍 언어이자, 안드로이드 공식 언어입니다. 쉽고 간결하고, JVM 기반 언어라는 특성 때문에 Java 언어를 대체하고자 많이 사용됩니다. 백엔드 분야에서는 일명 코프링(Kotlin + Spring)도 있지요.


1. Kotlin Language

1.1 Definition

코틀린(kotlin)은 IntelliJ, PyCharm 등 다양한 IDE를 선보인 것으로 유명한 JetBrains 사에서 오픈소스 그룹을 만들어 개발한 프로그래밍 언어입니다.

기존에는 안드로이드 앱을 자바로 개발했지만, 코틀린의 등장으로 점점 대체 되어 가는 추세입니다.

  • 2017년 구글에서 안드로이드 공식 언어로 지정
  • 2021년 신규 앱 개발시 코틀린으로 개발할 것을 권장

코틀린은 어떻게 Java대체할 수 있었을 까요? 바로 JVM 기반 언어라는 특성 때문입니다.

🌟 JVM 기반 언어를 다르게 얘기해보자면, 어떠한 언어도 자바 바이트 코드(.class)로 컴파일할 수 있다면 동작할 수 있는 것입니다.

JVM이란?

JVM (Java Virtual Machine): 자바 프로그램을 실행하기 위한 실행 환경을 만들어 주는 소프트웨어입니다.

이러한 JVM은 흔히 자바를 설치할 때 설치하는 JDK (Java Development Kit)의 JRE(Java Runtime Environment)에 포함되어 있습니다.


JVM 너란 애는 참?

JVM이란 게 얼마나 멋진 친구냐 한마디로 정의하면, "JVM은 운영체제에 종속적이지 않습니다." 참으로 식상한 말이죠?

이것을 다르게 해석하면, 우리가 만든 JVM 기반 프로그램을 Linux, Windows, macOS 등 "어디에서나 실행 가능하다" 는 말입니다. 이게 얼마나 대단한 일인지는 겪어보지 않고서는 잘 와닿지가 않습니다.

그럼 우리는 언제 깨달을 수 있을까요? 애석하게도 인간은 문제가 발생하기 전까지 깨닫지 못합니다... 우리가 깨달음을 얻는 순간은 바로 이슈가 터졌을 때입니다. 이슈가 터지기 전에 방지할 수 있다면 좋겠죠?

운영체제(Windows, MacOS, Linux, ...) 별로 애플리케이션 확장자는 exe, deb, rpm 등 굉장히 다양한 종류의 애플리케이션이 많은데요. 각 애플리케이션은 실행 환경이 달라지면, 실행할 수 없습니다. ⇒ 호환성이 없기 때문이죠.


JVM 기반 언어

사실 JVM 기반 언어라는 특성은 호환성이 매우 좋아 특별하기 때문에 코틀린 외에도 굉장히 많습니다. 대표적인 언어로, 스칼라(scala), 그루비(Groovy), Jython(Python)가 있습니다.

[참조] 자바를 넘어선 JVM 기반 프로그래밍 언어들


1.2 Features

코틀린 언어의 특징은 다음과 같습니다.

  1. 표현력과 간결함(expressive and concise): 매우 간결한 문법을 제공합니다.
  2. 안전한 코드(safer code): 코틀린에서는 변수 선언시 널 허용과 불허용을 구분해서 선언할 수 있습니다.
  3. 상호 운용성(interoperable): Java와 100% 호환됩니다.
  4. 구조화 동시성(structed concurrency): 코루틴(coroutines) 기법을 이용하면 비동기 프로그래밍을 간소화할 수 있다.


2. 기본 문법

Kotlin의 기본 문법에 대해 살펴보도록하겠습니다.

2.1 변수

⭐ Syntax

코틀린에서 변수는 valvar 키워드로 선언합니다. 각 키워드를 정리해 보면,

  • val (value): 불변성 변수
  • var (variable): 가변성 변수
val data1 = 10
var data2 = 10

fun main(){
	data1 = 20 // 오류!
	data2 = 20 // 성공!
}

📝 Type 선언: 명시적/묵시적

코틀린은 일반적으로 정적 타이핑 언어이지만, 타입 추론이 가능한 경우에는 위와 같이 생략할 수 있습니다. ⇒ 동적 타이핑 기능(타입 추론) 제공.

또한 명시적으로 타입을 선언할 수 있습니다.

val data1:Int = 10

🔥 변수 초기화 미루기

1) lateinit

lateinit 키워드를 사용하여 변수를 선언하면 변수의 초기화를 미룰 수 있습니다.

단, 2가지 조건이 있습니다.

  • var 키워드로 선언한 변수에만 사용 가능
  • Java에서 Primitive 타입으로 분류되는 Int, Long, Short, Double, Float, Boolean, Byte에는 사용할 수 없음.
lateinit var data1: Int		// 오류!
lateinit val data2: String	// 오류!
lateinit var data3: String 	// 성공!

2) lazy 키워드

lazy 키워드는 변수 선언문 뒤에 by lazy {}형식으로 선언하며, 변수가 최초로 이용되는 순간 중괄호로 묶은 부분이 자동으로 실행되어 그 결과 값이 변수의 초기 값으로 할당됩니다.

val data4: Int by lazy {
    println("in lazy .....")
    10
}

fun main(){
    println("in main .....")
    println(data4 + 10)
    println(data4 + 10)
}

/* 결과
in main .....
in lazy .....
20
20
*/

🔍 타입

코틀린의 모든 변수는 객체입니다. 자바에서 제공하는 원시 타입 개념 존재하지 않으며, 코틀린에서는 해당 타입들은 모두 객체로 제공됩니다.

  • 타입 종류
  1. 기초 타입 객체: Int, Short, Long, Double, Float, Byte, Boolean

  2. 문자와 문자열: Char, String

  3. 모든 타입: Any (Java로 치면 Object)

  4. 반환문이 없는 함수: Unit

    fun some(): Unit {
    	println(10+20)
    }
    
    // 반환 타입이 없는 경우에는 Unit이 생략된 것
    fun some() {
        ...
    }
  5. null이나 예외를 반환하는 함수: Nothing
    ⇒ Noting으로 선언한 변수는 null만 대입할 수 있습니다.

    val data1: Nothing? = null

📌 컬렉션 타입

컬렉션 타입(collection type) 이란 여러 개의 데이터를 표현하는 방법이며 Array, List, Set, Map 이 있습니다.


Array

Array는 배열을 표현하는 클래스입니다. 배열에 접근할 때는 대괄호([])를 이용하거나, set(), get() 함수를 이용합니다.

배열의 타입은 제네릭(generic) 으로 표현됩니다.

Array

fun main(){
	val data1: Array<Int> = Array(3, {0});
    data1[0] = 10
    data1[1] = 20
    data1.set(2, 30)
}

배열의 타입이 기초 타입이라면 Array를 이용하지 않고 각 기초 타입의 배열을 나타내는 클래스를 이용할 수 있습니다.

BooleanArray, ByteArray, CharArray, IntArray, ShortArray, LongArray, FloatArray, DoubleArray

val data4: IntArray = IntArray(3, {0})

arrayOf() 함수를 이용하면 선언과 동시에 할당할 수 있습니다.

val data4: IntArray = arrayOf<Int>(1,2,3)

List, Set, Map

List, Set, MapCollection 인터페이스를 타입으로 표현한 클래스이며, 컬렉션 타입 클래스 라고 합니다. 각 클래스의 특징은 다음과 같습니다.

  • List - 순서가 있는 데이터 집합. 데이터의 중복 허용
  • Set - 순서가 없으며 데이터의 중복을 허용하지 않음
  • Map - 키와 값으로 이루어진 데이터 집합으로 순서가 없으며, 키의 중복을 허용하지 않음

Collection 타입의 클래스는 가변(mutable) 클래스와 불변(immutable) 클래스로 나뉩니다. 불변 클래스는 초기에 데이터를 대입하면 더이상 변경(add, set)이 불가능한 클래스 입니다.

구분타입함수특징
ListListlistOf()불변
MutableListmutableListOf()가변
SetSetsetOf()불변
MutableSetmutableSetOf()가변
MapMapmapOf()불변
MutableMapmutableMapOf()가변

✨ 널 허용과 불허용

코틀린의 모든 변수는 객체이기 때문에 null 값이 들어갈 수 있습니다. null 은 값이 할당되지 않은 상황을 의미합니다.

코틀린에서는 변수를 선언할 때 null을 대입할 수 있는 변수인지 아닌지 명확하게 구분해서 선언해야합니다.

널 허용으로 선언하려면 타입 뒤에 물음표(?)를 추가해야합니다.

var data1: Int = 10
data1 = null	// 오류!

var data2: Int? = 10
data2 = null	// 성공!

2.2 함수

⭐ 선언

함수를 선언할 때는 fun 키워드를 사용합니다. 아래는 함수 선언 형식입니다.

fun 함수명(매개변수명: 타입): 반환 타입 { ... }

✨ 특징

다음은 함수 선언의 특징입니다.

  • 함수의 매개변수에는 val이나 var 키워드를 사용할 수 없으며, val로 자동 적용됨. (즉, 불변 변수, 대입 불가)
  • 함수의 매개변수에는 기본 값을 선언할 수 있습니다. 기본 값을 선언했다면, 호출시 인자를 전달하지 않아도 됩니다.
  • 함수 호출 시 매개 변수 명을 지정하면 매개변수 값의 순서를 바꿀 수 있습니다. (명명된 매개변수)
fun main(){
	fun plus(data1: Int, data2: Int = 10){
        return data1 + data2
    }
    
    println(plus(10))
    println(plus(10, 20))
    println(plus(data2 = 30, data1 = 10))
}

2.3 조건문, 반복문

if~else if~else 문

fun main(){
	var data1 = 30
    if(data1 > 10){
        println("status: 0, data1: $data1")
    }
    else if(data1 > 0 && data1 <= 10){
        println("status: 1, data1: $data1")
    }
    else {
        println("status: -1, data1: $data1")
    }
}

Kotlin에서는 if~else는 표현식으로도 사용할 수 있습니다.

fun main(){
	var data1 = 30
    val result: Int = if(data1 > 10){
        0
    }
    else if(data1 > 0 && data1 <= 10){
        1
    }
    else {
        -1
    }
}

when 조건문

fun main(){
	var data1 = 30
    when(data1) {
        10 -> println("data is 10")
        20 -> println("data is 20")
        else -> {
            println("data is unhandled!")
        }
    }
}

for 반복문

fun main(){
	var sum:Int = 0
    for(i in 1..10){
        sum += i
    }
    println(sum)
}

for(i in 1..10)
for(i in 1 until 10)
for(i in 2..10 step 2)
for(i in 10 downTo 1)
  • array와 함께 사용될 경우
fun main(){
	var arr: IntArray = arrayOf(10, 20, 30)
    for(i in arr.indices){
        print(data[i])
    }
    
    for((index, value) in arr.withIndex()){
        print(value)
    }
}

while 반복문

fun main(){
	var x: Int = 0
    var sum: Int = 0
    while(x < 10){
        sum += ++x
    }
    println(sum)
}


3. OOP 문법

다음으로는 코틀린의 객체 지향 프로그래밍에 대해 살펴보겠습니다.

3.1 클래스(Class), 생성자(Constructor)

Syntax

코틀린의 클래스는 class 키워드로 선언합니다.

class User {}

클래스의 멤버는 생성자, 변수, 함수, 클래스로 구성됩니다. 이 중에서 생성자는 constructor 키워드로 선언하는 함수 입니다.

class User {
    var name = "Heejeong Kim"
    constructor(name: String){
        this.name = name
    }
    fun getName(): String{
        return this.name
    }
}

constructor

코틀린의 생성자는 주 생성자보조 생성자로 나뉩니다. 주 생성자는 constructor 키워드로 클래스 선언부에 선언합니다.

// 1. 주 생성자 선언
class User constructor(){}

// 2. constructor 키워드 생략
class User (){}

// 3. 매개변수 없는 주 생성자 자동 생성
class User {}

// 4. 주 생성자의 매개 변수
class User (name: String, count: Int){}

보조 생성자는 클래스의 본문에 construtor 키워드로 선언하는 함수 입니다.

class User{
    constructor(name: String){
        // do somethine
    }
    constructor(name: String, age: Int){
        // do somethine
    }
}

init 영역

init 영역 - 주 생성자의 본문입니다.

class User (name: String, age: Int){
    // 클래스 멤버 변수 선언
    var name: String
    var age: Int
    init {
        this.name = name
        this.age = age
    }
    fun some(){
        println("My Name is $name}. age is $age}")
    }
}

val 키워드

클래스 선언시 val 키워드를 이용하여 선언하면, init 영역 없이 클래스의 멤버 변수로 만들 수 있습니다.

class User (val name: String, val age: Int){
    fun some(){
        println("My Name is $name}. age is $age}")
    }
}

fun main(){
    val user = User("Heejeong", "27")
    user.some()
}

3.2 상속(Inheritance)

Definition & Syntax

클래스를 선언할 때 다른 클래스를 참조해서 선언하는 것을 상속(inheritance)이라고 합니다.

코틀린에서는 클래스를 상속받으려면 선언부에 콜론(:)과 함께 상속받을 클래스 이름을 입력합니다

open class Parent {}

class Child: Parent(){
    ...
}

상위 클래스에 매개 변수가 있을 경우 하위 클래스에서도 상위 클래스를 호출할 때 매개변수 구성에 맞게 인자를 전달해야 합니다.

open class Parent(name: String) {}

class Child(name: String): Parent(name){
    ...
}

overriding

상속이 주는 최고의 이점은 상위 클래스에 정의된 멤버(변수, 함수)를 하위 클래스에서 자신의 멤버처럼 사용할 수 있다는 것입니다.

그런데, 때때로 하위 클래스에서 상위 클래스에서 정의된 멤버를 재정의 해야 할 수도 있습니다. 즉, "상위 클래스에서 선언된 변수나 함수를 같은 이름으로 하위 클래스에서 재정의 하는 것"을 오버라이딩이라고 합니다.

open class Person() {
    open var name = "hjkim"
    open fun someFun(){
        println("Hi~ I'm $name} (Person)")
    }
}

class Developer: Person {
    override var name = "developer_khj"
    open fun someFun(){
        println("Hi~ I'm $name} (Person)")
    }
}

fun main(){
    val obj = Developer()
    obj.someFun()
}

3.3 접근제한자

접근 제한자란 클래스의 멤버를 외부의 어느 범위까지 이용하게 할 것 인지를 결정하는 키워드 입니다.

코틀린이 제공하는 접근 제한자에는 public, internal, protected, private 가 있으며, 각 접근 제한자의 접근 범위는 다음 표와 같습니다.

접근 제한자최상위에서 이용클래스 멤버에서 이용
public모든 파일에서 가능모든 클래스에서 가능
internal같은 모듈 내에서 가능같은 모듈 내에서 가능
protected사용 불가상속 관계의 하위 클래스에서만 가능
private파일 내부에서만 이용클래스 내부에서만 이용

3.4 코틀린의 특수 클래스

데이터 클래스

데이터 클래스data 키워드로 선언하며, 자주 사용하는 데이터를 객체로 묶어 줍니다. 데이터 클래스는 VO 클래스를 편리하게 이용할 수 있게 해줍니다.

data class Student(val name: String, val email: String, val age: Int)

데이터 클래스는 주로 데이터를 다루는 것이 주 목적이기 때문에 equals() 메소드는 객체의 주요 데이터가 같은 비교합니다.

data class Student(val name: String, val email: String, val age: Int){
    lateinit var address: String
    constructor(name: String, email: String, age: Int, address: String): this(name, email, age){
		this.address = address
    }
}

fun main(){
    val obj1 = Student("홍길동", "gildong@gmail.com", 10, "busan")
    val obj2 = Student("홍길동", "gildong@gmail.com", 10, "seoul")
    print(obj1.equals(obj2)) // => true
}

오브젝트 클래스

오브젝트 클래스익명 클래스를 만들 목적으로 사용합니다. 클래스의 이름이 없기 때문에 선언과 동시에 객체를 생성해야 합니다.

val obj = object {
    var data = 10
    fun some(){
        println("data: $data")
    }
}
fun main(){
    obj.data = 30	// 오류!
    obj.some()		// 오류!
}

익명 클래스긴 하지만 익명 클래스를 선언한 거만으로 클래스의 구성요소에 접근할 수 없습니다.

사실상 자바의 인터페이스를 구현하는 원리와 유사합니다. 익명 클래스의 멤버 변수 및 메소드에 접근하기 위해서는 상속받아야합니다.

open class Super {
    open var data = 10
    open fun some(){
        println("I'm Super. data: $data")
    }
}
val obj = object: Super() {
    var data = 10
    fun some(){
        println("data: $data")
    }
}
fun main(){
    obj.data = 30	// 성공!
    obj.some()		// 성공!
}

컴패니언 클래스

컴패니언 클래스는 멤버 변수나 함수를 클래스 이름으로 접근하고자 할 때 사용합니다. Java에서는 static 키워드와 유사하다고 볼수 있습니다.

class Myclass {
    companion object {
        var data = 10
        fun some(){
            println(data)
        }
    }
}

fun main(){
    MyClass.data = 20	// 성공!
    MyClass.some()		// 성공!
}


4. 코틀린의 유용한 기법

4.1 람다 함수와 고차 함수

lambda

람다 함수란? 많은 프로그래밍 언어에서 제공하는 익명 함수 기법입니다.

람다 함수의 특징은 다음과 같습니다.

  • 일반적으로 함수는 fun 키워드로 선언하지만 람다 함수는 fun키워드를 이용하지 않으며 함수 이름이 없습니다.
  • 람다함수는 중괄호 {} 로 표현합니다.
  • {} 안에 화살표(→)가 있으며 화살표 왼쪽은 매개변수, 오른쪽은 함수 본문입니다.
  • 함수의 반환 값은 함수 본문의 마지막 표현식 입니다. (return 문 사용 X)
// 1. 형식
{ 매개변수 -> 함수 본문}

// 2. 예시
val sum = {no1: Int, no2: Int -> no1 + no2}

// 3. 매개 변수 없는 람다 함수 (화살표 생략 가능)
{ -> println("Hello, Kotlin!")}
{println("Hello, Kotlin!")}

typealias

typealias 를 이용해 변수 타입은 물론 함수 타입을 선언할 수 있습니다.

typealias MyInt = Int
fun main(){
    val data2:MyInt = 10
}

typealias MyFunType = (Int, Int) -> Boolean
fun main(){
    val someFun: MyFunType = {no1, no2 -> no1 > no2}
}

고차함수

고차함수란 함수를 매개변수로 전달받거나 반환하는 함수를 의미합니다.

fun hotFun(arg: (Int -> Boolean): () -> String){
    val result = if(arg(10)) {"valid"} else {"invalid"}
    return {"hotFun result: $result"}
}

fun main(){
    val result = hotFun({no -> no > 0})
    print(result())
}

4.2 널 안정성

Null 이란?

널(null)이란? 객체가 선언되었지만 초기화되지 않은 상태를 의미합니다.

객체는 흔히 데이터가 저장된 주소를 참조하므로 흔히 참조 변수라고 합니다. 데이터가 메모리에 저장되면 어디에 저장됐는지 알아야 이용할 수 있는데, 이 때 메모리 위치를 식별하는 것이 주소입니다.

그런데 널은 이 주소를 가지지 못한 상태를 나타냅니다.

객체가 널인 상태에서 객체에 접근하게 되면 널 포인트 예외(NullPointException)이 발생합니다.

이 때 널 안정성이란 널 포인트 예외가 발생하지 않도록 코드를 작성하는 것을 말합니다.


Null 안정성 연산자

  1. 널 허용 - ? 연산자: 변수 타입을 null 허용/불허용 구분
  2. 널 안정성 호출 - ?. 연산자: 널 허용으로 선언한 변수는 접근 시에 반드시 ?. 연산자로 접근
  3. 엘비스 - ?: 연산자: 변수가 null이면 null 반환
  4. 예외 발생 - !! 연산자: 객체가 null일때 예외를 일으키는 연산자. 일부로 예외를 던짐.


💎 Reference

  • [Book] 깡샘의 안드로이드 앱 프로그래밍 with 코틀린, 강성윤, 이지스퍼블리싱

💎 마치며

이번 포스팅에서는 본격적인 안드로이드 앱 개발에 앞서, 앱 개발시 사용할 언어인 코틀린의 특징과 문법을 정리해보았습니다.

다음 포스팅에서는 안드로이드 앱 프로젝트 구성에 대해 알아보겠습니다. 😍

profile
Java, Spring 기반 풀스택 개발자의 개발 블로그입니다.

0개의 댓글