지난 2년간 코틀린(Kotlin)은 놀라운 성장을 이루며 개발 커뮤니티 곳곳에서 화제의 중심에 올랐고, 그 인기는 지금도 계속 상승 중입니다. 구글(Google)이 코틀린을 안드로이드 앱 개발의 공식 언어로 지정한 것도 이미 잘 알려진 사실입니다.
그런데도 많은 개발자들이 여전히 안드로이드 개발에 코틀린보다 자바를 선호하는데, 그 이유는 무엇일까요? 가장 큰 이유 중 하나는 주력 언어를 자바에서 코틀린으로 바꾸는 일에 아직 익숙하지 않아서입니다. 사람들은 익숙한 것에서 새로운 것으로 바꾸는 변화를 두려워하곤 하죠.
지금 바로 코틀린을 시작하는 쉬운 방법
사실 자바에서 코틀린으로 전환하는 것은 생각보다 훨씬 쉽습니다. 코틀린은 매우 유연한 언어이기 때문에, 코틀린 문법으로 코드를 작성하면서도 실제 스타일은 자바 그대로 유지할 수 있습니다!

자바에서 코틀린으로 전환할 때 알아두어야 할 핵심 사항을 하나씩 살펴보겠습니다.
1. 자바와의 완벽한 상호 운용성
코틀린은 자바와 완벽하게 상호 운용 가능하며, 그 반대 방향도 마찬가지입니다. 즉, 코틀린 코드에서 자바 코드를 자유롭게 호출할 수 있어 앱 마이그레이션이 매우 수월해집니다. 자바로 프로젝트의 절반쯤 진행했다면요? 상관없습니다. 그대로 코틀린으로 이어서 작성하면 됩니다.
2. 원시(Primitive) 데이터 타입의 부재
자바에서 int와 Int 중 무엇을 써야 할지 고민하는 데 반나절을 보낸 기억이 있으신가요? 코틀린에는 더 이상 원시 데이터 타입이 없으며, 모든 값이 객체(Object)로 래핑되어 관리됩니다.
3. 약간 변경된 문법 (하지만 금방 익힐 수 있습니다)
변수, 함수, 클래스 선언 등 일부 문법이 조금 변경되었지만, 자바 같은 OOP 배경을 갖춘 개발자에게는 큰 장애물이 되지 않습니다. 게다가 void 키워드와 세미콜론(';')이 제거되었습니다. 세미콜론 제거, 고마워요 코틀린!!
안드로이드 클래스를 활용한 기본 예제를 통해 살펴보겠습니다.
//상속과 구현은 이제 ':' 를 사용합니다
class BasicActivity : AppCompatActivity() {
//변수 선언은 변수명이 먼저 오고 타입이 뒤에 옵니다
var a :Int = 10 //var는 변경 가능(mutable)
val b :Int = 15 //val은 변경 불가능(immutable)
/*코틀린에서 초기값 없이 non-primitive 타입 변수를 선언하려면
lateinit 키워드를 붙여야 합니다*/
lateinit var someTextView:TextView
//new 키워드도 사라졌습니다
var gameList:List<Integer> = ArrayList()
//오버라이딩은 override 키워드를 사용합니다
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_basic)
}
}
4. 타입 추론(Type Inference)
코틀린은 변수에 대한 타입 추론을 제공해 개발을 한결 편하게 만들어 줍니다.
var a = 5 //자동으로 정수(Int)로 추론됩니다
var b = "John" //자동으로 문자열(String)로 추론됩니다
5. 널 안전성 검사(Null Safety)
코틀린은 널 포인터 예외(NullPointerException)를 방지하기 위한 널 안전성 검사를 제공합니다. null이 될 가능성이 있는 모든 변수에는 ? 기호가 표시되어야(반드시 표시해야) 합니다.
var a:String? = null //허용됨
var b:String = null //허용되지 않음또한 자바의 '십억 달러짜리 실수'라 불리는 NullPointerException을 피할 수 있도록 널 안전 호출도 지원합니다.
//허용되지 않음, a는 null일 수 있습니다
a.function()
//null이어도 널 포인터 예외를 던지지 않고 흐름이 계속됩니다
a?.function()
//강제 호출, NPE를 유발할 수 있으므로 값이 100% null이 아니라고 확신할 때만 사용하세요
a!!.function()6. static 키워드의 부재
static 키워드는 이제 Object 키워드로 대체되었습니다. 클래스와 메서드 모두에 적용되며, 메서드의 경우 정확히는 Companion Object를 사용합니다.
7. 변경된 switch문
switch문은 when이라는 새로운 키워드로 바뀌었습니다. 훨씬 깔끔하고 유연해졌습니다.
when (x) {
1 -> println("One")
2 -> print("Two")
else -> print("Others")
}8. 검사 예외(Checked Exception)의 부재
자바에서 연산이나 캐스팅을 할 때 빨간 경고가 뜨면서 예외 처리를 추가하라고 요구했던 기억나시나요? 코틀린에서는 이것이 사라졌습니다. 이제 IDE가 더 이상 예외 처리를 강제하지 않습니다!!
여기까지가 '스타일은 자바인 채로' 코틀린 개발을 시작하기 위해 꼭 알아야 할 내용입니다.
코틀린 진입 장벽이 어렵지 않다는 게 조금은 느껴지시나요? 이제 코틀린이 자바와 크게 다르지 않게 느껴질 겁니다.
코틀린이 제공하는 강력한 기능들

