Kotlin/Kotlin | Spring 학습기록

[Kotlin] Java와 비슷하면서 다른 코틀린 문법 정리하기 (1)

kth990303 2022. 5. 2. 01:43
반응형

코틀린은 제트브레인에서 개발한 자바와의 상호 운용이 100% 지원되는 언어이다. 네이버, 카카오, 우아한형제들 등 많은 기업들이 백엔드 언어로 코틀린을 선택한 만큼 인기있는 언어이기도 하다. 이번 포스팅에서는 자바와 비슷하지만, 조금은 다르기도 한 코틀린 문법들을 나를 위해서 정리해보려 한다.


정적 메서드를 사용하기 위한 클래스가 필요없다

java에서는 utils 메서드를 만들기 위해 아예 utils 클래스를 만들어주어야 한다. 이는 main 함수에서도 마찬가지이다. 자바를 공부하는 사람이라면, 메인 클래스를 생성해준 후 psvm을 입력해주어 메인 메서드를 만드는 작업을 지겹도록 했을 것이다.

 

하지만 코틀린은 정적 메서드를 사용할 때 따로 클래스를 만들어주지 않아도 된다.

다음 코틀린 코드를 보자.

main함수를 위한 클래스가 필요없는 코틀린 코드

위와 같이 정적 메서드는 아예 함수로만 만들 수 있다

코틀린에는 static이란 키워드가 존재하지 않는다. 그리고 void형과 같이 반환타입이 없는 경우 생략할 수 있다. main 메서드에 있었던 args도 코틀린에선 작성하지 않아도 된다.

참고로 코틀린에서 함수는 fun으로 시작하면 된다.


변수 타입은 val 또는 var

java에서는 정수 타입은 int, 문자열은 String과 같이 타입을 지정해주어야 한다. 하지만, 코틀린은 타입 추론이 가능하다는 장점 덕분에 val, var만으로 타입을 지정해줄 수 있다. val은 불변 변수, 즉 상수를 의미하며 value이다. var은 가변 변수, variable이다.

val a = 3
var b = 2

a++  // compile error
b++  // b = 3

참고로 static final로 지정해주고 싶다면 const val로 작성해주면 된다.

const val = static final


생성자는 init, constructor로! VO, DTO와 같은 클래스는 data class!

class Cars(private val cars: List<Car>) {
    init {
        validateCarNames(cars.map { car -> car.name })
    }

    // ...
}

Java에서는 인스턴스 변수를 지정할 때 클래스 내부에 작성해주지만, 코틀린에서는 위와 같이 클래스명 옆의 괄호 안에 지정해준다. 파라미터의 타입은 일반 변수와 달리 변수명 옆에 따로 작성해주어야 한다는 점을 유의하자.

 

위와 같이 인스턴스 객체를 생성할 때 원하는 작업을 해주기 위한 init 함수를 이용할 수 있다.

매개변수가 없고 별도로 리턴하는 값이 없는 함수이다.

data class Person(val name: String, val age: Int, val nickname: String = name)

 

 

만약 DTO와 같이 POJO class이거나, VO와 같이 원시값을 포장해주는 객체여서 equals, hashcode 재정의를 해주어 데이터로 이용된다면 위와 같이 클래스 왼쪽에 data 키워드를 이용하여 데이터 클래스로 지정이 가능하다.

 

코틀린의 data class는 자동으로 getter, setter, 생성자들을 만들어줄 뿐 아니라, canonical methods (equals, hashcode, toString 함수)들까지 적절하게 구현해준다.

@Test
fun constructor() {
    // 생성자 자동 생성
    assertDoesNotThrow {
        Person("kth990303", 23, "K")
    }
    // 파라미터 디폴트 값 지정 가능
    assertDoesNotThrow {
        Person("kth990303", 23)
    }
    // 파라미터 순서 변경 가능
    assertDoesNotThrow {
        Person(age = 23, name = "kth990303", nickname = "K")
    }
}

data class Person을 이용한 간단한 테스트이다. 우리는 별도로 생성자를 만들어주지 않았음에도 불구하고 Person("kth990303", 23, "K")가 잘 생성됨을 확인할 수 있다. 그리고 nickname을 따로 파라미터로 넘겨주지 않은 Person("kth990303", 23) 또한 잘 생성되는 것을 확인할 수 있다. 이는 Person 클래스의 인스턴스 속성에 '='를 이용하여 디폴트 값을 지정해주었기 때문이다. (val nickname: String = name, 닉네임을 별도로 지정해주지 않으면 name과 같이 하도록) 마지막으로 코틀린에선 속성명을 올바르게 적어준다면 파라미터 순서를 변경할 수도 있다.

