Kotlin 사용하기

김희영·2025년 10월 29일

토막개발지식

목록 보기
18/27

Kotlin의 특징은 뭘까?

  • 간결함
  • Null 안전성 (Null Safety)
  • Java와 같이 사용 가능 -> 마이그레이션 용이
  • 타입 추론(명시 안해도 자기가 알아서 알잘딱)
  • 코루틴 (Coroutine) -> 비동기처리 용이

Kotlin 문법 따라잡기

코틀린은 자바와 호환이 되기 때문에, 자바로 쓴 생성자도 가져다 쓸 수 있다.

fun main() {
    val p = Person(10, "123") //java로 Person 정의한 거 사용
    p.Say()
}

문자열 사용이 편리하고, 변수 타입은 뒤에 쓴다.

fun main() {
    val number : Int= 10 		//Int 타입
    var message = "Hello JAVA"	//var : 가변 변수 | val : 불변 변수
    println("number: $number")	//문자열 내 변수 바로 사용 가능
    println("message: $message")
}

삼항 연산자 대신 if문으로 바로 넣을 수 있다.
Switch문 대신 when을 쓴다.

fun main() {
    val number : Int= 10
    var message = if(number%2==0) "Even" else "Odd"
    println("message: $message")
    
    when{
        number % 2 == 0 -> println("Even")
        number % 2 != 0 -> println("Odd")
    }
    //변수에 값 할당에 when 쓰면 else가 필요하다.
}

반복문은 for와 for-in이 있다.

fun main() {
    for(i in 0 until  5) {		//until은 포함하지 않는다.  (5 출력X)
        println(i)
    }

    for(i in 0 ..  4) {			// ...은 포함한다. (4까지 출력)
        println(i)
    }
}

함수도 다양하게 쓸 수 있다.

fun main() {
	val result = add(1,2) //add 함수 덕에 타입 추론이 됨
    println(result)
}

fun add(a:Int, b:Int): Int{
	return a+b
}

fun add1(a:Int, b:Int)=return a+b; //이렇게도 가능

객체 생성은 생성자가 없어도 됨

fun main() {
    val p = Person( "123")
    p.say()
}

class Person(private val name:String){ //변수는 여기에 (생성자는 필요 없음)
    fun say(){						//기본이 public
        println("$name is saying")
    }
}

list도 가변, 불변이 있다. (불변 권장)

fun main() {
    val num1=listOf("1","2","3")           //불변리스트
    val num2=mutableListOf("1","2","3")    //가변리스트 (비추)
    
    for(i in num2){
        println(i)
    }
}

map도 가변, 불변 있음. (역시나 불변 권장)

fun main() {
    val num1 = mapOf("1" to 1, "2" to 2, "3" to 3)          //불변
    val num2 = mutableMapOf("1" to 1, "2" to 2, "3" to 3)   //가변

    num2.put("4", 4)

    for ((key, value) in num2) {               //튜플 형태로 이용
        println("$key is $value")
    }
}

Null Safety가 있어 null 처리에 유리하다.

fun main() {
    val name: String? =null                 // ?로 null 허용
    // ?로 name가 null인지 확인하고, ?: 을 사용하여 null이면 다른 걸로 처리 가능
    println(name?.length ?: "Name is Null")
}

+) ?: : 엘비스 연산자
+) ? 썼는데 null이면 그 뒤에 건 실행을 안한다.

람다 표현식도 축약 가능

fun main() {
    val num1=listOf("1","2","3")
    num1.forEach {n->println("$n")}
    num1.forEach {println(it)}      //매개변수가 1개면 생략 가능 (it 고정)
}

데이터 class로 equals(), hashCode(), toString() 자동 생성 가능

fun main() {
    val p1 = Person("123")
    val p2 = Person("456")

    println(p1)  // toString() 자동 생성된 거임
    println(p1 == p2) // equals() 도 자동 생성
}

data class Person(val name:String){}

확장 함수? -> 기존 클래스에 새로운 함수 추가하는 기능 (유틸 클래스 추가 없이)

fun main() {
    val name:String="name"
    name.aaa()
}

fun String.aaa() { //확장 함수
    println("$this ~!!")
}

오버로딩 없이 설정하는 기본값

fun main() {
    say()			//"기본값" 출력
    say("new Name")	//"new Name" 출력
}

fun say(name:String = "기본값"){
    println(name)
}

object로 빠르게 객체 싱글톤 만들기

object Singleton {
    fun show() {
        println("Hello")
    }
}
class Test {

}

fun main() {
    val a = Singleton //()없어도 됨
    val b = Singleton

    println(a == b) // Singleton 이므로 항상 같은 인스턴스가 반환됨.

    val t1 = Test()
    val t2 = Test()

    println(t1 == t2) // 객체 생성마다 새로운 인스턴스가 생성되므로 서로 다른 인스턴스임.
}

+) JAVA로 싱글톤 만들기

public class Singleton {
	//최초 생성
    private static final Singleton INSTANCE = new Singleton();

    private Singleton() {}	//외부 접근 차단

	//최초 생성한 거 줌
    public static Singleton getInstance() {
        return INSTANCE;
    }

    public void show() {
        System.out.println("Hello");
    }

    public static void main(String[] args) {
        Singleton.getInstance().show();
    }
}

예외처리는 try를 표현식으로 사용 가능

fun main() {
	//try-catch를 강제하지 않기 때문에 그냥 쓰면 된다.
    //throw도 필요 없음
    Files.copy(File("a.txt").toPath(), File("a_copy.txt").toPath(), StandardCopyOption.REPLACE_EXISTING)
}

filter로 리스트 필터링 (java 스트림과 유사)

