Kotlin 기본 문법

ashd89·2025년 1월 3일

📌 기존 자바 코드가 어떤식으로 바뀌는 지!

참고 : https://jinjinyang.tistory.com/66

📖 1. 함수

1. 선언 방식 => fun 함수명 (매개변수명 : 타입) : 반환타입

기본 선언

fun sum(a: Int, b: Int):Int = a+b { }
  • return을 따로 지정할 필요가 없음 (return a+b와 동일)

반환 값이 void

fun sum2(a: Int, b: Int):Unit { }

코틀린 함수의 장점 => 매개값이 다른 동일한 함수를 계속 만들 필요 X

  • 디폴트 파라미터 선언
  • 파라미터 지정
  • 특정 인자만 값 세팅 가능

📖 2. 변수와 상수

1. 선언 방식 => var/val 변수명 : 타입(대문자) = 값

var : 변수(가변값)

val myName : String = "ashd"

val : 상수(불변값)

var myAge : Int = 20

2. 출력 => $활용

$변수

val number = 20
println("Please give me $number dollors")

${수식}

val number = 20
println("I only have ${if (number <10 ) number else 10} dollor")

📖 3. 클래스

0. 기본 형태 (생성자가 필요치 않음)

Java

@Getter
@Setter
@AllArgsConstructor
public class Person {
    private String name;
    private int age;
    private boolean isMarred;
}

Kotlin

class Person(val name: String, val age: Int, val isMarried: Boolean)

1. 자바와 달리 field 선언만 하고 Getter, Setter를 사용하지 않음

  • 간결한 선언
  • 명칭을 통한 getter/setter
fun main (args: Array<String>) {
	val person = Person("ashd", 20, false)
	println(person.isMarried());
}

### decompile(자바로 어떻게 되는지) => shiftX2(info창) - Bytecode Viewer

public final class PersonKt {
	public static final void main() {
      Person person = new Person("ashd", 20, false);
      String var1 = person.getName();
      System.out.println(var1);
      int var2 = person.getAge();
      System.out.println(var2);
      boolean var3 = person.isMarried();
      System.out.println(var3);
   }

2. Custom Getter/Setter

  • get() / set()
  • 변수 선언 후 getter/setter의 결정 방법을 선언
class Rectangle (val height: Int, val width: Int) {
    val isSquare:Boolean
        get(){
            return height == width
        }
}

하나의 파일에 여러 클래스와 함수, 최상위 변수를 선언 가능 + 장점

코틀린에서 편리해서 자주 활용되는 부분임
결합도가 높은 클래스, 유사한 클래스, enum 클래스 등 한곳에 모아 놓으면 관리와 사용이 편리해짐

📖 4. when(조건문) + enum

0. Enum (public enum -> enum class)

enum class Color(val r: Int, val g: Int, val b: Int){
    RED(255, 0, 0),
    ORANGE(255, 166, 0),
    YELLOW(255, 255, 0),
    GREEN(0, 255, 0);// 아래 함수와 구분을 위해서 세미콜론 추가

    fun rgb() = (r * 256 + g) *256 + b
}

1. When은 Swtich문을 대체한다 (Enum활용이 필수)

  • 화살표 사용
  • 다중 대체가 가능
  • else는 사용할 수 있지만, when은 case를 지정하지 않았을 때, 컴파일 단계에서 에러를 찾을 수 있어 수정이 편리
fun getKoreanColor(color: Color): String =
    when(color) {
        RED, ORANGE -> "빨강 혹은 오렌지"
        YELLOW -> "노랑"
        GREEN -> "녹색"
    }
}

2. 인자가 없는 When (Else처리 필수)

fun mix(c1: Color, c2: Color) =
    //인자가 없는 when의 경우 반드시 else을 정의해야 됨
    when {
        c1 == RED && c2 == YELLOW -> ORANGE
        c1 == YELLOW && c2 == BLUE -> GREEN
        else -> throw RuntimeException()
    }

3. When을 활용한 TypeCast

fun printObject(obj: Any): Unit = when(obj){
    is String -> println(obj.lowercase())
    is Duration -> println(obj.nano)
    is LocalDateTime -> println(obj.month)
    else -> println("Unknown type")
}

📖 5. 반복문

0. while (java와 동일)

  • do-while 사용 가능

1. for (python과 유사)

  • a..b는 a<=x<=b와 동일 (첫과 끝을 반드시 포함)
  • until을 써도 되지만 쓸모 없음
