Languege/Kotlin / / 2025. 3. 6. 22:18

[Kotlin Basic] 기초 문법 학습 3

이번 포스팅에서는 예외 처리(try-catch-finally), Checked vs Unchecked Exception, try-with-resources & use 함수, 코틀린의 Default Parameter & Named Argument, 가변인자, 클래스 초기화, 커스텀 getter, 상속과 인터페이스 구현 그리고 접근제어 키워드 등을 정리해 보겠습니다. 자바와 코틀린에서 어떻게 다른지 비교해가며 살펴보도록 하겠습니다.


1. try-catch-finally

자바와 코틀린 모두 예외 처리를 위해 try-catch-finally 블록을 사용합니다.
다만, 코틀린에서는 try-catch가 “식(Expression)”이므로 아래처럼 값을 반환할 수 있습니다.

Java

try-catch-finally는 “문(Statement)”이므로 직접 반환값을 형성하기 어렵습니다.

private int parseIntOrThrow(String str) {
  try {
    return Integer.parseInt(str);
  } catch (NumberFormatException e) {
    throw new IllegalArgumentException("잘못된 숫자입니다.");
  } finally {
    System.out.println("finally는 무조건 실행됩니다.");
  }
}

Kotlin

try-catch가 식(Expression)이기 때문에 블록 전체를 통으로 반환값으로 활용 가능합니다.

fun parseIntOrThrow(str: String): Int {
    return try {
        str.toInt()
    } catch (e: NumberFormatException) {
        throw IllegalArgumentException("잘못된 숫자입니다.")
    } finally {
        println("finally 블록은 항상 실행됩니다.")
    }
}

2. Checked Exception vs Unchecked Exception

Java

  • Checked Exception: 반드시 예외 처리를 강제하는 예외(IOException 등)
  • Unchecked Exception: 런타임 시점에 발생하며, 강제 예외 처리가 없는 예외(RuntimeException 등)
public void readFile() throws IOException {
  BufferedReader br = new BufferedReader(new FileReader("test.txt"));
  // ...
}

Kotlin

  • 코틀린에는 Checked Exception 개념이 없습니다. 즉, IOException 등도 명시적 예외 처리가 강제되지 않습니다.
  • 예외를 꼭 처리하지 않아도 컴파일 에러가 발생하지 않습니다.
fun readFile() {
    val reader = BufferedReader(FileReader("test.txt"))
    // ...
}

3. try-with-resources vs use

Java

JDK 7부터 추가된 try-with-resources 구문을 사용하면 자동으로 close()가 호출됩니다.

public void readFile2(String path) throws IOException {
  try (BufferedReader reader = new BufferedReader(new FileReader(path))) {
    System.out.println(reader.readLine());
  }
}

Kotlin

코틀린에는 try-with-resources가 없습니다. 대신 use라는 inline 확장 함수를 제공합니다. 이건 익숙하지가 않아서 가독성이 안좋아보이는데, 이번 프로젝트에서 사용하면서 눈에 익혀보겠습니다.

fun readFile2(path: String) {
    BufferedReader(FileReader(path)).use { reader ->
        println(reader.readLine())
    }
}

4. Default Parameter와 Named Argument

Java

Default Parameter가 없으므로, 오버로딩 메서드를 여러 개 만들어야 합니다.

public void repeat(String str, int num, boolean useNewLine) {
  // ...
}

public void repeat(String str, int num) {
  repeat(str, num, true);
}

public void repeat(String str) {
  repeat(str, 3);
}

Kotlin

파라미터에 기본값을 지정할 수 있고, Named Argument를 통해 원하는 파라미터만 선택하여 값 설정도 가능합니다. 빌더 패턴 + JS의 Default Value가 섞인 느낌이라 정말 좋네요

fun repeat(
    str: String,
    num: Int = 3,
    useNewLine: Boolean = true
) {
    // ...
}

// 사용 예시
repeat("Hello")
repeat("Hello", 5)
repeat("Hello", useNewLine = false)

5. 가변인자 (Varargs)

Java

public static void printAll(String... strings) {
  for (String str : strings) {
    System.out.println(str);
  }
}

Kotlin

코틀린에서는 vararg 키워드를 사용합니다. 배열을 전달하려면 스프레드 연산자(*)를 붙입니다.  JS에서도 ES6에 스프레드 연산자가 있어서 사용법만 상기해두면 좋아 보입니다.

fun printAll(vararg strings: String) {
   for (str in strings) {
       println(str)
   }
}

val array = arrayOf("A", "B")
printAll("Hello", "Kotlin")
printAll(*array) // 스프레드 연산자

6. 클래스 선언과 초기화 블록

