본문 바로가기
공부/안드로이드

안드로이드의 언어

by 화릿불 2025. 8. 20.

 

안드로이드에서 사용할 수 있는 언어는 어떤 게 있을까?

 

기본적으로 JavaKotlin이 있고, C++은 JNI(Java Native Interface)를 활용해서 사용가능하다.

하지만 오랜시간 Java가 사용된 만큼 대부분의 레거시는 Java로 작성되어 있고 생태계 또한 압도적이다.

실제로 프로젝트를 진행하며 참고했던 대부분의 자료는 Java로 작성되어 있었고, Kotlin은 주로 Jetpack Compose 같은 최신 라이브러리나 UI 작성에 많이 활용되는 것으로 파악했다. C++은 주로 성능이 필요한 곳에서 사용되는데 하드웨어에 직접 접근해서 연산을 수행하는 부분의 최적화나 네이티브(C/C++) 코드로 제공되는 라이브러리 등에서 사용된다.

 

안드로이드의 코드 대부분은 Java와 Kotlin으로 작성되는데 그렇다면 이 둘에는 어떤 차이가 있을까?

 

기본적으로 Kotlin의 탄생 배경을 보면 기존 오래된 Java의 한계를 극복하고 코드를 간결, 모던하게 작성하고자 탄생한 언어이다.

특히 불필요하게 장황한 코드, Null 안전성 문제, 현대적인 함수형 프로그래밍 지원 부족 등을 해결하는 것이 목적이었다.(지금은 Java도 제네릭 타입이나 스트림 등 현대적인 기능도 제공하고 있다.)
그 결과, Kotlin은 기존 Java 코드와 100% 호환되면서도 더 간결하고 안전하며 현대적인 개발을 가능하게 만들어졌다.

 

Null 안정성

안드로이드 개발을 하다보면 앱 크래시로 인해 앱이 종료되는 현상을 볼 수 있다.

앱 크래시는 굉장히 치명적이므로 반드시 막아주어야 하는데 가장 빈번한 원인이 바로 NullPointerException(NPE)이다.

 

Java의 경우, null 가능성을 컴파일러가 알 수 없기 때문에, 런타임에 다음과 같은 코드에서 예외가 발생할 수 있다.

String name = null;
int length = name.length();

상식적으로 비어있는 객체에 접근해서 length 함수를 호출하는 것 자체가 불가능하다. 아니 접근 자체가 허용되지 않는다.

하지만 Kotlin의 경우는 어떨까?

val name: String? = null // ?가 붙으면 "nullable" 타입이다.
val length = name?.length ?: 0

Kotlin의 경우 자꾸 "?"가 붙어있는 것을 볼 수 있다.

Kotlin은 언어 차원에서 null safety를 지원한다. 즉, 변수가 null일 수 있는지 여부를 타입 시스템에 반영한다.

 

위 코드에서 볼 수 있듯이 Kotlin에는 null safety를 위한 몇 가지 주요 키워드/연산자가 있다.

 

? (Safe Call 연산자)

  • name?.length는 name이 null이 아닐 경우에만 length를 호출한다.
  • 만약, null일 경우에는 전체 표현식이 null을 반환한다.
  • 따라서 앱이 죽지 않고 "안전하게" 넘어갈 수 있다.

?: (Elvis 연산자)

  • Safe Call의 결과가 null일 경우 기본값을 지정한다.
  • name?. length?: 0 → name이 null이면 0을 반환.
  • 일종의 "null 대비 fallback 값"을 작성하는 방식이다.

!! (Non-null Assertion 연산자)

  • name!! 은 "개발자가 확실히 null이 아니다"라고 강제하는 연산자다.
  • 하지만 만약 실제로 null이라면 NPE가 터진다.
  • 즉,!! 는 마지막 수단이며 지양해야 한다.
이렇게 Kotlin은 컴파일 시점부터 null 가능성을 체크해주기 때문에 런타임 NPE 발생 가능성을 크게 줄인다.

Java에서는 모든 객체가 사실상 nullable이라, 개발자가 매번 null 체크를 직접 해야 한다는 점에서 큰 차이가 있고 더 간결하다.

 

타입추론

위 두 코드를 비교해 보면 또 하나의 차이가 있다.

Java는 "String", "int"등 자료형을 명시하고 있지만 Kotlin은 "val" 하나밖에 없다.

즉, Kotlin은 자료형을 명시하지 않아도 자동으로 타입 추론이 가능하기 때문에 "val"만 적어도 충분하고 자료형을 명시해서 관리하는 것도 가능하다. 그렇기에 코드 작성 자체는 더 편하고 간결하게 작성할 수 있어 좋다고 생각하지만 타입이 명시하지 않을 경우 가독성이 떨어질 수 있고 협업 시 혼동이 생길 수 있다. 일장일단인 셈.

 

비동기 작업

다음으로는 비동기 작업이다.

Java에서는 주로 RxJava나 Thread, Executor 기반으로 비동기 처리를 했지만, Kotlin은 언어 차원에서 Coroutine이라는 경량 스레드를 제공한다. 따라서 비동기 코드를 훨씬 직관적으로 작성할 수 있다.

// 스레드 코스트가 큰 편
new Thread(() -> {
    // 백그라운드 작업
    runOnUiThread(() -> { "작업" });
}).start();