fun main(){
    for (i in 1..10){
        println(evenOrOdd(i))
    }
    // 마지막 10 미포함 -> until활용
    // for (i in 1<= until < 100)

2. map

  • 컬렉션 함수 존재 mutableMapOf, listOf, mapOf, setOf 사용 가능
  • 순환 및 구조 분해 이용 가능
    순환 (s.key & s.value) => for (s in students)
    구조분해 (num,name) => for ((num, name) in students)
val students = mutableMapOf<Int, String>()
    students[1] = "lee"
    students[2] = "kim"
    students[3] = "park"

일반 mapOf는 to를 통해 key, value 선언 (가장 많이 사용)

val st = mapOf(
        1 to "Jock",
        2 to "Diana",
        3 to "Frost"
    )

📖 6. 예외 처리

1. try catch (new를 사용하지 않음)

  • when처럼 반환의 값으로 취급
fun parse(numberStr: String): Int = try {
    Integer.parseInt(numberStr)

    throw IOException("일부로 발생시키는 checked exception")
} catch (e: IOException) {
    throw RuntimeException("UnChecked exception으로 변환") // 후에 설명할 checked  Exception 처리 방식
} finally {
    println("무조건 실행되는 코드블록")
}

중요 @Transactional과 checked Exception

  • Checked Exception: 컴파일 타임에 체크되는 예외 = 처리를 해야 코드가 돌아감
    Ex) IOException, SQLException, ClassNotFoundException 등

  • Unchecked Exception: NullPointerException 같은 작동 중에 발생하는 에러

Java에서는 @Transcational을 사용하면 unchecked exception이 발생할 경우에만 트랜잭션을 롤백한다.

= 컴파일 과정에서, reader로 읽을 파일이 존재하는지, 읽기 권한은 있는지 등의 checked Exception이 발생할 수 있는 에러는 찾아서 실행을 막음(이미 걸러짐) => Unchecked Exception이 발생했을 때만 롤백해도 정상적으로 작동

Kotiln에서는 Checked Exception이 없음 즉, @Transcational을 사용하면 checked exception이 발생한 경우 롤백하지 않는다.

= IO Exception과 같은 checked Exception이 발생했을 경우 강제로 unchecked Exception을 던져주거나, 롤백하는 코드가 필요
pstmt1이 실행하는 sql문이 name으로 찾아서 update라면, update가 실패할 가능성 존재

try (PreparedStatement pstmt1 = connection.prepareStatement(sql1);

                // 쿼리 실행 (예외 발생 가능성)
                pstmt1.setString(1,"Bob");
                pstmt1.executeUpdate();

                // 모든 쿼리가 성공적으로 실행되면 커밋
                connection.commit();
            } catch (SQLException e) {
                // 예외 발생 시 롤백
                System.out.println("예외 발생: " + e.getMessage());
                if (connection != null) {
                    try {
                        connection.rollback();
                        System.out.println("트랜잭션 롤백 완료");
                    } catch (SQLException rollbackEx) {
                        System.out.println("롤백 중 오류 발생: " + rollbackEx.getMessage());
                    }
                }
            }

📖 7. 확장 함수 및 확장 프로퍼티

1. 확장 함수 (kotlin은 기본적으로 final)

  • kotlin의 함수들은 강제로 Open을 달아주지 않는 이상 상속할 수 없다. (List와 같은 라이브러리 함수의 경우 상속이 불가능)
  • 오버라이딩은 불가능 / 오버로딩은 가능
  • 기본 형태는 fun 피확장함수명.새확장함수명 ( )

이 코드는 List를 상속받아 generic 데이터를 기준 값으로 정하는 확장 함수

  • 참고 : generic은 함수를 선언할 때, 이게 generic함수라는 것을 알려줘야함
fun <E> List<E>.getHigherThan(num: E): List<E> {
    // 구현....
    return arrayListOf<E>()
}

중요 : 확장 함수는 정적 바인딩된다!

open class Idol {
    open fun sing() = println("아이돌이 노래를 불러요")
}
 
class BTS: Idol() {
    override fun sing() = println("BTS가 노래를 불러요")
}
 
fun Idol.dance() = println("아이돌이 춤을 춰요")
fun BTS.dance() = println("BTS가 춤을 춰요")
    
fun main() {
    // 부모 타입으로 자식 객체 생성
    val bts: Idol = BTS()
    bts.sing() // BTS가 노래를 불러요
    bts.dance() // 아이돌이 춤을 춰요
}

타입과 객체 : Idol 타입의 변수인 bts는 Idol 클래스 또는 그 자식 클래스인 BTS의 객체를 참조

  • sing은 일반 함수이기 때문에 동적바인딩 즉 bts가 처음에는 부모 클래스인 Idol타입이지만 default값으로 자식 클래스인 BTS 객체로 선언될 때를 인식하고 => BTS
  • dance는 확장 함수이기 때문에 정적바인딩 되어서, 처음 부모 클래스인 Idol타입으로 인식 => Idol

