올리브영 테크블로그 포스팅 Java를 주로 다루는 개발자가 생각하는 Kotlin 장점 🌼
Tech

Java를 주로 다루는 개발자가 생각하는 Kotlin 장점 🌼

왜 Kotlin이 Java 개발자들에게 사랑받는지 알아보세요! ✌️

2024.12.08

목차




개요


안녕하세요!

올리브영의 파트너를 위한 서비스를 제공하는 파트너플랫폼 스쿼드 백엔드 개발자 🌼 들국화입니다.

이번 글에서는 Java에 익숙한 개발자가 Kotlin을 사용하며 경험할 수 있는 장점들을 소개하려고 합니다.

또한, 제가 느낀 만족도를 별점으로 표현해 보았는데요. 😊 ⭐️⭐️⭐️⭐️⭐️

그럼 지금부터 Kotlin의 장점을 하나씩 알아보겠습니다!




간결한 문법과 편리해진 기능


Java 와 다르게 사용되는 여러 가지 문법이 있지만, 장점이라고 생각될만한 간결해진 문법을 소개합니다.

또한 Kotlin 에서 제공하는 여러 가지 편리한 기능들을 소개하겠습니다.

1️⃣ 프로그램 진입점 ⭐️⭐️⭐️

Java 에서는 public static void main(String[] args) 메서드를 사용하여 프로그램을 실행합니다.

Kotlin 에서는 fun main() 함수를 사용하여 프로그램을 실행합니다.

프로그램 진입점이 되는 main function을 자주 사용하진 않지만, 매우 간결화된 main function 덕분에 배움의 시작부터 편리함을 느꼈습니다 !


2️⃣ 세미콜론 ⭐️⭐️⭐️

Kotlin 은 세미콜론을 사용하지 않아도 됩니다 ! 처음엔 어색해서 적응이 필요했지만 적응하는데에 약 5분 정도 걸렸습니다. (거의 즉시 적응)

intellij IDE 를 통해 자동완성으로 항상 세미콜론을 붙여온 저로서는 command + shift + enter를 입력하지 않아도 된다는 점이 편리했습니다.

👍 (편안 ☺️)

fun main() {
  println("Hello Kotlin") // 세미콜론 생략 가능
}

3️⃣ var, val ⭐️⭐️⭐️

Java 에서는 변수를 선언할 때 final 키워드를 사용하여 불변 변수를 선언합니다.

Kotlin 에서는 var 키워드를 사용하여 변수를 선언하고, val 키워드를 사용하여 불변 변수를 선언합니다.

var name = "변경 가능한 name" // 변경 가능
val immutableName = "변경 불가능한 name" // 재할당 불가능

4️⃣ Nullable, Non-Nullable Type ⭐️⭐️⭐️⭐️

Kotlin 에서는 null을 허용하는 Nullable 타입과 null을 허용하지 않는 Non-Nullable 타입을 구분합니다.

null 을 허용하는 변수에는 ? 를 붙여 Nullable 타입으로 선언할 수 있습니다.

Non-Nullable 타입을 다룰 때에는 null에 대한 걱정 없이 코딩할 수 있기 때문에 코드의 안정성을 높일 수 있습니다.

val nullableName: String? = null // null 허용
val nonNullableName: String = null // null 허용하지 않음, compile error 발생

5️⃣ Nullable Type을 다룰 때 사용하는 Safe call operator ?. , Elvis operator ?: ⭐️⭐️⭐️⭐️⭐️

가장 마음에 드는 Kotlin의 기능들 Safe call operatorElvis operator 입니다.

저희는 Java 로 Reference type을 다룰 때 항상 NPE에 대한 두려움을 가지고 null check를 해왔습니다 😭

Java 8 이후로 Optional을 통해 모던한 코드로 NPE를 방지할 수 있지만, Optional 객체를 생성해서 활용해야 하는 번거로움이 있었습니다.

