Kotlin IN ACTION - 1 ~ 2

Park Suyong·2022년 4월 19일
0

Study

목록 보기
12/12

Kotlin In Action

1. 코틀린이란 무엇이며, 왜 필요한가?

1-1. 정적 타입 지정 언어

  • 코틀린은 자바와 마찬가지로 정적 언어이다.

정적 언어란, 컴파일 시점에 데이터 타입이 지정되는 언어를 말한다. 반대로 동적 언어란 런타임 시점에 데이터 타입이 결정되며, 타입과 관계 없이 모든 값을 변수에 넣을 수 있다.

정적 언어의 장점은 다음과 같다.

  • 성능
    • 런타임에 어떤 메서드를 호출하는 것인지 알아 내는 과정이 필요 없다. 컴파일 시간은 더 소요되나, 메서드 호출 동작 등 런타임 동작은 더 빠르다.
  • 신뢰성
    • 컴파일러가 프로그램의 정확성을 검증한다. 따라서, 런타임 과정에서 프로그램이 오류로 인해 크래시 되는 현상을 더 줄일 수 있다.
  • 유지보수성
    • 코드에서 다루는 객체가 어떤 타입에 속하는지 알 수 있으므로, 처음 보는 코드를 다룰 때도 더 쉽다.
  • 도구 지원
    • 툴은 더 정확한 코드 완성 기능을 제공하며, 더 안전하게 리팩토링할 수 있다.
  • 다만 자바와 달리 코틀린은 타입 추론을 지원한다. 아래 코드처럼 문맥으로부터 타입을 자동으로 추론할 수 있다.
val x = 1
fun add(a: Int, b: Int) = a + b
  • 코틀린은 Nullable 한 타입을 지원한다. 기본적으로 타입은 Nullable 하지 않다. 이로써 한층 더 NPE에 자유로울 수 있다. (지연 초기화를 사용하지 않는 한 반드시 초기화 해야 한다.)
class Chapter1 {

    private lateinit var variable: String // 초기화 과정이 생략될 시 런타임 에러 발생

    fun main() {
        val a // 컴파일 에러
        val b = 3
    }
}
  • 코틀린은 함수 타입을 지원한다. 1-2를 참고한다.

1-2. 함수형 프로그래밍과 객체지향 프로그래밍

함수형 프로그래밍의 핵심 개념을 살펴 본다.

  • 일급 시민(객체) 함수 (First-Class / First Citizen)

    • 함수를 변수처럼 다룰 수 있다. 함수를 변수에 저장할 수 있으며, 함수를 또 다른 함수의 인자로 전달할 수 있다. 함수에서 함수를 반환하는 것도 가능하다.
  • 불변성

    • 함수형 프로그래밍에서는 일단 만들어 지고 나면 내부 상태가 절대 바뀌지 않는 불변 객체를 사용해 코드를 작성한다.
  • 부수 효과 (side effect) 없음

    • 함수형 프로그래밍에서는 입력이 같으면 항상 같은 출력을 내놓는다. 다른 객체의 상태를 변경하지 않으며, 함수 외부 등 외부 환경과 상호작용하지 않는 순수 함수(pure function)를 사용한다.

    순수 함수 (pure function)

    순수 함수란, 어떤 함수에 같은 인자를 주었을 때 같은 출력 결과를 내뱉는 함수를 말한다. 즉, 외부 요소로 인해 같은 인자를 전달하였다 하더라도 결과가 달라진다면 그것은 순수 함수가 아니다.

그렇다면 함수형 프로그래밍을 통해 얻을 수 있는 장점은 무엇일까?

  • 간결성
  • 명령형 코드를 사용한다면 함수형 코드는 더욱 간결하다.
  • 다중 스레드를 사용해도 안전함
  • 멀티 스레딩 프로그램에서는 하나의 데이터를 여러 스레드에서 접근하고 변경할 때 가장 큰 문제가 발생한다. 따라서 불변 데이터 구조를 유지하고 순수 함수를 데이터 구조에 적용했다면 멀티 스레드 환경에서 하나의 데이터를 여러 스레드가 변경할 수 없게 된다.
  • 테스트 용이성
  • Side Effect를 가진 함수라면 테스트 케이스를 여러 가지로 준비해야 한다. 하지만, 순수 함수의 경우에는 그러한 준비 없이 하나의 테스트 케이스로 충분히 테스트 할 수 있다.