Java

생성자 + 필드 + getter/setter를 명시적으로 작성해야 합니다.

public class JavaPerson {
  private final String name;
  private int age;

  public JavaPerson(String name, int age) {
    if (age <= 0) throw new IllegalArgumentException("나이가 잘못됨");
    this.name = name;
    this.age = age;
  }

  // getter/setter ...
}

Kotlin

  • 생성자와 필드 선언을 동시에! 자바 17에 생긴 record 타입과 비슷하네요.
  • init 블록으로 초기화 시 검증 로직을 넣을 수 있습니다.
class Person(
    val name: String,
    var age: Int
) {
    init {
        if (age <= 0) {
            throw IllegalArgumentException("나이가 잘못됨")
        }
    }
}
  • val은 불변 필드(getter만), var는 가변 필드(getter+setter)

7. 커스텀 Getter

코틀린은 프로퍼티 기반으로 Getter/Setter를 작성할 수 있어, 마치 필드처럼 접근 가능합니다.

class Person(val name: String) {
    val uppercaseName: String
        get() = name.uppercase()
}

val person = Person("kotlin")
println(person.uppercaseName) // "KOTLIN"

8. 상속과 인터페이스 구현

자바

public abstract class JavaAnimal {
  protected final String species;
  public JavaAnimal(String species) {
    this.species = species;
  }
  public abstract void move();
}

public class JavaCat extends JavaAnimal {
  public JavaCat(String species) {
    super(species);
  }
  @Override
  public void move() {
    System.out.println("사뿐사뿐");
  }
}

코틀린

클래스나 인터페이스 구현시에 “:” 기호 사용, 상위 클래스 생성자를 바로 호출해야 합니다.
추상 클래스의 함수나 프로퍼티를 재정의할 때는 override 키워드가 필수입니다.

abstract class Animal(
    protected val species: String
) {
    abstract fun move()
}

class Cat(species: String) : Animal(species) {
    override fun move() {
        println("사뿐사뿐")
    }
}

9. open 키워드와 초기화 블록 주의

코틀린에서 일반 클래스나 멤버를 상속/오버라이드하려면 open 키워드를 붙여야 합니다.
단, 상위 클래스 생성자 또는 초기화 블록에서 open 프로퍼티를 사용하면, 하위 클래스에서 override된 값이 제대로 초기화되기 전에 호출될 수 있으므로 주의가 필요합니다.

open class Base(open val number: Int = 100) {
    init {
        println("Base init: $number")
    }
}

class Derived(override val number: Int) : Base(number) {
    init {
        println("Derived init: $number")
    }
}

// 생성 순서상 Base init이 먼저 호출되므로, 의도치 않은 값(0)이 찍힐 수 있음

10. 접근 제어자 (Java vs Kotlin)

  • Java: 기본 접근제어자가 package-private
  • Kotlin: 기본 접근제어자가 public
    이외에 private, protected, internal 등이 존재

코틀린에서는 유틸성 함수를 만들 때, 별도의 클래스 없이 파일 최상단 범위에 함수를 배치하는 것이 흔합니다.

// StringUtils.kt
fun isDirectoryPath(path: String) = path.endsWith("/")
  • 굳이 유틸 클래스를 만들지 않아도 됩니다.

✏️ 마무리 정리

오늘은 자바와 코틀린에서 예외 처리부터 클래스 상속/인터페이스, 그리고 접근 제어자까지 주요 차이점들을 정리했습니다.

  1. try-catch-finally와 Checked vs Unchecked Exception
  2. 자바의 try-with-resources vs 코틀린의 use 함수
  3. 코틀린의 Default Parameter & Named Argument로 오버로딩 훨씬 줄이기
  4. 가변 인자(vararg) 처리 및 스프레드 연산자
  5. 클래스 선언 시 생성자 + 필드를 동시에 작성 가능, init 블록 주의사항
  6. 커스텀 getter와 프로퍼티 override
  7. 상속 시에 open 키워드 사용, 인터페이스 구현에서도 “:” 사용
  8. 접근 제어자 기본값과, 코틀린의 파일 최상단 함수 활용

다음에도 코틀린을 학습하면서 자주 헷갈리는 내용을 정리해보겠습니다.
읽어주셔서 감사합니다!

'Languege > Kotlin' 카테고리의 다른 글

[Kotlin Basic] 기초 문법 학습 5  (0) 2025.03.12
[Kotlin Basic] 기초 문법 학습 4  (0) 2025.03.10
[Kotlin Basic] 기초 문법 학습 2  (0) 2025.03.04
[Kotlin Basic] 기초 문법 학습 1  (0) 2025.03.03