Kotlin으로 코딩할 때는 추가 객체 생성 없이 ?. , ?: 연산자를 사용하여 null check와 동시에 간결하게 코드를 작성할 수 있습니다.


  1. JavaOptional, KotlinSafe call operator 코드 비교

    ?. operator는 null check를 하고, null이 아닐 경우에만 함수를 호출합니다.

    아래 두 코드는 모두 book을 찾고 null이 아닐 경우 제목을 변경하는 코드입니다.

    Java 코드의 경우 Optional 처리하기 위해 findBook() 메서드 내부적으로 Optional을 생성하여 return하도록 코딩해야 합니다.

    Kotlin 코드의 경우 추가 객체 생성 없이, 언어에서 지원하는 Safe call operator 를 통해 Java 코드보다 훨씬 간결하게 작성할 수 있습니다.

    [safe-call.png]


  1. JavaOptional, KotlinElvis operator 코드 비교

    ?: operator는 null일 경우 대체 값을 지정할 수 있습니다.

    아래 두 코드는 모두 book을 찾고 null일 경우 비즈니스 예외를 발생시키고, null이 아닐 경우 제목을 변경하는 코드입니다.

    이 예제 역시 마찬가지로 Java 로 작성한 코드보다 훨씬 간결하게 작성할 수 있습니다.

    [safe-call.png]


사랑해요 safe call, elvis .. 🥰


6️⃣ in 키워드 ⭐️⭐️

Kotlin 에서는 in 키워드를 사용하여 값이 어떤 범위에 포함되는지 편리하게 검사 가능합니다.

항상 범위에 대한 검사를 하기 위해 >=, <= 연산자를 사용했는데, in 키워드로 한 번에 해결 가능합니다 😊

// kotlin
val num = 6
print(num in 5..10) // true
// java
int num = 6;
System.out.println(5 <= num && num <= 10); // true

7️⃣ is 키워드, Smart casting ⭐️⭐️⭐️⭐️⭐️

Kotlin 에서는 is 키워드를 사용하여 타입을 검사할 수 있습니다.

  1. is 키워드는 Javainstanceof와 비슷한 기능을 제공하지만, 더 간결하고 편리한 기능을 제공합니다.
  2. is 키워드로 타입을 검사하면, 해당 타입으로 자동 캐스팅되어 추가 변환 없이 바로 사용할 수 있습니다 !!
  3. is 키워드에 !를 사용하여 not 연산도 가능합니다.
fun printLength(obj: Any) {
  if (obj is String) {
    // obj는 is 키워드로 검사된 이후 자동으로 형 변환되어 사용 가능합니다.
    println("Length : ${obj.length}")
  }
}

8️⃣ String template ⭐️⭐️⭐️⭐️⭐️

Kotlin 에서는 String template 을 사용하여 문자열을 간결하게 작성할 수 있습니다.

그동안 Java 개발자들은 String.format() 같은 메서드를 사용하여 문자열을 조합하거나 직접 붙여서 표현했는데,

Kotlin 에서는 String template을 사용하여 더 간결하게 문자열을 조합할 수 있습니다. (진짜 .. 너무편리해요 😭)

사용법은 간단합니다 ${변수명}, $변수명 의 형태로 사용하면 됩니다.

val name = "들국화"

println("이름은 : ${name}") // 이름은 : 들국화
log.info("name : $name") // name : 들국화

9️⃣ Raw String ⭐️⭐️⭐️⭐️⭐️

Raw String은 큰따옴표 세 개(""")로 감싸진 문자열입니다 ! Raw String을 사용하면

  1. 여러 줄의 문자열 작성 가능합니다.
  2. 문자열 Escape 처리 없이 그대로 사용 가능합니다.
  3. 코드 블록처럼 들여쓰기를 작성 가능하며 마지막에 trimIndent()로 정리 가능합니다.
val message = """
    Hello, World!
    "안녕하십니까 ?" 
    "Java 개발자가 생각하는 "Kotlin" 장점은 무엇인가요 ?"
    "Kotlin은 Java보다 간결하고 실용적이라고 생각합니다."
""".trimIndent()

println(message)

// Hello, World!
// "안녕하십니까 ?" 
// "Java 개발자가 생각하는 Kotlin 장점은 무엇인가요 ?"
// "Kotlin은 Java보다 간결하고 실용적이라고 생각합니다."

앞으로 우리는 이런 코드를 보지 않아도 됩니다........ 😇

[raw-string-omg.png]


🔟 when , if , try-catch Expression ⭐️⭐️⭐️⭐️

Kotlin 에서는 when, if, try-catch 가 단순히 구문(Statement) 이 아니라 식(Expression) 으로 동작합니다.

Expression은 결과값을 반환할 수 있는 코드 블록을 말합니다.

Kotlin 에서는 모두 으로 동작하기 때문에 값을 반환할 수 있으며, 더 간결하게 코드를 작성할 수 있습니다.

  1. if

    • Kotlin 에서 if 문은 식으로 동작합니다. 즉, 값을 반환할 수 있습니다.

    • Java 에서는 if 문 내부에서 값을 반환할 수 없지만, Kotlin 에서는 if 식 자체를 반환할 수 있습니다.

    [if.png]


  1. when

    • Kotlin 에서 when expression 사용해 Javaswitch-case 문을 완벽히 대체할 수 있습니다.

    • when expression은 default 대신에 else를 사용합니다.

    • 식으로 동작하기 때문에 when 절이 동작한 후 바로 값을 반환할 수 있습니다.

    • when + enum을 같이 사용할 경우 강력하고 간결한 분기 처리가 가능합니다 !!

      • 모든 enum 상수에 대해 분기 처리가 강제되며, 컴파일 단계에서 누락된 분기 처리를 자동으로 감지하여 실수를 방지합니다.

    // else를 사용하지 않고 전체 상수에 대한 분기 처리 강제.
    fun handle(bookType: BookType): Book {
      return when(bookType){
        BookType.PAPER -> handlePaperBook()
        BookType.EBOOK -> handleEbook()
        BookType.AUDIO -> handleAudio()
      }
    }
    
    // else 사용, 전체 상수에 대한 분기 처리를 하지 않는 대신에 else로 나머지 케이스를 처리.
    fun handle(bookType: BookType): Book {
      return when(bookType){
        BookType.PAPER -> handlePaperBook()
        else -> handleEtc()
      }
    }
    • Java 에서도 14 버전부터 switch expression 이 정식 release 되어 위와 같은 기능으로 사용할 수 있습니다 !

  1. try-catch

    • Kotlin 에서 try-catch 문 또한 식으로 동작합니다.
    fun safeParseInt(str: String): Int {
      return try {
          str.toInt()
      } catch (e: NumberFormatException) {
          -1 // 변환 실패 시 기본값 return
      }
    }

Class


Kotlin 에서의 클래스와 Java 에서의 클래스를 코드로 먼저 살펴보고, Kotlin 에서는 얼마나 간결하게 사용 가능한지 비교해 보겠습니다.

왼쪽이 Kotlin 클래스, 오른쪽이 Java 클래스 입니다.

처음 Kotlin 을 접했을 때 가장 놀랐던 부분이 바로 Class 선언 부분이었습니다. "아니.. 왜 이렇게 뭐가 없지 ? 🤔 " 라는 생각이 들었습니다.

[class_diff.png]


1️⃣ 클래스 선언 시 body를 생략할 수 있습니다. ⭐️⭐️⭐️

간결 그 자체, 중괄호 생략이 가능합니다.


2️⃣ 생성자 선언과 동시에 field를 선언할 수 있습니다. ⭐️⭐️⭐️

Kotlin 에서는 생성자를 선언함과 동시에 field를 선언할 수 있습니다. 이 문법을 통해서 생성자를 통해 field를 초기화하는 코드를 줄일 수 있습니다.

// Class를 선언하면서 소괄호를 통해 생성자를 동시에 선언할 수 있다 !! 
class Book(var title: String, val author: String) {}

3️⃣ getter, setter를 자동으로 생성해 줍니다. ⭐️⭐️⭐️⭐️⭐️

정말 너무 마음에 드는 기능입니다. 반복적으로 작성해야 했던 getter, setter를 자동으로 생성해 줍니다.

field를 만들면 compile 단계에서 자동 생성해 줍니다.

정말 마음에 드는 건 lombok 없이 간결한 코드를 작성할 수 있다는 점입니다.


4️⃣ 객체 생성 시 new 키워드 생략할 수 있습니다. ⭐️⭐️⭐️⭐️

Java 에서 객체를 생성할 때 반복적으로 사용하던 new 키워드를 생략할 수 있습니다 ! 와우..

val book = Book() // new 키워드 생략 가능

5️⃣ getter, setter 호출 ⭐️⭐️⭐️⭐️

getter, setter를 호출할 때는 class 멤버변수에 접근하듯 사용하면 gettersetter를 호출해 줍니다.

fun main() {
  val book = Book()
  println(book.title) // getter 호출 
  
  book.title = "Kotlin 장점에 대하여" // setter 호출
}

6️⃣ 클래스 상속, 인터페이스 구현 ⭐️⭐️⭐️

Java 에서는 extends 키워드를 사용하여 상속하고, implements 키워드를 사용하여 인터페이스를 구현합니다.

하지만 Kotlin 에서는 간결하게 extends, implements 모두 : 콜론으로 표현합니다 !

Kotlin 에서 특이한 점은 open 키워드를 사용해 상속 가능한 클래스로 만들어주어야 상속할 수 있습니다.

interface Flyable {
  fun fly()
}

open class Animal

class Dog : Animal()
class Bird : Flyable

7️⃣ override ⭐️⭐️⭐️⭐️

Java 에서는 메서드를 오버라이드할 때 @Override 어노테이션을 사용합니다.

Kotlin 에서는 override 키워드를 사용하여 메서드를 오버라이드합니다.

Java 에서는 @Override 어노테이션을 사용하지 않아도 문제가 없지만,

Kotlin 에서는 override 키워드를 사용하지 않은 채 function을 재정의하는 경우 compile 단계에서 에러가 발생합니다.

class Bird : Flyable {
  override fun fly() {
    // fly logic
  }
}

8️⃣ Data class ⭐️⭐️⭐️⭐️⭐️

Kotlin 에서는 data class를 사용하여 toString(), equals(), hashCode(), copy() 메서드를 자동으로 생성해 주는 클래스를 만들 수 있습니다.

data class 를 활용하면 반복적으로 Override하여 작성하던 Dto 클래스 코드를 매우 간결하게 작성할 수 있습니다.

copy() 함수를 통해 간편하게 객체를 복사하면서 일부 속성만 변경된 객체를 얻을 수 있습니다.

🫒 올리브영에 강연하러 오신 Josh Long 😎 형님께서는 data class 를 사용하면 lombok을 사용하지 않아도 된다고 말씀하셨습니당 😊

data class BookDto(val title: String, val author: String)

fun main() {
  val bookDto1 = BookDto("Effective Kotlin", "들국화")
  val bookDto2 = BookDto("Effective Kotlin", "들국화")
  val bookDto3 = bookDto2.copy(author = "수정된 작가")
  
  println(bookDto1)
  println(bookDto3)
  println(bookDto1.equals(bookDto2))
  println(bookDto1.equals(bookDto3))
  
  // BookDto(title=Effective Kotlin, author=들국화)
  // BookDto(title=Effective Kotlin, author=수정된 작가)
  // true
  // false
}

Function


1️⃣ Default parameter ⭐️⭐️⭐️⭐️⭐️

Kotlin 에서는 함수의 매개변수에 기본값을 지정할 수 있습니다 !

Java 에서는 오버로딩을 통해 기본값을 지정하는 method를 만들어 활용할 수 있지만, Kotlin 에서는 오버로딩하지 않고 함수 선언 시 매개변수에 기본값을 지정할 수 있습니다 !! 😊

Java 에서는 오버로딩을 하지 않는다면 method 내부에서 기본값을 할당하는 코딩을 해야 하지만, Kotlin 에서는 함수 선언 시 매개변수에 기본값을 지정할 수 있어 코드가 더 간결해집니다.

fun createBook(title: String = "default title", author: String = "default author"): Book {
  return Book(title, author)
}

val defaultBook = createBook()
val book = createBook("Effective Kotlin", "들국화")

println(defaultBook) // Book(title='default title', author='default author')
println(book) // Book(title='Effective Kotlin', author='들국화')

2️⃣ Named argument ⭐️⭐️⭐️⭐️⭐️

Kotlin 에서는 함수 호출 시 매개변수의 순서 상관없이 이름을 지정하여 인자를 전달할 수 있습니다.

Java 에서는 매개변수의 순서에 맞게 인자를 전달해야 하지만, Kotlin 에서는 매개변수의 이름을 지정하여 인자를 전달할 수 있습니다.

Named argument 덕분에, 매개변수가 많은 함수라도 builder 패턴을 사용하지 않고 간결하게 코드를 작성할 수 있습니다!

fun createBook(
  title: String = "default title",
  author: String = "default author",
  publishDate: LocalDateTime,
  totalPage: Int,
  price: Int,
  createDateTime: LocalDateTime,
  createUserId: String): Book {
  
  return //... create book
}

// argument의 name을 지정하여 전달할 수 있다.
createBook(
  title = "Effective Kotlin",
  price = 20000,
  publishDate = LocalDateTime.now(),
  totalPage = 300,
  author = "들국화",
  createDateTime = LocalDateTime.now(),
  createUserId = "user1"
)

3️⃣ 확장함수 ⭐️⭐️⭐️⭐️⭐️

Kotlin 에서는 기존 클래스에 새로운 함수를 추가할 수 있는 확장함수를 제공합니다.

이 기능은 정말 마음에 드는 기능 중 하나입니다. 제가 Java 를 다루면서 항상 꿈 꾸었던 기능이었습니다 😭


예를들어 List에서 첫번째 element를 가져오는 기능을 이용하고 싶으면 org.springframework.util.CollectionUtils 등과 같은 라이브러리 메서드를 사용해야 했습니다.

List<Book> books = List.of(new Book("Effective Java", "들국화"), new Book(), new Book());
Book firstBook = CollectionUtils.firstElement(books);

그런데 만약 첫번째 element가 아닌 임의의 element 객체를 얻고 싶다면 아래와 같이 Util method를 만들어 사용 했습니다.

public class OYUtils {
  public static <T> T findAnyElement(List<T> list) {
    return list.stream()
            .findAny()
            .orElse(null);
  }
}

Book anyBook = OYUtils.findAnyElement(books);

하지만 라이브러리를 이용하면서도, Util method를 만들면서도 이런 생각을 하죠 .. "아.. 이런 기능은 왜 List에 만들어 놓지 않았을까..? Java version이 올라가면 생길까 ? " 😭


그러나 ! Kotlin 에서는 이러한 needs를 확장함수를 통해 간단하게 충족시킬 수 있습니다.

확장함수 사용법은 아래와 같습니다. fun <클래스>.함수이름() 으로 선언하면 됩니다 !

너무너무 아름답게도 List객체라면 전부 제가 확장시킨 함수를 사용할 수 있습니다. 😊

fun <T> List<T>.findAnyElement(): T? {
  return this.stream()
    .findAny()
    .orElse(null)
}

val books = listOf(Book("Effective Kotlin", "들국화"), Book(), Book())
val numbers = listOf(1, 2, 3, 4, 5)

val anyBook = books.findAnyElement()
val anyNumber = numbers.findAnyElement()

println(anyBook) // Book(title='Effective Kotlin', author='들국화')
println(anyNumber) // 1

4️⃣ lambda ⭐️⭐️⭐️⭐️⭐️

함수형 프로그래밍 특징을 가지는 Kotlin 에서는 Java 와 달리 함수를 일급객체로 취급합니다.

함수를 parameter로 넘길 수 있고, 변수에 넣을 수 있습니다. 이는 익명 함수 인 lambda를 사용할 때에도 마찬가지입니다.

Kotlin 에서는 Java 와 달리 functional interface를 통해 lambda를 표현할 필요가 없습니다. 함수 식을 선언하고 할당하고, 전달할 수 있습니다 !

val books = listOf(Book("Effective Kotlin", "들국화"), Book(), Book())

// function body가 존재하는 lambda
val eqTitle = fun(book: Book): Boolean {
  return book.title == "Effective Kotlin"
}

// function body를 생략한 lambda
val eqTitle2 = fun(book: Book) = book.title == "Effective Kotlin"

// 중괄호로 감싸진 lambda
val eqTitle3 = { book: Book -> book.title == "Effective Kotlin" }

filterBook(books, eqTitle)
filterBook(books, eqTitle2)
filterBook(books, eqTitle3)
filterBook(books, { book: Book -> book.title == "Effective Kotlin" }) // lambda를 선언하며 전달

// filter 라는 매개변수는 함수 자체를 매개변수로 받고 있다.
fun filterBook(books: List<Book>, filter: (Book) -> Boolean): List<Book> {
  return books.filter(filter)
    .toList()
}

5️⃣ collections package ⭐️⭐️⭐️⭐️⭐️

Kotlin 의 collections package는 엄청나게 다양하고 매우 편리한 컬렉션 관련 함수, 확장함수를 제공합니다.


  1. ✅ Collection을 다루는데 있어서 매우 편리한 함수들을 제공합니다.

    listOf(), setOf(), mapOf() 등의 함수를 통해 읽기전용 collection을 생성할 수 있습니다.

    mutableListOf(), mutableSetOf(), mutableMapOf() 등의 함수를 통해 수정가능한 collection을 생성할 수 있습니다.

    읽기전용 collection 타입과 수정가능한 collection 타입이 분리되어 있고, add(), remove(), put() 등의 수정 기능을 가진 함수를 제공하지 않습니다. 읽기 전용임이 매우 명확하게 설계되어 있습니다.

    // 읽기전용 타입은 수정 가능한 함수를 제공하지 않습니다.
    val books: List<Book> = listOf(Book("Effective Kotlin", "들국화"), Book(), Book())
    val bookSets: Set<Book> = setOf(Book("Effective Kotlin", "들국화"), Book(), Book())
    val map: Map<BookKey, Book> = mapOf(BookKey() to Book("Effective Kotlin", "들국화"))
    // collection을 수정할 수 있는 함수들을 제공하지 않음.
    
    val mutableBooks: MutableList<Book> = mutableListOf(Book("Effective Kotlin", "들국화"), Book(), Book())
    val mutableBookSets: MutableSet<Book> = mutableSetOf(Book("Effective Kotlin", "들국화"), Book(), Book())
    val mutableBookMap: MutableMap<BookKey, Book> = mutableMapOf(BookKey() to Book("Effective Kotlin", "들국화"))
    
    mutableBooks.add(Book("Effective Kotlin", "들국화"))
    mutableBookSets.add(Book("Effective Kotlin", "들국화"))
    mutableBookMap.put(BookKey(), Book("Effective Kotlin", "들국화"))

    또한 get() 함수 대신 [] 대괄호로 값을 가져올 수 있습니다 !

    val books = listOf(Book("Effective Kotlin", "들국화"), Book(), Book())
    val map = mapOf(BookKey("key") to Book("Effective Kotlin", "들국화"))
    
    val book = books[0] // books.get(0) 대신 books[0] 사용 가능
    val book = map[BookKey("key")] // map.get(BookKey("key")) 대신 map[BookKey("key")] 사용 가능

  1. ✅ 다양하고 편리한 확장함수를 제공합니다.

    Java 에서는 Stream API를 통해 사용하던 편리한 함수들을 Kotlin 에서는 편리하게 구현되어 있는 확장함수로 Stream 객체 생성 없이 사용할 수 있습니다 !!

    filter(), map(), mapNotNull(), all(), none(), any(), sortedBy(), firtst(), firstOrNull(), last(), lastOrNull() 등등 와우.. ☺️

    val books: List<Book> = listOf(Book("Effective Kotlin", "들국화"), Book(), Book())
    
    // stream 객체 생성 hell에서 벗어난 모습
    val filteredBooks: List<Book> = books.filter({ book -> book.title == "Effective Kotlin" })
    val titles: List<String> = books.map({ book -> book.title })
    val nullableBook: Book? = books.firstOrNull()
    val book: Book = books.first()
    val nullableLastBook: Book? = books.lastOrNull()
    val lastBook: Book = books.last()


마무리


Java 를 주로 다루는 개발자가 Kotlin 을 접하면서 느낀 장점들을 정리하고 별점을 매겨보았습니다.

간결하고 효율적인 문법, null에 대한 고민을 줄여주는 설계, 함수형 프로그래밍의 지원, 확장 함수 등.

KotlinJava 에 익숙한 개발자라면 쉽게 접근할 수 있으며, 빠르게 학습할 수 있는 언어입니다. 또한, OOP와 FP의 장점을 모두 지닌 강력한 언어입니다.

지금까지 Java 만 다뤄본 분이 계시다면 Kotlin 을 한번 경험해보세요! 😊

읽어주셔서 감사합니다.




JavaKotlinJava to Kotlin
올리브영 테크 블로그 작성 Java를 주로 다루는 개발자가 생각하는 Kotlin 장점 🌼
🌼
들국화 |
Back-end Engineer