함수형 프로그래밍의 특징을 살펴 본다.

  • 데이터 타입으로 함수 타입을 지원한다. 이로써 어떤 함수가 다른 함수를 파라미터로 받거나 함수가 새로운 함수를 반환할 수 있다.
    • 람다 식을 지원함으로써 코드 블록을 쉽게 정의하고 전달할 수 있다.
    • 대부분의 Kotlin 표준 라이브러리는 인자로 받은 람다 함수를 인라인한다. 따라서, 객체 증가로 인한 가비지 컬렉션으로 프로그램이 멈출 걱정을 하지 않아도 된다.
  • Kotlin data class는 불변 객체를 간편하게 만드는 구문을 제공한다.
    • Kotlin 표준 라이브러리는 객체와 컬렉션을 함수형 스타일로 나눌 수 있는 API를 제공한다.

1-3 코틀린 철학

  • 실용성

  • 간결성

    • 코틀린은 boiler plate 코드를 자바에 비해 많이 줄여준다.

    • 아래 코드에서 getter, setter 등의 불필요한 로직을 없애 주는 모습을 볼 수 있다.

      class Person(
          private val name: String,
          private val age: Int
      ) {
          fun getLastName(): String = invokeLastName()
          private fun invokeLastName() = name.substring(0, 1)
      
          fun getNextAge(): Int = invokeNextAge()
          private fun invokeNextAge() = age + 1
      }
      
      fun main() {
          val suYong = Person("홍길동", 26)
          with(suYong) {
              println("성 ${getLastName()}")
              println("내년 나이 ${getNextAge()}")
          }
      }
  • 안전성

    • 코틀린은 JVM에서 실행된다. 이로써 Buffer Overflow를 예방하고 메모리 안전성을 보장한다.

    • 코틀린은 Null Pointer Exception을 어느 정도 예방한다. 코틀린은 기본적으로 Nullable 하지 않은 데이터 타입을 기초로 두며, ? 기호 추가를 통해 Nullable 한 데이터 타입을 추가할 수 있다.

      val name: String? = null // nullable
      val age: Int = 3 // non nullable
    • 코틀린은 Class Cast Exception 또한 어느 정도 예방한다. 특정 객체를 다른 타입으로 캐스팅 하기 전에 타입을 미리 검사하지 않는다면 ClassCastException이 발생하게 된다.

    • 코틀린은 컴파일 과정에서 자주 발생할 수 있는 예외를 잡아줌으로써 개발 생산성(개발 비용)을 크게 떨어뜨리지 않고 안전성을 높였다고 볼 수 있다.

  • 상호운용성

2. 코틀린 기초

2-1. 기본 요소: 함수와 변수

  • 코틀린은 타입 추론을 지원하기 때문에, 타입을 생략할 수 있다.

    fun max(a: Int, b: Int) = if (a > b) a else b
    
    val question = "질문"
    // val question: String = "질문"
    val answer = 0
    // val answer: Int = 0
  • val은 Immutable, var은 mutable 이다. 가급적 Immutable로 사용하는 것이 좋다.

    • 다만, val 참조가 불변이라 하더라도 그 참조가 가리키는 객체 내부값은 변경될 수 있다.

      val languages = arrayListOf("Kotlin")
      languages.add("Java")
      
      /*
      val languages = listOf("Kotlin")
      languages.add("Java") // Compile Error
      */
  • Kotlin은 문자열 템플릿 기능을 지원한다.

    • 아래 코드와 같이 $ 기호를 통해 문자열 변수를 사용할 수 있다.
    fun main(args: Array<String>) {
      val name = if (args.size > 0) args[0] else "Kotlin"
    	print("Hello, $name")
    }
    • 만약 $ 문자를 문자열 내부에서 사용하고 싶다면 \ 를 사용하여 이스케이프 시켜야 한다.