// 비동기로 작업을 실행하지만 블로킹 발생 가능
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<String> future = executor.submit(() -> "작업");
String result = future.get();

// 체이닝은 가능하지만 그만큼 장황해지고 콜백 지옥, 에러 처리가 어려움
CompletableFuture.supplyAsync(() -> "작업")
    .thenApply(result -> result + " 완료")
    .thenAccept(System.out::println);

Java에서는 Thread, Executor, CompletableFuture, RxJava 등을 통해 비동기를 처리했지만, 코드가 장황하고 관리 비용이 크다.

lifecycleScope.launch {
    try {
        val data = withContext(Dispatchers.IO) { "비동기 작업" }
        textView.text = data
    } catch (e: Exception) {
        // 에러 핸들링
    }
}

 

반면 Kotlin은 언어 차원에서 Coroutine을 지원해 경량 스레드, 구조적 동시성, 가독성 높은 코드를 작성할 수 있다.
특히, 안드로이드에서는 lifecycleScope 같은 스코프와 결합해 생명주기에 안전하게 비동기 처리를 할 수 있다.

경량 스레드라고 부르는 이유는 기존 OS의 네이티브 스레드는 스레드 하나를 생성하고 전환(Context Switching) 하는데 비용이 큰 편이다. 하지만 Coroutine의 경우, 스레드가 아니다. 하나의 스레드 안에서 여러 개가 동작하고 있는 하나의 태스크라고 보는 것이 더 정확하다. OS의 개입 없이 코드 레벨에서 전환이 일어나므로 작업을 중단(suspend) 했다가 재개(resume) 하는데 비용이 거의 없기 때문에 매우 가볍다고 볼 수 있다.

 

실제로 5만개의 단순 작업을 Thread, Executor, Coroutine 코드로 작성해서 실행시간과 메모리 사용량을 비교했다.

아래 이미지는 각각의 실행 시간과 메모리 사용량을 비교한 결과이다.

사용한 코드는 다음 깃허브 링크에 있다.

[Thread, Executor, Coroutine 비교]

Thread 보다는 Coroutine이 성능이 더 좋았고 Executor와 비교했을 때는 트레이드오프가 있음을 확인할 수 있었다.

 

이것저것 찾아본 결과, 자바 또한 지속적으로 발전하고 있어 Java 21부터 도입된 가상 스레드(Virtual Thread)는 코루틴과 유사한 수준의 높은 동시성 처리 성능을 보여주고 있는 것으로 확인했다.

하지만, 그럼에도 불구하고 현재 안드로이드 개발 환경에서 비동기 작업을 처리할 때는 안드로이드의 생명주기와 완벽하게 통합 가능하고 메모리 누수를 방지할 수 있는 Coroutine이 이점을 가질 것이라 생각한다.

 

확장 함수

그 다음 차이점을 보자.

Kotlin은 기존 클래스의 메서드를 쉽게 확장할 수 있도록 지원한다.

fun String.firstChar(): Char = this[0]
println("Hello".firstChar())

예를 들면, 기존 String 클래스에 firstChar라는 메서드는 없다. 하지만 위와 같은 코드를 메서드처럼 작성하여 String의 첫 번째 문자를 출력하는 확장 함수를 손쉽게 작성하고 이용할 수 있다.

하지만 Java의 경우, 이러한 기능을 사용하기 위해 상속이나 유틸 클래스를 따로 작성해야 하는데 실제 앱 규모로 넘어가면서 기능이 많아지거나 복잡해질수록 상속과 클래스 수가 엄청나게 늘어날 것이다. 이것까지 고려하게 된다면 상당한 코스트가 세이브된다.

 

근래에 들어서 구글은 안드로이드의 공식 언어로 Kotlin을 채택했다. 따라서 Compose 같은 최신 Jetpack 라이브러리는 Kotlin 전용으로 설계되었기 때문에, 새로운 UI 개발은 사실상 Kotlin을 선택할 수밖에 없다. (호환을 위해 Java에서도 API 호출을 통해 이용은 가능하다.) 

 

종합하면, Java는 여전히 풍부한 레거시와 안정성을 가진 언어이지만, 새로운 안드로이드 개발의 중심은 Kotlin으로 옮겨가고 있다고 생각한다.

물론 Java 역시 Virtual Thread와 같은 혁신을 통해 끊임없이 발전하고 있기에, 어떤 언어가 기술적으로 절대적으로 우월하다고 단정하기는 어렵다. 각 언어가 가진 생태계와 특성을 이해하는 것이 중요하다.

현시점에서 구글의 Kotlin 우선 정책, Jetpack Compose와 같은 최신 프레임워크의 등장으로 인해, 새로운 안드로이드 프로젝트를 시작한다면 Kotlin을 선택하는 것이 더 자연스럽고 효율적인 길일 것 같다. 따라서 안드로이드 개발자에게 코틀린은 이제 선택이 아닌 필수 역량이 되었다고 생각한다.

 

'공부 > 안드로이드' 카테고리의 다른 글

안드로이드의 기본 요소  (0) 2025.08.27
안드로이드 앱이 만들어지는 과정  (0) 2025.08.21
안드로이드 개발 공부  (0) 2025.08.19