다양한 클래스 타입

|

data class

data class LogItem(val text: String, val timestamp: String)

data 클래스는 데이터만을 갖는 클래스입니다. 반드시 디폴트 생성자를 선언해야 하며, 인자는 val 또는 var 키워드를 꼭 사용해야 합니다. 컴파일러는 자동으로 equal()toString() 함수를 생성해줍니다.


enum class

코틀린에서 enum은 다음과 같은 형태로 선언할 수 있습니다.

enum class COLOR(val rgb: Int) {
    RED(0xFF0000),
    GREEN(0x00FF00),
    BLUE(0x0000FF),
}

enum class STATUS(val text: String) {
    STARTED("Started"),
    FINISHED("Finished"),
    FAILED("Failed"),
    UNKNOWN("Unknown"),
}

사용 방법은 다음과 같습니다.

var color = COLOR.BLUE
println(color)
println(color.rgb)

var status = STATUS.STARTED
println(status)
println(status.text)


sealed class

sealed 클래스는 프로그램 내부에서는 상속을 할 수 있지만, 외부 모듈에서는 상속을 할 수 없도록 하는 키워드입니다. 다음과 같이 사용할 수 있습니다.

sealed class SnowSDK {
}

같은 프로젝트 안에만 있다면 sealed 클래스를 상속한 클래스가 어느 파일에 위치하든지 상관이 없습니다.


object 타입

익명(anonymous) 클래스를 사용할 때 Java에서는 다음과 같이 사용했습니다.

button.setOnClickListener(new View.OnClickListener() {
  @Override
    public void onClick(View v) {
    // TODO   
  }
});

하지만 코틀린에서는 다음과 같이 object 키워드를 이용해서 익명 클래스를 사용할 수 있습니다.

button.setOnClickListener(object : View.OnClickListener() {
    fun onClick(v: View) {
        TODO("...")
    }
})

public static main in Kotlin

|

코틀린에서는 Java의 static main 함수를 다음과 같이 작성할 수 있습니다.

Main.kt

fun main(args: Array<String>) {
    println("Hello, Kotlin")
}

Kotlin ListView 예제

|

build.gradle

dependencies {
    implementation 'com.android.support:design:28.0.0'
}


activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:orientation="vertical"
  tools:context=".MainActivity">

  <Button
    android:id="@+id/add_log_button"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="Add Log"/>

  <android.support.v7.widget.RecyclerView
    android:id="@+id/recycler_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>

</LinearLayout>


item_log.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView
  xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res-auto"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:layout_margin="4dp"
  app:cardCornerRadius="4dp"
  app:cardElevation="8dp">

  <LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingTop="8dp"
    android:paddingBottom="8dp"
    android:paddingStart="8dp"
    android:paddingEnd="8dp"
    android:orientation="vertical">

    <TextView
      android:id="@+id/message"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:paddingLeft="8dp"
      android:text="message"
      android:textColor="@android:color/holo_blue_dark"
      android:textSize="14sp"/>

    <TextView
      android:id="@+id/timestamp"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:gravity="right"
      android:text="timestamp"
      android:textColor="@android:color/darker_gray"
      android:textSize="10sp"/>

  </LinearLayout>

</android.support.v7.widget.CardView>


LogListAdapter.kt

import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.BaseAdapter
import android.widget.TextView

data class LogItem(val text: String, val timestamp: String)

class LogListAdapter(val ctx: Context) : BaseAdapter() {

    private val list = ArrayList<LogItem>()

    init {
        list.add(LogItem("hello", "2019/04/15 13:00"))
        list.add(LogItem("Nice to meet you", "2019/04/15 14:00"))
        list.add(LogItem("Good bye", "2019/04/15 17:00"))
    }

    override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
        var view: View
        val holder: ViewHolder

        if (convertView == null) {
            view = LayoutInflater.from(ctx).inflate(R.layout.item_log, parent, false)
            holder = ViewHolder()
            holder.message = view.findViewById(R.id.message)
            holder.timestamp = view.findViewById(R.id.timestamp)
            view.tag = holder

        } else {
            view = convertView
            holder = convertView.tag as ViewHolder
        }

        val item = list[position]
        holder.message.text = item.text
        holder.timestamp.text = item.timestamp

        return view
    }

    override fun getItem(position: Int): LogItem {
        return list[position]
    }

    override fun getItemId(position: Int): Long {
        return position.toLong()
    }

    override fun getCount(): Int {
        return list.size
    }
}

private class ViewHolder {
    lateinit var message: TextView
    lateinit var timestamp: TextView
}


MainActivity.kt

import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val adapter = LogListAdapter(applicationContext)
        listview.adapter = adapter
    }
}

Kotlin Anko gradle 세팅

|

build.gradle(프로젝트)

buildscript {
    ext.kotlin_version = '1.3.21'
    ext.anko_version='0.10.8'
    }
}


build.gradle(모듈)

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation "org.jetbrains.anko:anko:$anko_version"
}

Kotlin 인터페이스

|

interface

코틀린에서는 Java에서와 달리 interface 내부의 함수가 내용을 가질 수도 있고, abstract 멤버 변수를 가질 수도 있습니다.

또한 Java에서는 implements 키워드를 이용해서 인터페이스를 구현했는데, 코틀린에서는 상속과 마찬가지로 콜론(:)을 사용해서 인터페이스를 구현할 수 있습니다.

interface OnEventListener {
    open fun onEvent(type: Int)
}

open class EventHandler(var name: String) : OnEventListener {
    override fun onEvent(type: Int) {
        TODO("not implemented")
    }
}


다중 인터페이스 구현할 경우

아래 예제는 하나의 클래스가 여러 개의 인터페이스를 구현한 예제입니다. 인터페이스 내의 함수들이 구현체가 있고, 중복된 함수가 있을 경우 구현한 클래스에서는 필요한 클래스의 super를 호출할 수 있습니다. 둘 다 호출해도 상관없고, 필요한 super만 호출해도 상관없습니다.

interface onEventListener {
    fun onEvent() {}
    fun onMessageArrived() {}
}

interface onClientEventListener {
    fun onEvent() {}
    fun onConnected() {}
    fun onDisconnected() {}
}

class TcpServer : onEventListener, onClientEventListener {
    override fun onEvent() {
        super<onEventListener>.onEvent()
        super<onClientEventListener>.onEvent()
    }
}