2. 확장 프로퍼티

  • 확장 함수와 유사한 형태로
  • 단 일회성
  • getter는 필수 / setter는 선택
var StringBuilder.lastChar: Char
    get() = get(length - 1)
    set(value: Char) {
        this.setCharAt(length - 1, value)
    }
    
fun main() {
    val sb = StringBuilder("Gold")
    sb.lastChar = 'f'
    println(sb) // golf
}

📖 8. 인터페이스와 상속

1. 인터페이스 (반드시 overrride)

  • implements나 extends로 구현하지 않고 코틀린에서는 : 으로
  • 다중 상속도 동일하게 가능

2. 상속 (반드시 Open으로 선언 필수)

  • 메소드도 open으로 선언해야 상속 가능
  • 상속받은 메소드(override)는 재상속 가능
  • 다중 상속도 동일하게 가능

상속 생성자 선언 방법

  • 주 생성자(All args)는 선언부에서 기본으로 생성
  • 부 생성자는 construct를 통해 생성하고 :뒤에 this - 다른 생성자, super - 상속 클래스 생성자를 호출
class Child : Parent{
    private val subName:String

    constructor(subName: String):this(subName, "")
    
    constructor(subName:String, firstName: String):super(firstName){
        this.subName = subName
    }
}

위 코드의 간소화

  • subName만 있을 때는 firstName을 ""으로 받으므로 => Default 값으로 변경
  • firstName이 존재할 경우 부모 클래스의 생성자 호출
class Child(val subName:String, firstName: String = "") : Parent(firstName)

📖 9. Sealed(봉인된) class

  • 인터페이스와 달리, 클래스로 인식되어서 클래스명()로

1. 인터페이스를 사용 중에 추가된 클래스가 있을 때

  • 추가 클래스가 있을 때, getMessage 함수에서 컴파일 단계에서 에러로 처리되지 않음 => else처리
interface Error

class FileError(val filename:String) : Error

class DatabaseError(val database:Database) : Error

class RedisError(val host:String): Error

enum class Database{
    ORACLE, MYSQL, MARIADB;
}

fun getMassage(error: Error) = when(error){
    is FileError -> "error is fileError ${error.filename}"
    is DatabaseError -> "error is databaseError ${error.database.name}"
    else -> throw IllegalArgumentException("error is unknown")
}

2. sealed class를 사용 중에 추가된 클래스가 있을 때

  • RedisError를 추가하면, getMessage2에 RedisError에 대한 처리가 안되어있기 때문에 처리 편리 => enum에서 -> 이용하는 방식과 동일한 원리
sealed class Error2{
    class FileError(val filename:String) : Error2()
    class DatabaseError(val database:Database) : Error2()
    class RedisError(val host:String): Error2()
}

enum class Database2{
    ORACLE, MYSQL, MARIADB;
}

fun getMassage2(error: Error2) = when(error){
    is Error2.DatabaseError -> "error is databaseError ${error.database.name}"
    is Error2.FileError -> "error is fileError ${error.filename}"
    is Error2.RedisError -> "error is redisError ${error.host}"
}

📖 10. 활용 (private set / data class / object class)

1. set 제한 (private set)

class Account {
	var balance: long = 0
    private set
}

2. data class (java의 record와 유사)

toString을 쓴 것 처럼 객체 안의 내용을 확인 가능 => 객체 조회시 이름@주소가 아니라, 주 생성자의 값들이 출력됨

  • DTO 생성 시 유용
  • toString()/equals()/hashCode() 사용 가능
data class User(val name: String, val age: Int)

3. object 키워드 (싱글톤 패턴)

  • object라고만 써야함 (class x )
  • 유일 인스턴스가 자동으로 생성
object NumberUtil {
    fun sum(a:Int, b:Int) = a+b
    fun multiply(a:Int, b:Int) = a*b
}

fun main() {
    println(NumberUtil.multiply(10 ,2))
}

- 익명 클래스로 사용 가능

참고 : companion Object

  • static 대용으로 사용 가능 (클래스 생성 시 팩토리 생성자 기능)
    => 상수 선언
    => 처음 생성되는 데이터의 나이를 0으로 고정하는 팩토리 생성자
data class Child (val name:String, val age:Int){
    companion object {
        const val MAX_CHILDREN = 4

        fun ofDefaultAge(subName:String):Child{
            return Child(subName, 0)
        }
    }
}

intellij kotlin 프로젝트 Tip

  • 생성한 kotlin 폴더가 특수 폴더로 지정되지 않을 때

0개의 댓글