코틀린

Kotlin을 정복해봅시다 1

newwisdom 2021. 8. 6. 13:37
반응형

2021-05-16글

코틀린이란?

  • 자바 플랫폼에서 돌아가는 새로운 프로그래밍 언어
  • 간결하고 실용적
  • 자바 코드와의 상호 운용성을 중시
  • 코틀린 컴파일러가 생성한 바이트코드는 일반적인 자바 코드와 똑같이 효율적으로 실행

정적 타입 지정 언어

정적 타입 지정이란 모든 프로그램 구성 요소의 타입을 컴파일 시점에 알 수 있다.
프로그램 안에서 객체의 필드나 메서드를 사용할 때마다 컴파일러가 타입을 검증해준다.

코틀린은 타입추론을 지원하여 개발자가 타입 선언해야하는 불편함이 줄어든다.
매개 변수 이름 뒤에 매개 변수의 자료형을 쓴다.

private val PATTERN = Pattern.compile("//(.)\n(.*)")
private const val FIRST_TARGET_STRING = "//"
private const val LAST_TARGET_STRING = "\n"

변수 선언

  • val : 값이 변경되지 않는 변수
  • var : 값이 변경될 수 있는 변수

블록이 본문인 함수

fun max(a: Int, b: Int) : Int {
    return if (a > b) a else b
}

식이 본문인 함수

fun max(a: Int, b: Int) : Int = if (a > b) a else b

코틀린 학습 테스트

named arguments

Crew("신지혜", 20, "아마찌")

Crew 객체에 name과 nickname 필드가 있을 때 생성자에 전달하는 인자만을 보고는 어떤 문자열이 어떤 역할인지 헷갈릴 수 있다.
이는 함수의 시그니처를 살펴보지 않고는 알아내기 어려운데,
코틀린으로 작성한 함수를 호출할 때는 가독성을 높이기 위해 전달하는 인자의 이름을 명시해줄 수 있다.
이때 인자 중 어느 하나라도 이름을 명시하면 그 뒤로 오는 모든 인자는 이름을 명시해야 한다.

Person("신지혜", 20, nickname = "아마찌"),
Person(name = "신지혜", nickname = "아마찌", age = 20)

nullable types

class Person(val name: String, val age: Int?, var nickname: String?)

null이 될수 있는 type을 명시적으로 표시할 수 있다.
type에 ?를 붙임으로서 null이 가능한 변수임을 명시적으로 표현한다.

default arguments

class Person(val name: String, val age: Int? = null, var nickname: String? =null)

자바는오버로딩을 하여 인자가 다른 같은 메서드를 만든다.

하지만 코틀린은 기본 인자를 지원하기 때문에, 1개의 메소드만 정의하여 메서드 오버로딩 기능을 한다.
기본 인자를 설정하는 방법은, 다음과 같이 인자 이름 다음에 인자 = 기본값처럼 정의하면 된다.

fun add(num1, num2: Int = 0)

위의 메소드는 아래처럼 인자의 개수가 다르게 호출될 수 있습니다.

add(1)      // num2 = 0 은 기본인자로 전달
add(1, 2)

data classe

data class Person(val name: String, val age: Int? = null, var nickname: String? =null)
  • 수리 피셜 데이터 클래스는 DTO다.
  • 딱히 비즈니스 로직을 갖고 있지않다.
  • 생성자부터 getter & setter, 심지어 canonical methods까지 알아서 생성해준다.

제한 사항

  • 기본 생성자에는 최소 하나의 파라미터가 있어야 한다.
  • 기본 생성자의 파라미터는 val이나 var여야만 한다.
  • 데이터 클래스는 abstract, open, sealed, inner가 되면 안 된다.

🤔 Canonical Methods?

캐노니컬 메소드는 Any에 선언된 메소드 (Any는 자바의 Object처럼 코틀린에서 모든 객체의 조상이 되는 객체)
따라서 코틀린의 모든 인스턴스가 갖고 있는 메소드를 뜻한다.