fun main() {
    val num = listOf(1,2,3,4,5,6,7,8,9)
    val newNum= num.filter { it % 2 == 0 } //배개변수 1개라 it으로 받음

    for(i in newNum){
        println(i)
    }
}

forEach와 map 기본 제공

fun main() {
    val num = listOf(1,2,3,4,5,6,7,8,9)
    //map로 각 요소에 10씩 추가
    //forEach로 각 요소 출력
    num.map{it+10}.forEach {println(it)}
}

is로 타입 캐스팅 (체크와 캐스팅을 한번에)

fun main() {
    val obj: Any = "hi"

    if(obj is String) {
        println(obj.length)
    }

}

스코프 함수 (apply, let, also, run, with)
-> 특정 객체(컨텍스트)의 범위를 지정하여 해당 객체의 멤버(프로퍼티, 메소드)에 간결하고 효율적으로 접근할 수 있게 해주는 코틀린의 기능

함수리턴값컨텍스트(this or it)주요 사용 목적
apply객체 자체this객체 설정 (초기화, 프로퍼티 변경)
let람다 결과값it안전 호출과 변환 (Nullable 처리, 새로운 값 반환)
also객체 자체it객체 유지 + 추가 로직 수행 (디버깅, 로깅)
run람다 결과값this객체 설정 + 결과 반환 (객체 없이 블록 실행도 가능)
with람다 결과값this객체를 컨텍스트로 작업 수행 후 결과 반환
fun main() {
    //apply (객체를 생성 -> 내부 속성 설정 -> 객체 반환)
    val p = Person().apply {
        //this(객체)로 넘어와서 객체가 반환
        this.name="new Name"
        this.age = 20
    }
    p.printInfo() //Name: new Name , Age: 20

    //let (null이 아닌 경우 특정 로직을 실행)
    val rst1 = p.name?.let {
        //it으로 넘어옴 (name 길이: 8)
        println("name 길이: ${it.length}") //name 길이: 8
        it.length //rst에 10 할당
    }

    //also 객체를 유지하며 추가적 작업 수행
    p.age.also { println(it) } //20
    println("rst1 : $rst1") //rst1 : 8


    //run 객체 받아 작업 수행 후 결과 리턴
    val rst2 = p.run {
        this.age * 2 + 10
    }

    println("rst2 : $rst2") //rst2 : 50

    //with 객체를 받아 작업 수행 후 결과 리턴
    with(p) {
        increaseAge(10) //this.increaseAge()와 동일
        printInfo()
    }

}

class Person {
    var name: String?=""
    var age: Int=0
    fun printInfo(){
        println("Name: $name , Age: $age")
    }
    fun increaseAge(years: Int) {
        age += years
    }
}

lateinit으로 나중에 초기화

fun main() {
    var p = Person()
    p.name = "new Name"
    println(p.name)
}

class Person {
    lateinit var name: String
}

+)주의!! lateinit 하면 컴파일러가 잡아주지 않는다...

lasy로 최초 접근시 초기화 하기

fun main() {
    val name : String by lazy {
        println("initializing")
        "Hello"
    }
    println(name)
}

동반 객체 (companion object) : 정적 멤버 선언용. 객체처럼 쓸 수 있는 static

fun main() {
    println(MathUtil.PI)
    println(MathUtil.sq(3))
}

class MathUtil{
    companion object{
        val PI=3.14
        fun sq(n:Int)=n*n
    }
}

open 키워드로 상속 받을 수 있게 하기
open class 클래스명 형식으로 적어야 이걸 상속 받는 클래스를 만들 수 있다.
(기본적으로는 java의 final 형태)

fun main() {
    val aa = AA()
    aa.fun1() // AA 출력
}

open class A { //상속 가능하게 open 붙여줌
    open fun fun1() { //메서드도 open 붙여줌
        println("A")
    }
}

//:로 오버라이딩, 인터페이스 정의하기 (둘다 :로 쓴다.)
//다중 구현은 , 하고 하면 됨 (AA: A,B 같이)
class AA : AA() {
// 메서드 오버라이딩 가능함. (override 붙여야함)
    override fun fun1() { 
        println("AA")
    }
}

+) 다중 구현 : 상속은 java, kotlin 모두 1개 밖에 못 받지만, 인터페이스는 둘다 여러개를 상속받는 다중 상속을 지원함

필드 수정은 막고, 접근은 허용할 때는 val (굳이 private 없어도 됨)
반대로 수정을 허가하고, 접근 불가일 때는 Getter 호출 시 예외 발생하게 하면 됨

fun main() {

    val p = Person()

    p.name = "KIM"
    println(p.name)

}

class Person {
	//그냥 var로 하면 getter, setter가 자동으로 생성.
    private var _name: String = "AAA"

	//getter, setter 직접 구현 (가상 필드)
    var name: String
        get() = throw IllegalAccessException("Can't access") //예외처리
        set(value) {_name = value}	//setter는 허용
}

생성자와 init 블록
init 블록: 첫 생성시 값을 세팅하면서 로직 실행

fun main() {
    val a = A("abc", 25)
}

class A(val a: String, val b: Int) { //주생성자
	//부생성자 (this는 주 생성자 호출하는 거) (=부생성자로 주생성자 만들기임)
	constructor(a: String) : this(a, 0) //a값만 받고, b 값은 그냥 0으로 하는거
    
    //init 블록
    init {
        println("객체 생성되었습니다: $a, $b")
    }
}

+) 사실 부생성자는 그냥 val a: String, val b: Int =0 해도 되긴 한다. (a 값만 받고, b는 기본 0)

profile
내는 반드시 엄청난 개발자가 되고 말것어

0개의 댓글