본문 바로가기
Study/다재다능 코틀린 프로그래밍

Day 2. Java 개발자를 위한 코틀린 필수 사항

반응형
본 포스트는 지인들과 스터디한 내용을 정리한 포스트입니다

 

http://www.kyobobook.co.kr/product/detailViewKor.laf?mallGb=KOR&ejkGb=KOR&barcode=9788931463422 

 

다재다능 코틀린 프로그래밍 - 교보문고

다양한 프로그래밍 패러다임으로 살펴보는 코틀린 기본서 | 코틀린은 멀티패러다임 프로그래밍 언어다. 코틀린은 스크립트로 사용할 수도 있고, 객체지향 코드나 함수형 코드, 비동기 프로그램

www.kyobobook.co.kr


1. Java와 다른 Kotlin 특징

  • Java에서 사용하던 익숙한 버릇을 버리고 다음 내용들을 살펴보자
  • 코틀린은 개발자가 변수를 만들때 뮤터블(변경 가능), 이뮤터블(변경 불가능)인지 선택해야한다
  • 뮤터블 변수 사용을 줄이기 위해 명령문보다는 표현식을 선호한다

 

2. 더 적은 타이핑

  • 세미콜론은 생략해도 된다
    • 모든 표현식이나 명령문을 세미콜론으로 끝낼 필요가 없다
    • 한 줄에 두 개 이상의 표현식이나 명령문을 사용할 때만 필요
    • 물론 사용한다고 오류는 안나지만.. 사용하지 않는 게 좋다
  • 변수 타입 지정은 생략해도 된다
    • 코틀린은 정적 타입 언어지만 반드시 변수의 타입을 꼭 지정해야 한다는 뜻은 아니다
    • 정적 타입이란 변수의 타입이 컴파일 시점에 검증되고 정확해져야 한다는 의미
    • 코틀린은 컨텍스트에 기반한 스마트한 타입 추론 기능을 가지고 있다
    • 타입 확인 코드
      val greet = "hello"
      println(greet)
      println(greet::class) // 변수에 의해 참조되고 있는 객체의 코틀린 클래스
      println(greet.javaClass) // Java 클래스
       
      • 코틀린의 타입 추론 기능이 변수에 할당된 값에 기반하여 greet라는 변수의 타입을 String이라고 정의했다
    • 타입 추론의 오해
      • 코틀린은 컴파일 시점에 타입 추론을 한다 ( 실행 시간에 확인하는 것이 아님, 성능 저하 X )
      • 예제코드 
        • var로 "greet" 라는 뮤터블 객체를 선언 및 초기화
        • 코틀린의 타입추론으로 인해 "greet" 변수는 String type으로 취급받는다
        • 다시 정수 0을 할당하려고하자 컴파일 오류 발생
    • 변수의 이름에 타입 관련된 정보를 표현하지 말자 ( 내부 변수는 어짜피 나만 쓰는 것 )
    • 타입 정보 생략이 가능한 경우 타입을 생략, 대신 적절한 변수 이름을 짓고 타입 추론을 사용하자
  • 클래스와 함수는 생략 가능하다
    • Java 언어와는 다르게, 코틀린은 명령문이나 표현식이 메소드에 속할 필요가 없고 메소드 또한 클래스에 소속될 필요가 없다
    • 코드가 컴파일되거나, 스크립트로 실행될 때 코틀린은 JVM에서 실행하기 위해 필수적으로 필요한 랩퍼 클래스와 메소드를 생성한다
    • 예제 코드
      // standalone.kts
      import java.lang.Exception
      import java.lang.RuntimeException
      
      fun nofluff() {
          println("nofluff called...")
          throw RuntimeException("oops")
      }
      println("not in a function, calling nofluff()")
      try {
          nofluff()
      } catch (ex:Exception) {
          val stackTrace = ex.stackTrace
          println(stackTrace[0])
          println(stackTrace[1])
      }
    • 실행 결과
      • 코틀린은 스스로 nofluff() 메서드를 Standalone이라는 동기화된 클래스의 메소드 안으로 넣는다
      • 클래스 이름은 파일이름을 통해 추론된다
      • 단독적으로 동작하는 코드는 <init> 즉 생성자 안으로 들어간다
  • try-catch는 선택사항이다
    • Java 컴파일러를 사용할 때는 명시적 예외(Checked Exception)를 확실하게 처리(try-catch)하거나 호출한 쪽으로 던져야 했다(throw)
    • 코틀린은 checked이든 unchecked든 상관없이 어떠한 예외도 처리하도록 강제하지 않는다
    • try-catch가 없는 함수를 호출했을 경우, 예외가 발생하면 자동으로 해당함수를 호출한 함수 또는 호출한 코드로 전달된다
    • 예제 코드
      // exceptionHandling.kts
      println("start")
      
      fun test1() {
          println("start test1")
          throw Exception("Exception")
      }
      
      fun test2() {
          println("start test2")
          test1()
      }
      
      println("start main")
      
      try {
          test2()
      } catch (ex:Exception) {
          println(ex.stackTrace[0])
      }
      println("end main")
    • 실행 결과
      • test1() 메서드가 호출되고 checked Exception을 발생시켰지만 Java와 다르게 호출부인 test2() 메서드에서 반드시 try-catch문을 쓰지않아도 자연스럽게 상위 호출부로 exception을 throw한다

 