코틀린의 data class는 모든 Canonical 메서드를 올바르게 구현하고 있다.

  • equlas(other: Any?): Boolean - 이 메소드는 참조가 아니라 데이터 클래스 간 값의 일치를 비교한다.
  • hashCode(): Int - 해쉬코드는 인스턴스의 숫자 표현이다. hashCode()가 같은 인스턴스에서 여러 번 호출될 때 항상 동일한 값을 반환해야 한다. equals()로 비교할 때 참을 반환하는 두 인스턴스는 같은 hashCode()를 가져야만 한다.
  • toString(): String - 인스턴스의 문자열 표현이다. 데이터 클래스는 이를 멤버 변수의 값을 나열하도록 자동으로 재정의 한다.
  • copy() : 객체를 복사하여 새 객체 생성
  • componentsN() : 속성을 순서대로 반환

2단계 문자열 계산기 구현하면서 배운 사실들

메서드 정의

fun 메서드명(x: Int): 반환타입 {    retunr 2 * x}

Unit / Nothing

코틀린은 원시타입과 wrapper type을 구분하지 않는다.

Unit

함수의 반환 구문이 없다는 것을 표현한다. (자바의 void에 해당).
void와는 다르게 인자로도 사용할 수 있다.

Nothing

함수가 정상적으로 끝나지 않는다라는걸 명시적으로 표현한다.

firstOrNull

컬렉션 내 첫 번째 인자를 반환한다. 단순히 리스트 내에서 첫 번째에 위치하는 인자를 반환하는 것뿐 아니라, 특정 조건을 만족하는 첫 번째 인자를 반환하도록 구성하는 것도 가능하다.

val operator = values().firstOrNull { item -> item.op == op }    ?: throw IllegalArgumentException("존재하지 않는 연산자입니다.")

자동차 경주 피드백

코틀린의 코딩 컨벤션

클래스

  • 프로퍼티
  • 초기화 블록
  • 부 생성자
  • 함수
  • 동반 객체

순으로 작성한다.

➕ ktlint 적용하기

ktlint란 코틀린을 위한 정적 분석 도구로, 코틀린으로 작성한 코드의 스타일 검사와, 형식에 맞지 않는 부분을 수정하는 기능을 제공한다.

사용하기 위해서는 다음과 같이 플러그인을 추가해준다.

plugins {    id 'org.jetbrains.kotlin.jvm' version '1.3.72'    id 'org.jmailen.kotlinter' version "3.2.0" // 추가}

플러그인을 추가하면 gradle-Tasks-formating이 생성되는데 lintKotlin을 눌러주면 lint로 코틀린 컨벤션이 틀린 부분을 잡아준다.

주생성자, 부 생성자

주 생성자

class Car(val name: String, val position: Int = 0)

클래스 이름 뒤에 오는 생성자가 바로 주 생성자이다.
주 생성자는 생성자 파라미터를 지정하고, 이에 의해 초기화되는 프로퍼티를 정의하는데 사용된다.
또한 주 생성자는 객체 초기화를 시작하는 유일한 곳이기 때문에 제공되는 인자들이 완전해야 한다.

주 생성자에는 별도의 코드를 포함시킬 수 없다.
이때 코틀린은 init 키워드로 초기화 블록을 선언할 수 있다.

class Car(val name: String, val position: Int = 0) {

    init {
        require(name.length <= MAX_NAME_LENGTH) {
            "자동차의 이름은 5글자를 초과할 수 없습니다."
        }
    // ...

부 생성자

파라미터 목록이 다른 여러 생성자를 만들 경우 부 생성자를 둘 수 있다.
부 생성자에서는 this 키워드를 사용해 주 생성자를 호출하도록 한다.

class Car(val name: String, var position: Int) {
    constructor(name: String) : this(name, 0)
}

하지만 이 경우보다는 default키워드를 사용하여 매개변수의 기본값을 사용하자.

주 생성자 호출 시점

constructor(carNUm : Int) : this(ArryaList()) {
    createList(carName)
}

위와 같이 부 생성자에서 주 생성자를 호출할 때 createList()가 실행되는 순서는 다음과 같다.

public Cars(int carNUm) {
  this(new ArrayList())
  this.createList(carNum)
}

require(), check() - 조건 확인 함수

  • require() : 식이 참이 아닐 경우 IllegalArgumentException 발생
  • check() : 식이 참이 아닐 경우 IllegalStateException 발생
  • 위 예제처럼 {} 블록에 예외 메시지를 작성할 수도 있다.

setter만 private으로 - 가시성 변경자

자바를 생각하면 필드를 private 로 만들고, getter를 통해 값을 꺼내도록 하였지만,
코틀린에서는 필드를 private으로 두기보다는 setter 함수만 private으로 지정한다.

class Car(val name: String) {    var position: Int = 0        private set}

자바는 필드 기반 언어인데 코틀린은 프로퍼티 기반 언어이다.
팁을 준다면 코틀린에서 프로퍼티에 무언가 연산이 들어간다면 함수를 만들고,
값 자체만 반환한다면 위와 같이 쓰자.

상수

기본 자료형의 상수는 const val 키워드로 표현한다.
기본 자료형 외에는 const 키워드를 사용할 수 없다.

상수는 클래스 외부에다가도 선언할 수 있다.
하지만 이 경우 확장자가 파일로 변경되니 사람 취향껏 하면 된다.

companion object - 동반 객체

코틀린에서는 static 키워드가 없는데, 이 대신 사용할 수 있는 것이 companion object 이다.
이는 객체이며, companion object 내에 선언된 속성과 함수는 {클래스 이름}.{필드/함수 이름} 형태로 바로 호출할 수 있다.
클래스의 맨 하단에 작성한다.

@JvmStatic

ompanion object를 사용하여 구성한 코드를 자바에서 사용하려면 속성 및 함수가 자바의 필드/메서드로 해석되도록 알려주어야 한다.

const 선언이 되어 있는 프로퍼티는 별도의 처리 없이 자바에서도 사용 가능하며,
함수는 @JvmStatic 어노테이션을 사용하여 자바에서 정적 메서드로 사용할 수 있게 한다.

@JvmField

const 키워드는 기본 자료형에만 사용이 가능하다.
이외의 타입 객체를 자바에서 정적 필드처럼 사용하려면 @JvmField 어노테이션을 사용해야 한다.

Utility 클래스

자바에서는 모든 메서드를 클래스 내부에 작성해야했지만, 코틀린에서는 그럴 필요가 없다.
필요한 유틸리티 메서드만 모아놓은 파일만을 만들고 이를 파일 최상위에 위치시키면 된다.

스마트 캐스트

코틀린에서는 컴파일러가 대신 캐스팅을 해준다.
원하는 타입을 검사하고 나면, 개발자가 변수를 원하는 타입으로 캐스팅하지 않아도 해당 변수가 원하는 타입으로 선언된 것처럼 사용할 수 있다.
이는 컴파일러가 캐스팅을 수행해주어 가능한 일이다.

fun calculate(text: String?): Int {    if (text.isNullOrBlank()) {        throw IllegalArgumentException()    }    val tokens = text.split(" ")    // ...}

코드 리뷰 중 짤막한 배움

maxBy{}

fun findMaxPosition(): Int {        return cars.maxBy { it.position }!!.position    }

가장 큰 원소를 찾기 위해 비교에 사용할 값을 인자로 받는다.
모든 컬렉션에 대해 maxBy 함수를 호출할 수 있다.

{ it.position } 는 비교에 사용할 값을 돌려주는 함수이다.
maxBy{} 의 반환 값은 nullable인데, 반환된 값의 프로퍼티를 사용하고 싶은 경우 !! 키워드를 통해 null이 아님을 선언하여 꺼낸다.

참고

?:(엘비스 오퍼레이터 ) : null인 경우 설정한 default 값을 넣는다.

Pair

Kotlin에서 제공하는 객체 타입 중 연관 타입끼리 관계가 없어도 2개를 쌍으로 가지고 있는 객체

getter.first .second 또는 .component1() / .component2()로 접근할 수 있다.

RacingCar 일부

fun race(moveStrategy: MoveStrategy): Pair<List<Cars>, List<Car>> {    val carsGroup = arrayListOf<Cars>()    for (i in 0 until tryNumber) {        this.cars = this.cars.moveAll(moveStrategy)        carsGroup.add(this.cars)    }    return Pair(carsGroup, findWinners())}private fun findWinners(): List<Car> {    val maxPosition = cars.findMaxPosition()    return cars.findCarsBySamePosition(maxPosition)}

==과 ===

  • == 연산자는 자바의 equal와 같다.
  • === 연산자는 자바의 ==와 같다.

List, MutableList

코틀린에서는 읽기 전용 리스트(List)와 수정할 수 있는 리스트(MutableList)가 있다.
arrayListOf() 를 쓰기보다는 코틀린이 제공하는 List 또는 MutableList를 사용하자

List

데이터를 읽기만 가능하고 리스트를 선언할 때 넣은 데이터들을 수정, 삭제, 변경할 수 없다.

var cars = listOf(Car("ama"), Car("mazzi"))println(cars[0]) // Ocars.add(Car("new")) // X 컴파일에러cars.remove(1)   // X 컴파일에러

MutableList

기존 자바에서 사용하던 ArrayList와 유사하므로 ArrayList의 함수들을 모두 사용할 수 있다.

var carsMutable = mutableListOf<Car>()carsMutable.add(Car("ama")) // OcarsMutable.add(Car("mazzi")) // OcarsMutable.remove(0) // Oprintln(carsMutable[0]) // O

List와 MutalbeList 서로의 타입으로 변경도 가능하다. 이 때 서로 새로운 리스트를 반환한다.

var carsMutable = mutableListOf<Car>()carsMutable.add(Car("ama")) carsMutable.add(Car("mazzi")) var arrNotMutable = carsMutable.toList() //mutable -> list 변경var arrReMutable = arrNotMutable.toMutableList() // list -> mutable 변경arrReMutable.add(Car("new"))println(arrReMutable)

assertThrows

자바처럼 assertThatThrownBy 를 쓰지 말고 아래와 같이 쓰자.
이유는 assertThatThrownBy 를 사용할 경우 isInstanceOf() 를 사용하는데 여기에는 .java.class 를 붙여주어야 해 코틀린 스럽지 못하기 때문이다.

<IllegalArgumentException> {Car(name = input)}

object

클래스를 만듦과 동시에 인스턴스로 만든다. (싱글턴)

람다 작성

코틀린은 람다식을 작성할 때 {}로 표현한다.
람다의 인자가 하나라면 람다식 내부에서 it으로 받을 수 있다.
함수의 인자로 람다가 넘어올 때, 맨 마지막 순번이라면 () 밖에 쓸 수 있다.
만약 람다 하나만 받는 거라면 ()를 생략할 수 있겠죠?

val newCars = cars.map { it.move(moveStrategy) }

associate{}

map을 기본적으로 반환한다.

Input을 재귀적으로

tailrec fun inputCarNames(): List<String> {
    println("경주할 자동차 이름을 입력하세요(이름은 쉼표(,)를 기준으로 구분).")
    return readLine()?.replace(" ", "")?.split(",") ?: inputCarNames()
}

tailrec fun inputTryNumber(): Int {
    println("시도할 횟수는 몇 회인가요?")
    return readLine()?.toIntOrNull() ?: inputTryNumber()
}
  • tailrec : 꼬리재귀(tail recursive)라는 의미로, 추가적인 연산이 없이 자신 스스로 재귀적으로 호출하다가 어떤 값을 리턴하는 함수
    • 해당 키워드를 붙이면 재귀적인 함수 호출의 최적화가(?) 일어남

중위 함수 (to 키워드)

  • 중위 표현법 : 변수와 변수사이에 함수를 넣어 연산자 처럼 사용하는 것

조건

  • 멤버 메서드 또는 확장 함수여야 함
  • 하나의 매개변수를 가져야함
  • infix 키워드를 사용하여 정의

ex) Pair 객체를 생성할 때 to

by 키워드

위 코드에서 Car의 일급 컬렉션인 Cars를 순회할 때 현재는 List<Car>에 접근하여 순회하고 있다.
하지만 코틀린에서는 다음과 같이 변경할 수 있다.

위와 같이 cars 일급 컬렉션을 바로 순회할 수 있는 이유는 Cars가 다음과 같이 List<Car> 인터페이스를 구현하고 있기 때문이다.

뭐야 완전 신기해....
제이슨이 일단 지금은 "와 뭐야" 이정도까지만 알아두어도 된다고 하였다.
코틀린 짱

반응형