kotlin/기초

kotlin 문법 기초 - 2

blogger903 2024. 7. 14. 00:38
728x90

kotlin 개발전 문법관련 기초내용 정리 포스트입니다

인프런 - 코틀린 문법부터 실무까지 (자바 to 코틀린 실무) 강의를 보면서 예시 코드를 다르게 하여 포스팅합니다
typescript, java 개발 경험이 있습니다

다루는 내용

  • 함수
  • 이름 붙인 파라미터
  • 디폴트 파라미터
  • 확장함수
  • 가변인자
  • 중위호출
  • 구조 분해

함수

반환타입

기본적으로 반환타입을 명시하되, 반환 타입 추론도 가능합니다
그러나, 함수에서 {} 에서는 명시적으로 return이 없으면 Unit을 반환합니다
반환타입을 명시안하는 경우에는 Unit입니다

// single expression function (단일 표현식 함수)
// 반환 타입 명시
fun add(a: Int, b: Int): Int {
    return a + b
}

// 반환 타입 추론 (권장: 간단한 함수의 경우)
fun multiply(a: Int, b: Int) = a * b

// Unit 반환 (명시적)
fun printHello(): Unit {
    println("Hello")
}

// Unit 반환 (암시적)
fun printWorld() {
    println("World")
}

// Unit을 변수에 할당
val result = printHello()

fun main() {
    println(add(2, 3))  // 출력: 5
    println(multiply(2, 3))  // 출력: 6
    printHello()  // 출력: Hello
    printWorld()  // 출력: World
    println(result)  // 출력: kotlin.Unit
}

이름 붙인 파라미터

이름 붙인 인자를 사용하면
순서와 상관없이 인자를 입력할수 있다.
파라미터가 많아져도 파라미터 순서 알필요없다

fun namedParam(x: Int, y: Int, z: Int) = x + y + z

fun main() {
    println(namedParam(x = 1, y = 2, z = 3))
    println(namedParam(z = 3, x = 1, y = 2))
    println(namedParam(1, 2, 3))
}

디폴트 파라미터

fun namedParam(x: Int = 1, y: Int = 2, z: Int= 3) = x + y + z

fun main() {
    println(namedParam())
}

typescript에서는 이미 사용되지만 java에서는 못본것 같음
가독성 좋은 코드 작성하기에 좋은 재료

코틀린에서는 변수나 함수가 클래스 내에 있지 않고 최상위에 있을 수 있다

named parameter와 default parameter는 테스트 코드 작성할때 엔티티를 생성하고 조회하고 업데이트하고 삭제하는 일이 대부분입니다.
그럴때마다 항상 엔티티를 생성해주는건 너무 귀찮습니다. dummy엔티티를 생성할수 있게 코드를 작성하고 default parameter로 기본값을 넣어준 후
테스트 케이스에 필요한 필드값만 named parameter로 넣어주면 테스트 케이스 작성하는데 가독성있고 응집도 있는 코드로 빠른속도로 테스트코드 작성할수 있습니다

 

그리고 Kotlin Fill Class 라는 plugin을 사용하면 dummy 값을 생성해주는 plugin도 사용하면 좋습니다

 

참고: https://d2.naver.com/news/2459981

확장함수

굉장히 쿨한 기능

fun String.double() = this + this

fun main() {
    println("Hello".double())
    println(setOf(1,2,3).maxOrNull())
}

기존 클래스를 상속하지 않고서 기능 확장이 가능합니다.
내부적으로 정적 메서드로 만들어서 제공하는 것이기에 확장함수는 오버라이드를 할수 없습니다.
maxOrNull는 코틀린에서 제공하는 확장함수입니다

확장 프로퍼티도 가능은 합니다

val String.lastChar: Char
    get() = get(this.length-1)

도메인 엔티티를 응답 객체로 반환해야할때 Mapper 클래스로 static 메서드를 많이 이용했었는데,
코틀린에서는 확장함수가 가능하니 Mapper 대안으로 쓰면 좋고 사실 Mapper를 쓸 필요도 없어짐

 

확장함수 good practice 1

단일 파라미터인 경우 

적용전

fun getLocalDateTimeString(localDateTime: LocalDateTime): String {
    return localDateTime.format(
        DateTimeFormatter
            .ofPattern("yyyy-MM-dd 탄생")
    )
}

 

적용후

fun LocalDateTime.toBirthDayString() = this.format(
    DateTimeFormatter
        .ofPattern("yyyy-MM-dd 탄생")
)

 

사용하는 곳에서 굉장히 쿨해집니다.

적용전

internal class DateTimeUtilsTest : StringSpec ({
    "DateTimeUtil 출력 검증" {
        val result = getLocalDateTimeString(
            LocalDateTime.of(2023, 12, 21, 10, 10)
        )
        result shouldBe "2023-12-21 탄생"
    }
})

적용후

internal class DateTimeUtilsTest : StringSpec ({
    "DateTimeUtil 출력 검증" {
        val result =
            LocalDateTime.of(2023, 12, 21, 10, 10).toBirthDayString()
        result shouldBe "2023-12-21 탄생"
    }
})


자바 코드에 적용

적용전

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class PrinceDetailDto {
    private PrinceLevel princeLevel;
    private SkillType skillType;
    private Integer experienceYears;
    private String princeId;
    private String name;
    private Integer age;
    private StatusCode status;
    private String birthDate;

    public static PrinceDetailDto fromEntity(Prince prince) {
        return PrinceDetailDto.builder()
                .princeLevel(prince.getPrinceLevel())
                .skillType(prince.getSkillType())
                .experienceYears(prince.getExperienceYears())
                .princeId(prince.getPrinceId())
                .name(prince.getName())
                .age(prince.getAge())
                .status(prince.getStatus())
                .birthDate(getLocalDateTimeString(prince.getCreatedAt()))
                .build();
    }
}

 