코틀린과 자바의 차이점을 살펴봤으니, 이제 코틀린만이 제공하는 기능들과 점진적으로 '코틀린다운' 방식으로 개발하는 방법을 알아보겠습니다.
1. 유연성(Flexibility)
코틀린을 사랑하게 되는 가장 큰 이유는 바로 이 언어의 유연성입니다. OOP 순수주의자이신가요? 요즘 가장 뜨거운 화제인 함수형 프로그래밍(Functional Programming)을 맛보고 싶으신가요? 놀랍게도 코틀린에서는 둘 다 가능합니다! 함수형 패러다임이 제공하는 모든 기능을 완벽히 갖추지는 못했지만, 충분히 활용할 만한 수준을 지원합니다.
2. 람다(Lambda) 지원
자바 8도 람다를 지원한다는 것은 알고 있습니다. 하지만 코틀린이 먼저 이를 도입했고, 훨씬 뛰어난 모습을 보여줍니다. 물론 처음에 람다 함수에 적응하는 것은 쉽지 않겠지만, 앞서 말했듯이 코틀린은 이 부분에서도 매우 유연합니다. 어떤 방식을 선택할지는 여러분의 몫입니다!
일부 안드로이드 라이브러리 역시 람다 지원을 제공합니다. 예를 들어 View.OnClickListener(안드로이드 개발자라면 누구나 익숙할 리스너죠):
val randomButton : Button
//람다 함수를 인자로 사용
randomButton.setOnClickListener{v -> doRandomStuff(v) }3. 데이터 클래스(Data Class)
코틀린은 Model/Entity 클래스의 대체재로 데이터 클래스를 제공합니다. 반복적인 setter/getter 작성이 필요 없으며, equals 메서드와 toString 함수가 기본 내장되어 있어 직접 구현할 필요가 없습니다. 활용법도 매우 간단합니다.
data class Person(
val id:String,
val name:String = "John Doe" //기본값 설정
)
//초기화 블록
var person = Person("id","Not John")4. 확장 함수(Extension Functions)
코틀린은 확장 함수를 지원해 코드 가독성을 높여 줍니다. 또한 기존 클래스 자체를 수정하지 않고도 해당 클래스에 새로운 기능을 추가할 수 있습니다.
fun Int.superOperation(anotherInt:Int):Int {
val superNumber = this * anotherInt + anotherInt
return superNumber
}
//이제 어디서든 이 함수를 호출할 수 있습니다
val someNumber = 5
val superNumber = someNumber.superOperation(10) //655. 이름 붙은 인자와 기본 인자(Named & Default Arguments)
C#처럼 코틀린도 메서드에 이름 붙은 인자와 기본 인자를 지원합니다. 모든 인자에 일일이 값을 전달할 필요가 없어집니다. 수정하고 싶은 값만 골라서 전달하면 끝! 열 번의 함수 호출마다 똑같은 값을 반복해서 넘겨주던 번거로움은 이제 안녕!
//함수 선언
fun someOperations(x: Int, y:Int, z:Int = 1){
return x+y+z
}
someOperations(1,2) //4를 반환
someOperations(y = 1, x = 1) //3을 반환6. 참조 동등성(Referential Equality)
코틀린은 참조 동등성 연산자('===')도 제공합니다. 두 변수가 현재 동일한 객체를 참조하고 있는지 확인할 수 있습니다.
var a = 10
var b = 10
a == b //true
a === b //역시 true, 원시 타입 객체 비교는 값만 확인합니다
data class Person (val name: String)
val p1 = Person("John")
val p2 = Person("John")
p1 == p2 //true
p1 === p2 //false7. 스마트 캐스팅(Smart Casting)
instanceof 대신 코틀린은 캐스팅과 타입 검사를 위한 더 간결하고 가독성 좋은 문법을 제공합니다.
//타입 검사
//타입 검사를 통과하면 코틀린의 스마트 캐스트가 자동으로 객체를 해당 타입으로 변환합니다
if (shape is Circle) {
println("it's a Circle")
shape.radius //자동으로 Circle 타입으로 캐스팅됩니다
} else if (shape is Square) {
println("it's a Square")
} else if (shape is Rectangle) {
print("it's a Rectangle")
}코틀린은 명시적 캐스팅을 위한 as 키워드도 제공합니다.
//정적 캐스팅과 유사하게 동작하므로 안전하지 않은 방식으로 간주됩니다
var anotherShape = shape as Circle
//대신 안전 캐스팅(safe casting)을 사용할 수 있습니다
var safeShape = shape as? Circle8. 코루틴(Coroutines)
자바에서 비동기 호출은 늘 번거로운 작업이었습니다. Thread를 생성하면 코드가 길어지고 가독성이 크게 떨어지죠. 코루틴은 기존의 스레드 작업을 대체하여 깔끔하고 견고한 코드 작성을 가능하게 해 줍니다.
import kotlinx.coroutines.*
fun main() {
// 백그라운드에서 새 코루틴을 실행하고 즉시 다음 코드로 진행
GlobalScope.launch {
// 1초간 논블로킹 딜레이 (기본 시간 단위는 ms)
delay(1000L)
// 딜레이 후 출력
println("World!")
}
// 코루틴이 지연되는 동안 메인 스레드는 계속 실행됩니다
println("Hello,")
// JVM을 유지하기 위해 메인 스레드를 2초간 블로킹
Thread.sleep(2000L)
}코루틴은 Job Joining, Channel, Shared Context, Parents 같은 더 복잡한 연산도 지원합니다. 자세한 내용과 추가 사용법은 공식 문서를 참고하시기 바랍니다.
마무리하며
정리하자면, 코틀린 시작은 매우 쉽습니다. 자바를 작성하듯이 코틀린 코드를 작성할 수 있습니다. 그리고 코틀린은 자바를 사용할 때보다 우위를 점할 수 있는 여러 강력한 기능을 제공합니다.
물론 지금도 자바는 여전히 강력한 언어입니다. 하지만 새로운 것을 배우기 시작하는 일이 결코 손해일 리 없습니다. 프로그래머인 우리는 평생 학습을 받아들이고 배움을 멈추지 않아야 합니다. 코틀린의 가장 큰 장점은 자바에서 아주 쉽게 전환할 수 있다는 점입니다.
읽어주셔서 감사합니다. 재미있게 읽으셨기를 바라며, 실무에도 도움이 되길 희망합니다! 코틀린 탐험에 행운을 빌고, 다음 글에서 다시 만나요.