After ADS ’19: Java❤️ Kotlin, 행복하자 🎵

AndroidDevSummit 세션 내용을 정리해봅니다.

SOUP
SOUP
Nov 8 · 20 min read
Java ❤️ Kotlin, Happy Together 🎵

아무래도 안드로이드는 이제 10년이 지난 플랫폼이어서 Kotlin이 first language로 소개된지는 좀 지났지만, 아직도 프로젝트에서 Java와 Kotlin을 함께 사용하는 경우가 많습니다. 😆
혹시 놓치고 있던 내용은 없었는지 내용을 정리해봅니다. 🗒

Calling into the Java from Kotlin

Kotlin에서 Java를 호출할 때!

Nullability?

Java는 기본적으로 값들이 null이 될 수 있고, Kotlin은 될 수 없습니다. 그래서 Kotlin에서 Java API를 호출할 때, 함수의 매개변수 / 반환값이 Nullable인지 판단할 수 없습니다.

간혹 런타임의 안정성을 고려해서 모든 Kotlin 코드에 ? 을 붙이는 경우도 있는데요. 좋지 않은 방법이라고 생각됩니다.

Nullability annotations

그렇다면 Kotlin에서 사용할 때, Null이 되지 않는 것을 알려줄 방법은 없을까요? Nullability Annotation(@Nullable, @NonNull)을 사용하면 됩니다.

아래는 Kotlin에서 인식할 수 있는 Annotation 목록입니다.

  • JetBrains (org.jetbrains.annotations)
  • Android (com.android.annotations, android.support.annotations, androidx.annotations)
  • JSR-305 (javax.annotation)
  • FindBugs (edu.umd.cs.findbugs.annotations)
  • Eclipse (org.eclipse.jdt.annotation)
  • Lombok (lombok.NonNull)

아래처럼 Java 코드를 변경하면, Kotlin 코드가 더 괜찮아집니다.

// Without annotation

Property prefixes (get / set / is)

Java bean 스타일을 따르면, Kotlin에서 Property처럼 사용할 수 있습니다.

Getters/Setters

  • get: get-으로 시작하는 매개변수 없는 메소드
  • set: set-으로 시작하는 단일 매개변수 메소드
  • is: is-로 시작하면서 setter와 이름이 같은 getter 메소드
public final class User {
public String getName() { ... }
public void setName(String name) { ... }
public boolean isActive() { ... }
}

Keywords

Java는 버전이 업데이트될 때, 새로운 키워드가 거의 추가되지 않았고 이전 코드가 잘 동작하는 것도 보장하고 있습니다. 하지만 Kotlin은 새로운 언어라서 고유한 키워드를 사용합니다. 이로 인해, 키워드 중복이 있을 수 있습니다.

fun, is, in, object, typealias, typeof, val, var, when …

위의 키워드들은 Kotlin에서 사용하는 것들로, Java에서는 다른 용도로 사용할 수 있었지만 Kotlin 코드에서는 그렇지 않습니다. 즉, Java의 함수나 매개변수에서 이런 키워드를 사용하고 있으면, Kotlin에서 호출할 때 문제가 됩니다.

public boolean is(SomeObject input) { ... }

아래는 해결하는 방법입니다.

  • Option 1: (Recommended) 이름을 변경한다.
public boolean isSame(SomeObject input) { ... }
  • Option 2: 중복되는 부분을 ``로 감싼다.
public boolean is(SomeObject input) { ... }

Any

Kotlin Extension Function / Property에서 아래 이름을 사용하지 마세요.

also, apply, asDynamic, ensureNeverFroze, n, freeze, hashCode, Iterator, let, objcPtr, pin, run, runCatching, takeIf, takeUnless, to, unsaveCast, usePinned, isFrozen, javaClass, jsClass

Operator Overloading

Java는 연산자 오버로딩 기능이 없지만, Kotlin은 있습니다.
Kotlin에서는 연산자를 함수로 인식합니다.

자세한 것은 아래 표를 참고하세요.

아래처럼 Java에 정의한 함수도 Kotlin에서 연산자로 사용할 수 있습니다. 😆

public final class RomanNumeral {
private final String value;
public RomanNumaral(String value) {
this.value = value;
}
public RomanNumeral plus(RomanNumeral other) {
// convert roman numeral to decimal
// add
// convert result to roman numeral
return result
}
}

SAM (Single Abstract Method)

Kotlin은 SAM conversion을 지원합니다.

public interface Runnable {
void run();
}

함수 매개변수에 SAM을 사용하려면, 마지막 매개변수에 선언되어야 합니다.

// Java
public static int calculate(Operation operation,
int firstNumber, int secondNumber) { ... }

// Kotlin
calculate({ ... }, 6, 7)

아래처럼 순서를 변경하면, SAM이 적용됩니다.

// Java
public static int calculate(int firstNumber, int secondNumber,
Operation operation) { ... }

// Kotlin
calculate(6, 7) { ... }

하지만 SAM conversion은 Java interop에서만 동작합니다.
Kotlin에서는 함수를 Type으로 지원하기 때문에 SAM이 불필요합니다. 🤔

interface MyListener {
fun onCheckedChanged(radioGroup: RadioGroup, resId: Int)
}

Kotlin에서는 함수 타입을 사용하여 SAM과 비슷하게 사용할 수 있습니다.

fun setMyListener(listener: (RadioGroup, Int) -> Unit) { ... }

Java Interface를 사용하더라도 Kotlin 코드에서는 SAM이 동작하지 않습니다.

