728x90

Java를 오래 사용하다 보니, Kotlin 문법을 알고 있어도 자연스럽게 Java 스타일로 코드를 작성하곤 했습니다.
문법적으로 문제는 없었지만 Kotlin의 장점을 충분히 살리지 못하고 있다는 아쉬움이 늘 있어,
Kotlin다운 코드 작성법을 소개해 도움이 되었으면 하는 마음으로 정리했습니다.
1. Null 체크
if (A != null) vs A?.let { }
Java 스타일
if (user != null) { user.doSomething() } else { return null }
Kotlin 스타일
user?.let { it.doSomething() }
더 좋은 Kotlin 스타일
user?.doSomething()
let은 “스코프 함수”이지 null-check 용이 아님
단순 null-safe call 이면 ?.doSomething()이 더 읽기 좋음
“스코프가 필요할 때만 let 을 쓰는 게 진짜 코틀린스럽다”
더보기
스코프 함수 정리
| 스코프 함수 | this / it |
반환값 | 주 사용 목적 |
| let | it | 람다 결과 | 변환, null-safe 블록, 일시적 스코프 |
| run | this | 람다 결과 | 객체 기반 계산, 초기화 로직 |
| apply | this | 객체 자체 | 객체 설정(빌더 느낌) |
| also | it | 객체 자체 | 부가 작업(side effect) |
| with | this | 람다 결과 | 객체를 파라미터로 스코프 제공 |
| 스코프 함수 | this / it |
대표 사용 예시 | 이렇게 쓰면 좋다 |
| let | it | value?.let { ... } | null 체크 후 다른 값으로 변환할 때 |
| run | this | user.run { validate(); toDto() } | 객체를 바탕으로 무언가 계산해 ‘값’을 만들 때 |
| apply | this | User().apply { name = "Sol"; age = 29 } | 생성 후 속성 값을 채워 넣을 때 |
| also | it | user.also { log.info("user: $it") } | 로깅, 디버깅 등 부가 작업을 섞어 쓰고 싶을 때 |
| with | this | with(user) { validate(); toDto() } | 특정 객체의 여러 메서드를 연속으로 호출할 때 |
2. 조건문 return
if → early return + Elvis
Java 스타일
if (name == null) { return "Unknown" } return name
Kotlin 스타일
return name ?: "Unknown"
더 좋은 Kotlin 스타일
val displayName = name ?: error("Name is required")
코틀린의 Elvis 연산자는 “기본값 제공”이나 “예외 발생”에 강력
if-null-check 블록을 한 줄로 줄이는 게 코틀린스러운 사고방식
3. 불필요한 변수 선언 vs scope functions
Java 스타일
val user = repository.getUser()
if (user != null) {
user.validate()
user.updateTimestamp()
return user
}
return null
Kotlin 스타일
return repository.getUser()?.apply {
validate()
updateTimestamp()
}
apply는 “this 객체 꾸미기”에 최적
apply/also/let/run/with의 의도별 정리 테이블 넣으면 좋음
4. builder 패턴 → named parameters + apply
Java 스타일
val user = User.Builder()
.name("Solbi")
.age(29)
.isActive(true)
.build()
Kotlin 스타일
val user = User(
name = "Solbi",
age = 29,
isActive = true
)
빌더가 반드시 필요하면
val user = User().apply {
name = "Solbi"
age = 29
isActive = true
}
코틀린에서는 데이터 클래스 + named argument 만으로 빌더 패턴 대체 가능
apply 패턴은 “객체 설정 코드"를 매우 읽기 쉽게 만들어줌
5. List 반복
for → map / filter / firstOrNull
Java 스타일
for (u in users) {
if (u.id == targetId) {
return u
}
}
return null
Kotlin 스타일
return users.firstOrNull { it.id == targetId }
더 좋은 Kotlin 스타일
val target = users.find { it.id == targetId }
Kotlin에서는 “반복문을 눈으로 관리"하지 않음 → 컬렉션 함수 사용
map/filter/fold는 선언형 스타일 → 훨씬 안전하고 읽기 쉬움
6. StringBuilder 남용 → 템플릿 문자열 + trimIndent
Java 스타일
val sb = StringBuilder()
sb.append("Hello ")
sb.append(name)
sb.append("! Welcome")
return sb.toString()
Kotlin 스타일
return "Hello $name! Welcome"
멀티라인에서는
val query = """
SELECT *
FROM user
WHERE id = $userId
""".trimIndent()
7. instanceof → is + smart cast
Java 스타일
if (obj instanceof User) {
User user =(User) obj
user.run()
}
Kotlin 스타일
if (obj is User) {
obj.run()
}
is 체크 후 자동으로 Smart Cast 적용됨
8. final + getter/setter → data class
Java 스타일
public class User {
private String name;
private int age;
// getters & setters …
}
Kotlin 스타일
data class User(
val name: String,
val age: Int
)
equals, hashCode, toString 자동 생성
copy()로 immutable 객체 패턴 구현 가능
9. try-catch → runCatching
Java 스타일
try {
return riskyOperation()
} catch (e: Exception) {
log.error(e)
return null
}
Kotlin 스타일
return runCatching { riskyOperation() }
.onFailure { log.error(it) }
.getOrNull()
10. 싱글톤
Java static → Kotlin object
Java 스타일
public class UserCache {
private static final UserCache INSTANCE = new UserCache();
public static UserCache getInstance() { return INSTANCE; }
}
Kotlin 스타일
object UserCache {
val map = mutableMapOf<String, User>()
}
JVM 싱글톤 + thread safe + lazy 모두 기본 지원
728x90