목차
개요
안녕하세요!
올리브영의 파트너를 위한 서비스를 제공하는 파트너플랫폼 스쿼드 백엔드 개발자 🌼 들국화입니다.
이번 글에서는 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 operator 와 Elvis operator 입니다.
저희는 Java 로 Reference type을 다룰 때 항상 NPE
에 대한 두려움을 가지고 null
check를 해왔습니다 😭
Java 8 이후로 Optional
을 통해 모던한 코드로 NPE
를 방지할 수 있지만, Optional
객체를 생성해서 활용해야 하는 번거로움이 있었습니다.
Kotlin으로 코딩할 때는 추가 객체 생성 없이 ?.
, ?:
연산자를 사용하여 null
check와 동시에 간결하게 코드를 작성할 수 있습니다.
-
✅ Java 의
Optional
, Kotlin 의 Safe call operator 코드 비교?.
operator는null
check를 하고,null
이 아닐 경우에만 함수를 호출합니다.아래 두 코드는 모두
book
을 찾고null
이 아닐 경우 제목을 변경하는 코드입니다.Java 코드의 경우
Optional
처리하기 위해findBook()
메서드 내부적으로Optional
을 생성하여return
하도록 코딩해야 합니다.Kotlin 코드의 경우 추가 객체 생성 없이, 언어에서 지원하는 Safe call operator 를 통해 Java 코드보다 훨씬 간결하게 작성할 수 있습니다.
-
✅ Java 의
Optional
, Kotlin 의 Elvis operator 코드 비교?:
operator는null
일 경우 대체 값을 지정할 수 있습니다.아래 두 코드는 모두
book
을 찾고null
일 경우 비즈니스 예외를 발생시키고,null
이 아닐 경우 제목을 변경하는 코드입니다.이 예제 역시 마찬가지로 Java 로 작성한 코드보다 훨씬 간결하게 작성할 수 있습니다.
사랑해요 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
키워드를 사용하여 타입을 검사할 수 있습니다.
is
키워드는 Java 의instanceof
와 비슷한 기능을 제공하지만, 더 간결하고 편리한 기능을 제공합니다.is
키워드로 타입을 검사하면, 해당 타입으로 자동 캐스팅되어 추가 변환 없이 바로 사용할 수 있습니다 !!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을 사용하면
- 여러 줄의 문자열 작성 가능합니다.
- 문자열 Escape 처리 없이 그대로 사용 가능합니다.
- 코드 블록처럼 들여쓰기를 작성 가능하며 마지막에
trimIndent()
로 정리 가능합니다.
val message = """
Hello, World!
"안녕하십니까 ?"
"Java 개발자가 생각하는 "Kotlin" 장점은 무엇인가요 ?"
"Kotlin은 Java보다 간결하고 실용적이라고 생각합니다."
""".trimIndent()
println(message)
// Hello, World!
// "안녕하십니까 ?"
// "Java 개발자가 생각하는 Kotlin 장점은 무엇인가요 ?"
// "Kotlin은 Java보다 간결하고 실용적이라고 생각합니다."
앞으로 우리는 이런 코드를 보지 않아도 됩니다........ 😇
🔟 when , if , try-catch Expression ⭐️⭐️⭐️⭐️
Kotlin 에서는 when
, if
, try-catch
가 단순히 구문(Statement) 이 아니라 식(Expression) 으로 동작합니다.
Expression은 결과값을 반환할 수 있는 코드 블록을 말합니다.
Kotlin 에서는 모두 식으로 동작하기 때문에 값을 반환할 수 있으며, 더 간결하게 코드를 작성할 수 있습니다.
-
if
-
Kotlin 에서
if
문은 식으로 동작합니다. 즉, 값을 반환할 수 있습니다. -
Java 에서는
if
문 내부에서 값을 반환할 수 없지만, Kotlin 에서는if
식 자체를 반환할 수 있습니다.
-
-
when
-
Kotlin 에서
when
expression 사용해 Java 의switch-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 되어 위와 같은 기능으로 사용할 수 있습니다 !
-
-
try-catch
- Kotlin 에서
try-catch
문 또한 식으로 동작합니다.
fun safeParseInt(str: String): Int { return try { str.toInt() } catch (e: NumberFormatException) { -1 // 변환 실패 시 기본값 return } }
- Kotlin 에서
Class
Kotlin 에서의 클래스와 Java 에서의 클래스를 코드로 먼저 살펴보고, Kotlin 에서는 얼마나 간결하게 사용 가능한지 비교해 보겠습니다.
왼쪽이 Kotlin 클래스, 오른쪽이 Java 클래스 입니다.
처음 Kotlin 을 접했을 때 가장 놀랐던 부분이 바로 Class 선언 부분이었습니다. "아니.. 왜 이렇게 뭐가 없지 ? 🤔 " 라는 생각이 들었습니다.
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 멤버변수에 접근하듯 사용하면 getter
와 setter
를 호출해 줍니다.
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는 엄청나게 다양하고 매우 편리한 컬렉션 관련 함수, 확장함수를 제공합니다.
-
✅ 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")] 사용 가능
-
✅ 다양하고 편리한 확장함수를 제공합니다.
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에 대한 고민을 줄여주는 설계, 함수형 프로그래밍의 지원, 확장 함수 등.
Kotlin 은 Java 에 익숙한 개발자라면 쉽게 접근할 수 있으며, 빠르게 학습할 수 있는 언어입니다. 또한, OOP와 FP의 장점을 모두 지닌 강력한 언어입니다.
지금까지 Java 만 다뤄본 분이 계시다면 Kotlin 을 한번 경험해보세요! 😊
읽어주셔서 감사합니다.