// RadioGroup.java
public interface OnCheckedChangeListener {
void onCheckedChanged(RadioGroup var1, int var2);
}

Calling Kotlin from the Java

mData = KotlinCode.call

@JvmName, @JvmMultifileClass

Java Util 코드를 Kotlin 코드로 변경할 때, 참고할 만한 내용입니다.

public class Utils {
public static String timeSince(Date date) {...}

private Utils() {}
}

Annotation을 이용하여 DatesKt 대신 다른 이름으로 생성할 수 있습니다.

@JvmMultifileClass     // 다수의 파일을 동일한 클래스로 병합
@file:JvmName("Utils") // 클래스명 지정
package com.example.myapplication

호출하는 Java 코드는 기존과 동일하게 사용 가능합니다.

// App.java
mTimeSinceView.setText(Utils.timeSince(date));

@JvmField

Data 클래스는 기본적으로 getter/setter를 갖습니다.

// Message.kt
data class Message(
val user: User,
val text: String,
val received: Date
)

@JvmField Annotation을 이용하여, 변수로 노출할 수 있다.

// Message.kt
data class Message(
@JvmField val user: User,
@JvmField val text: String,
@JvmField val received: Date
) {

@JvmField // X, 함수로 다뤄져 동작하지 않습니다.
val nameWithId: String get() = ...

// lateinit는 필드로 노출됩니다.
lateinit var attachment: Any
}

@JvmName

// User.kt
data class
User(
val id: Long,
var name: String,
val isVerified: Boolean,
var likesPink: Boolean
)

@JvmName Annotation을 이용하여, getter/setter 이름을 변경할 수 있다.

// User.kt
data class
User(
val id: Long,
@set:JvmName("changeName")
var name: String,
val isVerified: Boolean,
@get:JvmName("likesPink")
var likesPink: Boolean
)

@JvmStatic

아래처럼 Service 코드를 Kotlin으로 전환했다고 가정합시다.

// MyService.java
public class MyService {
void doWork() { ... }

Java에서 schedule(context) 함수를 호출하면, Companion이 붙습니다.

// Java
MyService.Companion.schedule(this);

Annotation을 추가하여 기존 코드를 유지할 수 있습니다.

// MyService.kt
class MyService {
internal fun doWork() {...}

companion object {
@JvmStatic
fun schedule(context: Context) {...}
}
}

@JvmStatic은 아래 부분에 사용할 수 있습니다.

  • companion object의 함수와 속성
  • named object의 함수와 속성
  • interface의 companion object (Kotlin 1.3 ⬆️ / JVM target 1.8 ⬆️)

@JvmOverloads

Default Parameters는 Java 언어에서 기본으로 지원되지 않습니다.

@JvmMultifileClass
@file:JvmName("Utils")

@JvmOverloads Annotation을 이용하면, Java 언어에서 사용할 수 있고, @JvmName 을 이용하여 이름을 변경할 수도 있습니다.

@JvmMultifileClass
@file:JvmName("Utils")

@Throws

Kotlin은 checked exceptions이 없습니다. 따라서 Java 언어에서 호출할 때는 try-catch 문이 불필요하다는 warning이 보이게 됩니다.

Checked exceptions이란?
Java에서 Compile time에 Exception Handling 여부를 확인하는 장치

// ChatStream.kt
class ChatStream {
fun sendMessage(message: Message) {...}
}

이 때, @Throws 를 이용하여 Checked exception을 사용할 수 있습니다.

// ChatStream.kt
class ChatStream {
@Throws(IOException::class)
fun sendMessage(message: Message) {...}
}

One More Thing…

Feature leak prevention

안드로이드 앱의 feature 유출에 대한 내용입니다.

  • Function Parameters
    함수 매개변수의 NonNull을 확인하는 목적으로 매개변수 이름을 String으로 Intrinsics 함수를 호출하고 있습니다. 이 String은 Proguard로 난독화되지 않는 부분이므로, 민감한 이름은 함수 매개변수에 사용하지 않을 것을 권장합니다.
// Repository.kt
class Repository {
fun fetchDataRemotely(
secretFeatureDataSource: SecretFeatureDataSource) {...}
}
  • Variable Names
    변수를 사용하는 경우에도 변수 이름이 String으로 노출될 수 있습니다.
// Repository.kt
class Repository {
@Inject
lateinit var
secretFeatureDataSource: SecretFeatureDataSource

fun fetchDataRemotely() {
secretFeatureDataSource.fetchDataFromServer()
// ...
}
}
  • Extension function name
    코틀린 확장함수를 사용하는 경우에도 함수 이름이 노출될 수 있습니다.
// Utils.kt
@file:JvmName("Utils")
package com.example.myapplication.newfeatureinkotlin
  • Proguard Rules for this

위의 경우에 변수 명을 개발자가 직접 난독화할 수도 있지만, 개발 생산성이 크게 저하될 겁니다. 아래처럼 Proguard Rule을 추가하여, Release 버전에 포함되지 않게 할 수 있습니다.

-assumenosideeffects class kotlin.jvm.internal.Intrinsics {
public static void checkParameterIsNotNull(...);
public static void throwUninitializedPropertyAccessException(...);
# More checks
}

지금까지 Kotlin과 Java를 함께 사용할 때, 주의해야 할 점에 대한 세션 내용을 살펴봤습니다. 많은 분들이 이미 알고 있을 내용이 많지만, 마지막의 Feature Leak Prevention 내용은 도움이 될거라 생각합니다.

다음 번에는 다른 영상에 대한 내용을 정리해보겠습니다.

SOUP

Written by

SOUP

Android Developer in South Korea.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade