Kotlin In Action (2)

sixhustle·2021년 2월 18일
0

Kotlin

목록 보기
2/2

함수, val, var

함수를 정의할 때 fun 키워드를 사용합니다. val은 읽기 전용 변수, var은 변경 가능한 변수입니다.

fun main(args: Array<String>) {
  println("Hello, World!")
}
  • 함수를 선언할 때 fun 키워드를 사용합니다.
  • Parameter이름 뒤에 Type을 명시합니다.
  • 함수를 최상위 수준에 정의할 수 있습니다. class안에 함수를 넣지않아도 됩니다.
  • 표준 자바 라이브러리 함수를 간결하게 사용할 수 있는 wrapping된 코틀린 표준 라이브러리를 제공합니다. println도 그런 함수 중 하나입니다.
  • 세미콜론(;)을 붙이지 않아도 됩니다.

함수

  • 문(statement) : 아무런 값을 만들어내지 않습니다. Java에선 if, for, while 문은 조건을 확인하는 것이지 값을 바꾸진 않습니다.
  • 식(expression) : 값을 만들어 냅니다.

블록이 본문인 함수

fun max(a: Int, b: Int): Int {
   return if ( a > b ) a else b
}
  • 함수의 반환 타입은 파라미터 목록의 닫는 괄호 다음에 오고, 괄호와 타입 사이를 콜론(:)으로 구분합니다.
  • 위 처럼 중괄호로 둘러싸인 함수를 블록이 본문인 함수라고 부릅니다.

식이 본문인 함수

if는 코틀린에서 식이며, 값을 만들어 냅니다.

fun max(a: Int, b: Int): Int = if (a>b) a else b
  • 중괄호를 없애고 return을 제거하면서 등호(=)를 식 앞에 붙여서 표현할 수 있습니다.
  • 위처럼 등호와 식으로 이뤄진 함수를 식이 본문인 함수라고 합니다.

타입 추론 : 반환 타입 생략

fun max(a: Int, b: Int) = if (a>b) a else b
  • 식이 본문인 함수의 경우 컴파일러가 함수 본문 식을 분석해서 결과 타입을 함수 반환 타입으로 정해줍니다. (변수도 동일합니다.)
  • 이 처럼, 컴파일러가 타입을 분석해 정해주는 기능을 타입 추론이라고 부릅니다.

변수

// 초기화 식
val question = "삶, 우주, 그리고 모든 것에 대한 궁극적인 질문" // String
val answer = 42 // Int
val yearsToCompute = 7.5e6 // Double

// 초기화 식 X
val answer: Int
answer = 42
  • 컴파일러가 초기화 식을 분석해서 초기화 식의 타입을 변수 타입으로 지정합니다.
  • 초기화 식을 사용하지 않고 변수를 선언하려면 변수 타입을 반드시 명시해야 합니다.
  • 값이 없으면, 컴파일러가 타입을 추론할 수 없습니다.

val

val languages = arrayListOf("Java") // 불변 참조
languages.add("Kotlin") // 참조가 가리키는 객체 내부를 변경
  • 변경 불가능한 참조를 저장하는 변수입니다. Java final 변수에 해당합니다.
  • 변경 불가능한 참조가 가리키는 객체의 내부 값은 변경할 수 있습니다.

var

  • 변경 가능한 참조로 변수의 값은 변경될 수 있습니다.
  • 변수의 타입은 고정돼야합니다. Int로 선언된 변수에 String값을 대입할 수 없습니다.

문자열 템플릿

문자열 템플릿을 사용하면, 문자열을 연결하지 않아도 되므로 코드가 간결해집니다. 변수 이름 앞에 $를 붙이거나, 식을 ${식}처럼 ${}로 둘러싸면 변수나 식의 값을 문자열 안에 넣을 수 있습니다.

fun main(args: Array<String>) {
   val name = if (Args.size > 0) args[0] else "Kotlin"
   println("Hello, ${name}!") // Hello, Bob!
}
  • 문자열 템플릿의 기능을 사용한 예제입니다.
  • "Bob"을 인자로 넘기면 "Hello, Bob!"이 출력됩니다.
  • 존재하지 않는 변수를 사용하면 컴파일 시점에 컴파일 오류가 발생합니다.
  • 변수는 { } 로 감싸는 습관을 들이길 권장합니다.

클래스와 프로퍼티

코틀린에서는 값 객체 클래스를 아주 간결하게 표현할 수 있습니다.

프로퍼티

  • 필드(name)와 접근자(getter/setter)를 묶어서 부르는 개념입니다.
  • 코틀린은 프로퍼티를 언어 기본 기능으로 제공합니다.
  • 아래의 Java / Kotlin은 동일한 코드입니다.

Java

public class Person {
   private String name;
   private Boolean isMarried;

   public Person(String name) {
      this.name = name;
   }
   
   public String getName() {
      return this.name;
   }
   
   ...
   
}

Person person = new Person("Bob", true);
person.getName(); // Bob
person.isMarried(); // true

Kotlin

1  class Person(
2     val name: String,
3     var isMarried: Boolean
4  )

5  val person = Person("Bob", true)
6  person.name // Bob
7  person.isMarried // true
8  person.isMarried = false
9  person.isMarried // false

2 : val 읽기 전용 프로퍼티입니다. private field + public getter를 제공합니다.
3 : var private field + public getter/setter를 제공합니다.
5 : new 키워드없이 생성자를 호출합니다.
6 : 프로퍼티 이름을 직접 사용해도 코틀린이 자동으로 getter를 호출해줍니다.
8 : setter도 마찬가지로 프로퍼티 이름을 직접 사용해도 자동으로 setter를 호출해줍니다.

커스텀 접근자

class Rectangle(val height: Int, val width: Int) {
   val isSquare: Boolean
      get() {
         return height == width
      }
      // get() = height == width // 위의 get과 동일
}

val rectangle = Rectangle(41, 43)
rectangle.isSquare // false
  • 위는 isSquare의 get() 프로퍼티를 선언한 것입니다.

pacakge & import

package

  • 클래스를 관리하는 단위 입니다.
  • 같은 패키지에 속해 있다면, 다른 파일에서 정의한 선언일지라도 직접 사용할 수 있습니다.

import

  • 다른 패키지에 정의한 선언을 사용하기 위한 문입니다.

when과 enum

when은 자바의 switch와 비슷하지만, 더 강력합니다.

eunm class Color (
   val r: Int, val g: Int, val b: Int
) {
   RED(255, 0, 0),
   ORAGNE(255, 165, 0);
   fun rgs() = (r*256 + g) * 256 + b
}
  • enum을 정의할 때는 상수에 해당하는 프로퍼티 값을 지정해야 합니다.
  • enum안에 메소드를 정의할 경우, 상수와 메소드 사이에 세미콜론(;)이 필수입니다.
fun getMnemonic(color: Color) = 
   when (color) {
      Color.RED -> "Richard"
      Color.ORAGNE -> "Of"
      Color.YELLOW -> "York"
      Color.GREEN -> "Gave"
      Color.BLUE -> "Battle"
      Color.INDIGO -> "In"
      Color.VIOLET -> "Vain"
   }
  • 빨주노초파남보 > Richard Of Yowrk Gave Battle in Vain 변환하는 코드입니다.
  • if와 마찬가지로 when도 값을 만들어내는 식입니다.
fun getWarmth(color: Color) = when(color) {
   Color.RED, Color.ORANGE -> "warm"
   Color.GREEN -> "neutral"
}
  • 콤마(,)를 사용하여 한 분기 안에 여러 값을 매치 패턴으로 사용할 수 있습니다.
fun mix(c1: Color, c2: Color) =
   when (setOf(c1, c2)) {
      setOf(RED, YELLOW)  -> ORANGE
      setOf(YELLOW, BLUE) -> GREEN
      else -> throw Exception("Dirty Color")
   }
  • 분기 조건으로 임의의 객체를 허용합니다.(Java는 상수만을 허용합니다.)
  • 만족하는 분기 조건을 찾지 못하면, else 분기의 문장을 계산합니다.
fun mixOptimized(c1: Color, c2: Color) =
   when {
      (c1 == RED && c2 == YELLOW) ||
      (c1 == YELLOW && c2 == RED) -> ORANGE
      (c1 == YELLOW && c2 == BLUE) ||
      (c1 == BLUE && c2 == YELLOW) -> GREEN
      else -> throw Exception("Dirty Color")
  • 바로위의 setOf의 경우 분기를 실행할 때 마다 Set인스턴스를 생성하여 가비지 객체가 늘어납니다.
  • 인자가 없는 when식을 사용하면 불필요한 객체 생성을 막을 수 있습니다.

Smart cast

어떤 변수의 타입을 검사하고 나면 그 변수를 캐스팅하지 않아도 검사한 타입의 변수처럼 사용할 수 있습니다.
그런 경우 컴파일러가 스마트 캐스트를 활용해 자동으로 타입을 바꿔줍니다.

interface Expr
class Num(val value: Int) : Expr
class Sum(val left: Expr, val right: Expr) : Expr

fun eval(e: Expr): Int {
   if(e is Num) {
      val n = e as Num
      return n.value
   }
   if(e is Sum) {
      return eval(e.right) + eval(e.left)
   }
   throw IllegalArgumentException("Unknown expression")
}
   
  • is로 검사하고 나면 컴파일러가 캐스팅을 수행해줍니다. 이를 스마트 캐스트라고 부릅니다.
  • is를 사용해 변수 타입을 검사합니다. (Java의 instanceof와 비슷합니다.)
  • 스마트 캐스트를 사용한다면 프로퍼티는 반드시 val이어야 합니다.
  • 프로퍼티 접근에 대한 값이 항상 같은 값을 내놓기위해 커스텀 접근자를 사용할수도 없습니다. --> 이유 이해가 안갔음..
fun eval(e: Expr): Int =
   when (e) {
      is Num -> e.value
      is Sum -> eval(e.right) + eval(e.left)
      else -> throw IllegalArgumentException("Unknown expression")
   }
  • if를 when으로 변경한 리팩토링 코드입니다.

Iteration

for, while, do-while 루프는 자바가 제공하는 같은 키워드의 기능과 비슷합니다.
하지만 코틀린의 for는 자바의 for보다 더 편리합니다. 특히 맵을 이터레이션하거나 이터레이션하면서 컬렉션의 원소와 인덱스를 함께 사용해야 하는 경우 코틀린의 for가 더 편리합니다.

fun fizzBuzz(i: Int) = when {
   i % 15 == 0 -> "FizzBuzz "
   i % 3 == 0 -> "Fizz "
   i % 5 == 0 -> "Buzz "
}

// 1부터 100까지 1씩 증가
for ( i in 1..100 ) {
   fizzBuzz(i)
}

// 100부터 1까지 2씩 감소
for ( i in 100 downTo 1 step 2 ) {
   fizzBuzz(i)
  • kotlin은 루프에 범위를 사용합니다.
  • 1..100은 폐구간으로 1부터 100까지 포함하는 구간입니다.
  • 어떤 범위에 속한 값을 일정한 순서로 iteration하는 경우를 수열이라고 부릅니다.
  • step은 증가 값을 의미합니다.
  • 100 downTo 1은 역방향 수열을 의미합니다. (기본 증가 값은 -1입니다.)

Map에 대한 Iteration

val binaryReps = TreeMap<Cahr, String> ()

for (c in 'A'..'F') {
   val binary = Integer.toBinaryString(c.toInt())
   binaryReps[c] = binary
}

for ((letter, binary) in binaryReps) {
   println("${letter} = ${binary}")
}
  • ..연산자는 문자 타입의 값에도 적용할 수 있습니다. (A..F : A부터 F까지)
  • get/put을 사용하는 대신 binaryReps[c]/binaryReps[c] = value를 사용할 수 있습니다.
val list = arrayListOf("10,", "11", "1001")
for ((index, elements) in list.wintIndex()) {
   println("${index}: ${element}")
  • 위 코드는 인덱스와 함께 컬렉션을 이터레이션한 코드입니다.
  • 구조 분해 구문은 컬렉션에도 활용할 수 있습니다.

Exception

코틀린 예외 처리는 자바와 비슷합니다. 다만 코틀린에서 함수가 던질 수 있는 예외를 선언하지 않아도 됩니다.

if (percentage !in 0..100) {
   throw IllegalArgumentException("A percentage ...")
}
  • 예외 인스턴스를 만들 때도 new를 붙일 필요가 없습니다.
  • throw는 식입니다. 따라서 다른 식에 포함될 수 있습니다.
fun readNumber(reader: BufferedReader) {
   val number = try {
      Integer.parseInt(reader.readLine())
   } catch (e: NumberFormatException) {
      return
   }
}
  • 코틀린에서는 체크 예외를 함수에 명시하지 않아도 됩니다. (ex: IOException)
  • try도 식입니다. 따라서 try의 값을 변수에 대입할 수 있습니다.

References

  • Kotlin in Action

0개의 댓글