// equals, hashcode 재정의 확인
@Test
fun `data class`(){
    val person1 = Person("kth990303", 23, "K")
    val person2 = Person("kth990303", 23, "K")
    assertThat(person1).isEqualTo(person2)
}

// getter, setter 작업 가능
@Test
fun setter() {
    val person = Person("kth990303", 23, "K")
    assertThat(person.phoneNumber).isEmpty()
    person.phoneNumber="010-1234-5678"
    assertThat(person.phoneNumber).isEqualTo("010-1234-5678")
}

data class로 만들어주었기 때문에 person1, person2의 주소값이 달라도 이름, 나이, 닉네임이 같으면 같은 사람으로 인식하게 해준다.

또한 getter, setter과 같은 메서드들도 자동으로 생성된 것을 확인할 수 있다. 만약 getter, setter를 만들어주고 싶지 않다면 속성을 private으로 선언해주면 된다.


변수를 출력할 때, + 기호가 아닌 $ 기호로!

private const val WINNER_MESSAGE = "가 최종 우승했습니다."

private fun printCarPositionInfo(car: Car) {
    println("${car.name} : ${"-".repeat(car.position)}")
}

fun printWinners(winners: List<Car>) {
    println("${getWinnerNames(winners)} $WINNER_MESSAGE")
}

fun getWinnerNames(winners: List<Car>): String{
    return winners.joinToString(separator = ", ") { winner -> winner.name }
}

java에서는 변수 속성을 출력할 때 + 기호를 이용해야 하지만, kotlin에서는 ${}을 이용하여 한 따옴표 안에 출력하게 할 수 있다.

이는 함수 리턴값에서도 적용할 수 있다. getWinnerNames 함수에서 우승자들의 이름마다 ", "을 join해준 String 값을 반환해주면 printWinners 함수에서 그 값을 ${}로 받아 출력해주는 모습이다. printWinners 함수의 $WINNER_MESSAGE를 보면 알 수 있듯이, 단순한 출력문을 따옴표 안에 출력할 때는 중괄호를 생략할 수 있다.


예외를 발생시키자! check(), require()

위 코드는 자동차 이름이 공백이거나 5자 초과일 경우 IllegalArgumentException 예외를 발생시키는 코드이다.

java에선 if문으로 검증하고 만족하지 않을 경우 throw new IllegalArgumentException(MESSAGE)와 같은 방식으로 작성했다면, 코틀린은 위와 같이 간편하게 작성할 수 있다.

require문은 '~~ 조건을 만족하는 애가 필요하다.' 로 해석하면 편하다. require문을 만족하지 못할 경우 IllegalArgumentException을 중괄호 안에 있는 메시지와 함께 발생시킨다.

check문은 require문과 유사하나, IllegalStateException을 발생시키는 함수이다.

만약 null값도 함께 검사하고 싶다면 requireNotNull(), checkNotNull() 함수를 실행하면 된다. 다만, 이 때 파라미터에 T? 와 같이 물음표를 붙여주어야 하는데, 물음표와 느낌표의 의미에 대해서는 2탄에서 자세히 포스팅할 예정이다.


try-catch문을 바로 리턴할 수 있다

fun getCarInput(): Cars {
    val userInput = getCarNamesByUser()
    return try {
        Cars(generateCars(userInput))
    } catch (e: IllegalArgumentException) {
        printErrorMessage(e.message)
        getCarInput()
    }
}

java에서는 try문 안에서 return을 해주어야 하지만, 코틀린에서는 return try로 바로 리턴할 수 있다.

이러한 디테일도 기억해두면 좋을 듯하다.

 

또한 코틀린에서는 try문을 식으로도 이용 가능하다.

val cars = try {
    Cars(generateCars(userInput))
} catch (e: IllegalArgumentException) {
    printErrorMessage(e.message)
    getCarInput()
}

try문을 변수에 담아주는 것이 코틀린에서는 가능하다.


알면 알수록 신기한 코틀린.

자바 코드와 코틀린 코드를 혼용해서 사용 가능하다는 장점과, command + shift + a에서 Convert java code to kotlin을 통해 자동 변환도 IDE에서 제공(다만, import java를 완벽하게 변환해주진 않는다.) 해준다는 점이 인상깊었다.

 

2탄에서는 stream api를 대체하는 함수들, random 함수, 코틀린에서의 물음표(?)와 느낌표(!!), 코틀린 테스트 문법 (io.kotest.assertions)에 대해 포스팅할 예정이다.

 

어서 코틀린 문법을 코틀린답게 사용할 수 있는 실력을 만들고 싶다 ㅎㅎ

반응형