2-2. 클래스와 프로퍼티

  • 간단하게 Person 클래스(value object : 코드 없이 데이터만 저장하는 클래스)가 있다면, 아래와 같을 것이다.

    • Java
    public class Person {
      private final String name;
      private final int age;
      
      public Person(String name, int age) {
        this.name = name;
        this.age = age;
      }
      
      public String getName() {
        return name;
      }
      
      public int getAge() {
        return age;
      }
    }
    • Kotlin
    class Person(
      val name: String, // 읽기 전용 프로퍼티
      var age: Int // 읽기, 쓰기가 가능한 프로퍼티
    )
    • 위와 같이, Kotlin은 불필요한 boiler plate code를 줄여줄 수 있다.
    • Kotlin 같은 경우, 기본적으로 접근 제한자는 public 이다.
    • 그렇다면 위에서 선언한 클래스를 사용해 본다. Java, Kotlin은 각각 아래와 같다.
    Person person = new Person("Bob", 32);
    System.out.println(person.getName());
    System.out.println(person.getAge());
    val person = Person("Bob", 32)
    println(person.name)
    println(person.age)
  • Kotlin은 커스텀 접근자를 제공한다.

    class Rectangle(val height: Int, val width: Int) {
      val isSquare: Boolean
      	get() {
          return height == width
        }
    }
    
    fun main() {
      val rectangle = Rectangle(40, 40)
      print(rectangle.isSquare)
    }

2-3. 선택 표현과 처리: enum과 when

  • when 구문 같은 경우, Java switch문과 대응된다. 하지만, Enum 클래스와 함께 사용할 때 Java보다 더 좋은 효율을 얻을 수 있다. 아래와 같다.

    enum class Color {
      RED, ORAGNGE, BLUE, GREEN
    }
    
    fun getMnemonic(color: Color) =
    	when (color) {
        Color.RED -> "Rechard"
        Color.ORANGE -> "Of"
        Color.BLUE, Color.GREEN -> "York"
        else -> throw Exception("Unknown Color")
      }
    
    print(getMnemonic(Color.BLUE))
  • 인자가 없는 when 구문을 사용해 불필요한 객체 생성을 막을 수도 있다.

    • 다만, when 구문에 아무 인자도 없으려면 각 분기의 조건이 반드시 Boolean 결과를 계산해야 한다.
    fun mixOptimized(c1: Color, c2: Color) =
    	when {
        (c1 == RED && c2 == ORANGE) -> ORANGE
        (c1 == BLUE && c2 == GREEN) -> GREEN
      }
  • Kotlin은 스마트 캐스트를 지원한다.

    • Kotlin에서는 is를 사용하여 변수 타입을 검사한다. Java의 instanceOf와 유사하다.
    • 다만 Java에서는 특정 변수를 instanceOf를 통해 값을 확인한 다음, 해당 타입을 사용하기 위해 명시적으로 타입을 캐스팅 해야 한다.
    • 하지만 이 과정은 여러 번 반복되면 불필요한 코드의 중복으로 이어질 수 있다.
    • Kotlin에서는 이를 컴파일러가 대신하여 캐스팅을 해준다. 이를 스마트 캐스트라 한다.
    • 당연하게도 스마트 캐스트 이후에 값이 변경될 수 없다. 따라서, 클래스의 프로퍼티에 대해 스마트 캐스트를 사용한다면 그 프로퍼티는 반드시 val 이어야 한다.
    if (e is Sum) {
      return e.right + e.left
    }

2-4. Kotlin 예외 처리

  • 코틀린의 예외 처리는 기본적으로 자바와 유사하다.

  • 하지만 코틀린이 클래스 인스턴스 변수를 생성하여 사용하듯, 예외를 던질 때에도 new 키워드를 사용할 필요가 없다.

  • 아래와 같이 try catch 구문을 바로 반환할 수 있으며, try catch 구문을 변수에 대입하는 것도 가능하다.

    fun getNumber(value: String): Int {
      return try {
        value.toInt()
      } catch (e: Exception) {
        0
      }
    }
profile
Android Developer

2개의 댓글

comment-user-thumbnail
2022년 5월 31일

인자가 없는 when 구문을 사용해 불필요한 객체 생성을 막을 수도 있다에서 불필요한 객체의 예를 들어주실수 있을까요? :)

코틀린은 처음 접해봤는데 굉장히 간결하고 편해보이네요!

1개의 답글