적용후

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class PrinceDetailDto {
    private PrinceLevel princeLevel;
    private SkillType skillType;
    private Integer experienceYears;
    private String princeId;
    private String name;
    private Integer age;
    private StatusCode status;
    private String birthDate;

    public static PrinceDetailDto fromEntity(Prince prince) {
        return PrinceDetailDto.builder()
                .princeLevel(prince.getPrinceLevel())
                .skillType(prince.getSkillType())
                .experienceYears(prince.getExperienceYears())
                .princeId(prince.getPrinceId())
                .name(prince.getName())
                .age(prince.getAge())
                .status(prince.getStatus())
                .birthDate(toBirthDayString(prince.getCreatedAt()))
                .build();
    }
}

 

자바코드에 적용시에는 static 메서드 사용하는 것처럼 느껴지기 때문에 가독성이 좋지 않습니다

위 클래스를 java에서 kotlin으로 변환후 확인해보겠습니다

class PrinceDetailDto(
    val princeLevel: PrinceLevel,
    val skillType: SkillType,
    val experienceYears: Int,
    val princeId: String,
    val name: String,
    val age: Int,
    val status: StatusCode,
    val birthDate: String,
)

fun Prince.toPrinceDetailDto() = PrinceDetailDto(
    princeLevel = this.princeLevel,
    skillType = this.skillType,
    experienceYears = this.experienceYears,
    princeId = this.princeId,
    name = this.name,
    age = this.age,
    status = this.status,
    birthDate = this.createdAt!!.toBirthDayString()
)

 

이렇게 되면 java에서 사용하던 mapper 메서드인 fromEntities를 사용하는 코드도 변환됩니다

 

변환전 kotlin에서 java static 메서드 사용예시

    fun getPrince(princeId: String?): PrinceDetailDto {
        return princeRepository.findByPrinceId(princeId)
            .map { prince: Prince? -> PrinceDetailDto.fromEntity(prince) }
            .orElseThrow { PrinceMakerException(PrinceMakerErrorCode.NO_SUCH_PRINCE) }
    }

 

변환후 java static 메서드를 확장함수로 변환

    fun getPrince(princeId: String?): PrinceDetailDto {
        return princeRepository.findByPrinceId(princeId!!)
            ?.let { it.toPrinceDetailDto() }
            ?: throw PrinceMakerException(PrinceMakerErrorCode.NO_SUCH_PRINCE)
    }

가변인자

fun printAll(vararg messages: String) {
    for (message in messages) {
        println(message)
    }
}


fun main() {
    printAll("Hello", "World", "Kotlin")
}

가변인자말고 다른 변수랑 쓸때는 가변인수는 마지막 인자로 존재해야합니다

fun formatNames(prefix: String, vararg names: String): List<String> {
    return names.map { "$prefix $it" }
}

fun main() {
    val formattedNames = formatNames("Mr.", "John", "Jane", "Alice")
    println(formattedNames)  // [Mr. John, Mr. Jane, Mr. Alice]
}

node.js에서 자주 등장하는 커링

fun outerFunction(vararg ints: Int): (vararg String) -> Unit {
    return { strings ->
        // 두 가변인자 모두 사용 가능
    }
}

// 호출
outerFunction(1, 2, 3)("a", "b", "c")

배열을 가변인자로 전달할때는 *를 붙입니다

fun sum(vararg numbers: Int): Int {
    return numbers.sum()
}

fun main() {
    val numbers = intArrayOf(1, 2, 3, 4, 5)
    println(sum(*numbers))  // 15
    println(sum(1, 2, 3))   // 6
}

중위호출

코틀린으로는 가독성 높이는데 많은 도움이 되고 대단한 장점임

infix fun LocalDate.addDays(days: Long): LocalDate = this.plusDays(days)
infix fun String.linkTo(url: String) = this to "https://$url"

val websiteMap = mapOf(
    "Google" linkTo "google.com",
    "Naver" linkTo "naver.com",
    "Daum" linkTo "daum.net"
)

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

infix fun String.toPerson(age: Int) = this to Person(this, age)

val peopleMap = mapOf(
    "Alice" toPerson 25,
    "Bob" toPerson 30,
    "Charlie" toPerson 35
)
fun main() {
    val today = LocalDate.now()
    val futureDate = today addDays 5
    println("5일 후: $futureDate")
    println(websiteMap)
    println(peopleMap)
}

mapOf는 이렇게도 사용가능합니다

    println(mapOf("key" to "valuie", "key2" to "value2"))

구조 분해 선언(Destructuring declarations)

Pair 구조분해

val pair = Pair("John", 30)
val (name, age) = pair
println("$name is $age years old")  // 출력: John is 30 years old

데이터 클래스 구조분해

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

val person = Person("Alice", 25)
val (name, age) = person
println("$name is $age years old")  // 출력: Alice is 25 years old

리스트 구조분해

val (first, second, third) = listOf("One", "Two", "Three")
println("$first, $second, $third")  // 출력: One, Two, Three

Map 엔트리 구조분해

val map = mapOf("a" to 1, "b" to 2, "c" to 3)
for ((key, value) in map) {
    println("$key = $value")
}

함수쪽은 전체적으로 typescript에서 사용하는 여러 기능들이 사용되고 있어서 node.js환경에서 백엔드 개발하시는 분들에게는 익숙한 개념들이었습니다

'kotlin > 기초' 카테고리의 다른 글

kotlin 코루틴 정리 - 1  (0) 2024.08.25
kotlin 문법 기초 - 4  (0) 2024.07.23
kotlin 문법 기초 - 3  (0) 2024.07.17
kotlin 문법 기초-1  (0) 2024.07.10