3. 현명한 경고

  • 컴파일 시간에 빠른 경고를 받을 수 있다면 개발자들은 잠재적 문제를 사전에 대처할 수 있다
  • 코틀린 컴파일러는 코드 안의 다양한 잠재적 문제들을 찾아낸다
  • 예제 코드: 함수나 메서드에서 사용되지 않는 파라미터가 존재한다면 컴파일러가 경고해준다
    // unused.kts
    fun compute(n: Int) = 0
    println(compute(4))
    
    
    // result
    0
    unused.kts:1:13: warning: parameter 'n' is never used
    fun compute(n: Int) = 0
                ^
    
    Process finished with exit code 0​
  • 코틀린 -Werror 옵션으로 스크립트를 실행시키면 경고를 오류처럼 다룰 수 있다

 

4. var보다는 val

  • 이뮤터블 변수(상수 또는 값)을 정의하기 위해서 다음처럼 val을 사용한다
  • 코틀린은 변수의 이름이 변수의 타입보다 중요하다고 여기기 때문에 변수 이름이 타입보다 우선한다
    val pi: Double = 3.14​
     
  • 변수 타입이 명확하다면 타입추론을 이용해 : Double 구문을 생략할 수 있다
  • val
    • Java의 final과 비슷하다
    • 변수의 값을 바꾸거나 재할당하려는 시도를 한다면 컴파일 오류가 발생
    • 이뮤터블
  • var
    • 뮤터블
    • 불명예의 키워드
  • 뮤터빌리티(변경가능성)이 있는 변수는 추론하기 어렵게 만들고, 오류를 발생할 가능성을 높인다
  • val은 변수나 참조만 상수로 만든다 ( 객체 자체를 상수로 만드는 것은 불가 )
  • 즉 Java와 같이 참조에 대한 immutability만 보장해주고, 객체의 변화는 방지할 수 없다
  • 가능하면 var보다는 val을 사용하자 ( 권장 )

 

5. 향상된 동일성 체크

  • Java와 마찬가지로 코틀린도 두 가지 동일성 체크가 있다
  • 값 비교
    • Java equal()
    • 코틀린 '=='
    • 구조상의 동일성
  • 참조 대상 비교
    • Java '=='
    • 코틀린 '==='
    • 참조상의 동일성
  • 추가적으로 코틀린의 값 비교('==')는 Java의 equal()보다 뛰어나다
    • Java 코드로 str1.equal(str2)를 실행시킬 경우 str1이 null이면 NullPointException을 발생시킨다
    • 코틀린의 코드 str1 == str2는 null을 안전하게 다룬다, 즉 str1 혹은 str2이 Null이여도 NPE가 발생하지 않는다
    • 즉 코틀린에서 == 연산자를 사용할 때, null 체크를 먼저하고 equal() 메서드를 실행한다

 

6. 문자열 템플릿

  • '+' 연산자를 이용해서 값을 연결해 문자열을 만들면 코드는 장황해지고, 유지보수는 여려워진다
  • 문자열 템플릿은 이런 문제를 해결해준다
    • 큰 따옴표로 엮어진 문자열 안에서 $ 심볼을 변수 앞에 붙여주면 어떤 변수라도 문자열 안에 들어간다
      // stringTemplete.kts
      val temp1 = "hi"
      val temp2 = 30
      val temp3 = 3.0
      
      val str1 = "$temp1 my age is $temp2, $temp3"
      println(str1)​
    • 단순 표현식말고 복잡한 수식을 넣으려면 ${...}로 감싸서 사용하면 된다
      val temp1 = "hi"
      val temp2 = 30
      val temp3 = 3.0
      
      var str2 = "$temp1 => ${temp2 * temp3}"
      println(str2)​
    • $심볼 뒤에 변수이름이나 표현식이 없으면 $는 그대로 문자 취급된다, 또한 역슬래시를 이용해서 $심볼을 명시적으로 문자취급할 수 있다
      val temp1 = "hi"
      val temp2 = 30
      val temp3 = 3.0
      
      var str3 = "\$temp1 => \${temp2 * temp3}$"
      println(str3)
      
      
      // result
      // $temp1 => ${temp2 * temp3}$
  • 문자열 템플릿은 변수로 초기화될 때 사용되므로 만들어질 당시의 변수값으로 된다, var(뮤터블)를 이용하여 문자열을 만든 후 값을 변경하더라도 변경 전 변수의 값으로 된다 ( var 대신 val을 사용해야하는 이유 )

 

7. RAW 문자열

  • 이스케이프 문자
    • 이스케이프 문자를 사용하면 코드가 지저분해진다
    • 코틀린에서는 이스케이프 문자를 사용하는 대신 시작과 끝에 큰따옴표 세 개를 이옹해 raw 문자열을 사용할 수 있다
    • 예제 코드
      // escapeString.kts
      val name = "joojimin"
      val escapeStr1 = "The kid asked, \"How's it going, $name?\""
      println(escapeStr1)
      
      val escapeStr2 = """The kid asked, "How's it going, $name?""""
      println(escapeStr2)
      
      // results
      The kid asked, "How's it going, joojimin?"
      The kid asked, "How's it going, joojimin?"​
    • 이스케이프할 필요가 없는 작고, 단순하고, 간단한 문자열이라면 ""로 작성하자
    • 문자열이 복잡하거나 여러 줄을 써야할 경우엔 raw 문자열(""" """)을 사용하자
  • 멀티라인 문자열
    • 기존 멀티라인 문자열을 만들기 위해서는 '+'를 이용하여 멀티 라인을 구성해야했다
    • 코틀린에서는 raw 문자열(""" """)을 이용해 '+' 연산자 없이도 멀티라인 문자열을 만들 수 있다
    • 예제 코드
      // MultiLineString.kts
      val name = "Eve"
      
      val memo = """Dear $name, a quick reminder about the
          party we have scheduled next Tuesday at
          the 'Low Ceremony Cafe' at Noon. | Please plan to..."""
      println(memo)​
    • 결과
      • 여기서 문제가 있다, String을 표현하기 위해서 라인을 추가했지만 코드 포메터에 의해 두번째, 세번째 라인에 들여쓰기가 포함이 됐다 ( 혹은 뎁스가 늘어나면서 표현하기 싫은 들여쓰기가 추가된 경우도 마찬가지 )
      • 이를 해결하기 위해 각 라인 앞에 default 들여쓰기 제거 문자 | 을 추가하고 trimMargin() 메서드를 통해 들여쓰기를 제거하면된다 ( 들여쓰기 제거 문자를 바꾸고 싶으면 trimMargin 파라미터로 넘기면된다 )
      • val name = "Eve"
        
        val memo = """Dear $name, a quick reminder about the
            |party we have scheduled next Tuesday at
            |the 'Low Ceremony Cafe' at Noon. | Please plan to...""".trimMargin()
        println(memo)
      • 줄바꿈이 시작되는 첫번째 문자에만 적용된다

 

8. 표현식은 많이, 명령문은 적게

  • Java, C#, JavaScript 같은 언어들은 표현식보다는 명령문을 더 많이 가지고 있다( if문, for문, try문 등등 )
  • 명령문은 아무것도 리턴하지 않으며, 상태가 변하고, 변수를 변하게 하고 등등 어두운 면을 가지고 있다
  • 반면 표현식은 결과를 리턴해주고, 어떠한 상태도 변화시키지 않는다
  • 기존 명령문 코드 형식 ( 코틀린 )
    fun canVote(name: String, age: Int): String {
        var status: String
        if ( age > 17 ) {
            status = "yes, please vote"
        } else {
            status = "nope, please come back"
        }
        return "$name, $status"
    }
    println(canVote("Eve", 12))​
  • 위 코드를 표현식으로 변경한 형식 ( 코틀린 )
    fun canVoteWithExpressionStyle(name: String, age:Int): String {
        val status = if (age > 17) "yes, please vote" else "nope, please come back"
        return "$name, $status"
    }
    println(canVoteWithExpressionStyle("Eve", 12))​


  • 명령문보다 표현식을 활용하면 var대신 val를 사용할 수 있을 뿐더러, 훨씬 간결한 코드가 작성되는 것을 예제로 확인해 볼 수 있다

 

728x90
반응형