Post

Java to Kotlin: 코드 변환

Java to Kotlin: 코드 변환

1. IntelliJ IDEA의 자동 변환 기능 활용

사용 방법:

  1. 변환하고 싶은 Java 파일(.java)을 엽니다.
  2. Code 메뉴 -> Convert Java File to Kotlin File을 선택합니다.
    • 단축키: Ctrl + Alt + Shift + K (Windows/Linux), Cmd + Alt + Shift + K (macOS)
  3. IntelliJ IDEA가 자동으로 Kotlin 코드로 변환해 줍니다. 필요한 경우, 변환된 코드에 대한 개선 사항을 제안하기도 합니다.

예시:

Java 코드 (User.java)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public class User {
    private String name;
    private int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        if (age != user.age) return false;
        return name != null ? name.equals(user.name) : user.name == null;
    }

    @Override
    public int hashCode() {
        int result = name != null ? name.hashCode() : 0;
        result = 31 * result + age;
        return result;
    }

    @Override
    public String toString() {
        return "User{" +
               "name='" + name + '\'' +
               ", age=" + age +
               '}';
    }
}

자동 변환된 Kotlin 코드 (User.kt)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.example // 필요시 패키지 추가

class User(var name: String?, var age: Int) {
    // equals, hashCode, toString은 data class가 아닐 경우 IntelliJ가 직접 생성해줌
    // 하지만 대부분의 경우 data class로 변환하는 것이 더 Kotlin스럽다.
    // 아래 2번에서 data class로 변환하는 방법을 설명합니다.
    
    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (javaClass != other?.javaClass) return false

        other as User

        if (name != other.name) return false
        if (age != other.age) return false

        return true
    }

    override fun hashCode(): Int {
        var result = name?.hashCode() ?: 0
        result = 31 * result + age
        return result
    }

    override fun toString(): String {
        return "User(name=$name, age=$age)"
    }
}

` `


2. Kotlin스럽게 코드 다듬기 (Idiomatic Kotlin)

2.1. Data Class 활용

Java의 POJO(Plain Old Java Object)는 Kotlin의 data class로 완벽하게 대체될 수 있습니다. equals(), hashCode(), toString(), copy() 등을 자동으로 생성해 줍니다.

Kotlin 코드 (개선된 User.kt)

1
data class User(var name: String, var age: Int)

팁: var 대신 val을 사용하여 불변(immutable) 객체로 만드는 것이 권장됩니다. 가능한 한 val을 우선적으로 사용하고, 변경이 필요한 경우에만 var을 사용하세요.

1
data class User(val name: String, val age: Int)

2.2. Null Safety 적용

Kotlin은 NullPointerException(NPE)을 방지하기 위한 강력한 Null Safety 기능을 제공합니다. 자동 변환 시 String?와 같이 nullable 타입으로 변환될 수 있습니다.

  • ? (Nullable Type): 해당 변수가 null 값을 가질 수 있음을 명시합니다.
  • !! (Non-null Assertion Operator): 개발자가 null이 아님을 확신할 때 사용하지만, null인 경우 NPE를 발생시키므로 사용을 지양하는 것이 좋습니다.
  • ?. (Safe Call Operator): null이 아니면 호출하고, null이면 null을 반환합니다.
  • ?: (Elvis Operator): null일 경우 기본값을 제공합니다.

Java 코드:

1
2
3
4
5
6
7
public String getDisplayName(String firstName, String lastName) {
    if (lastName != null && !lastName.isEmpty()) {
        return firstName + " " + lastName;
    } else {
        return firstName;
    }
}

Kotlin 코드 (변환 후 개선):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
fun getDisplayName(firstName: String, lastName: String?): String { // lastName을 nullable로 선언
    return if (!lastName.isNullOrEmpty()) { // isNullOrEmpty()는 null과 빈 문자열 모두 처리
        "$firstName $lastName" // String Interpolation
    } else {
        firstName
    }
}

// 또는 Elvis Operator 활용
fun getDisplayNameElvis(firstName: String, lastName: String?): String {
    return firstName + (lastName?.let { " $it" } ?: "")
    // lastName이 null이 아니면 " $lastName"을 반환, null이면 ""을 반환
}

// 더 간결하게
fun getDisplayNameConcise(firstName: String, lastName: String?): String =
    firstName + (lastName?.takeIf { it.isNotEmpty() }?.let { " $it" } ?: "")

2.3. 람다(Lambda) 및 고차 함수(Higher-Order Functions) 활용

Java 코드:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class CollectionExample {
    public static List<String> filterAndMap(List<String> names) {
        List<String> result = new ArrayList<>();
        for (String name : names) {
            if (name.startsWith("A")) {
                result.add(name.toUpperCase());
            }
        }
        return result;
    }

    // Java 8 Stream API 사용 시
    public static List<String> filterAndMapStream(List<String> names) {
        return names.stream()
                    .filter(name -> name.startsWith("A"))
                    .map(String::toUpperCase)
                    .collect(Collectors.toList());
    }
}

Kotlin 코드:

1
2
3
4
5
fun filterAndMap(names: List<String>): List<String> {
    return names
        .filter { it.startsWith("A") } // 람다를 사용하여 필터링
        .map { it.uppercase() }      // 람다를 사용하여 매핑
}

2.4. 확장 함수(Extension Functions) 활용

Java 코드:

1
2
3
4
5
6
7
8
9
10
11
public class StringUtil {
    public static String capitalizeFirstLetter(String str) {
        if (str == null || str.isEmpty()) {
            return str;
        }
        return str.substring(0, 1).toUpperCase() + str.substring(1);
    }
}

// 사용 예시:
// StringUtil.capitalizeFirstLetter("hello");

Kotlin 코드:

1
2
3
4
5
6
7
8
9
10
fun String.capitalizeFirstLetter(): String { // String 클래스에 확장 함수 추가
    return if (isNullOrEmpty()) {
        this
    } else {
        this.substring(0, 1).uppercase() + this.substring(1)
    }
}

// 사용 예시:
// "hello".capitalizeFirstLetter()

2.5. 싱글톤(Singleton) 객체

Java 코드:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class MySingleton {
    private static MySingleton instance = new MySingleton();

    private MySingleton() {}

    public static MySingleton getInstance() {
        return instance;
    }

    public void doSomething() {
        System.out.println("Doing something...");
    }
}

Kotlin 코드:

1
2
3
4
5
6
7
8
object MySingleton {
    fun doSomething() {
        println("Doing something...")
    }
}

// 사용 예시:
// MySingleton.doSomething()

2.6. 프로퍼티(Properties) 활용

Java의 getter/setter는 Kotlin의 프로퍼티로 대체되어 더욱 간결해집니다.

Java 코드:

1
2
3
4
5
6
7
8
9
10
11
public class Person {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

Kotlin 코드:

1
2
3
4
5
6
class Person {
    var name: String = "" // getter, setter가 자동으로 생성됨
}

// 또는 주 생성자에서 선언
// class Person(var name: String)

3. Java-Kotlin 상호 운용성 고려하기

  • Kotlin에서 Java 호출: Kotlin 코드는 Java 클래스, 메서드, 필드를 자연스럽게 호출할 수 있습니다. Java의 원시 타입은 Kotlin의 내장 타입으로 매핑됩니다.
  • Java에서 Kotlin 호출:
    • 패키지 레벨 함수 (Top-level functions): Kotlin 파일의 최상위에 선언된 함수는 Java에서 FileNameKt.functionName() 형태로 호출할 수 있습니다. (예: MyFileKt.myFunction())
    • @JvmStatic: Kotlin objectcompanion object 내의 함수/프로퍼티를 Java에서 정적(static) 멤버처럼 접근하게 해줍니다.
    • @JvmField: Kotlin 프로퍼티를 Java에서 필드처럼 직접 접근하게 해줍니다.
    • @JvmOverloads: 기본 파라미터를 가진 Kotlin 함수에 대해 Java에서 오버로드된 메서드를 자동으로 생성해줍니다.
    • @JvmName: Java에서 Kotlin 함수나 프로퍼티가 다른 이름을 가지도록 지정합니다.
  • SAM (Single Abstract Method) 변환: Kotlin은 Java 인터페이스의 단일 추상 메서드에 대해 람다를 사용하여 더 간결하게 인스턴스를 생성할 수 있습니다.

예시: @JvmStatic@JvmOverloads

Kotlin 코드:

1
2
3
4
5
6
7
8
9
class Greeter {
    companion object {
        @JvmStatic
        @JvmOverloads
        fun greet(name: String, prefix: String = "Hello"): String {
            return "$prefix, $name!"
        }
    }
}

Java 코드에서 호출:

1
2
3
4
5
6
7
8
9
10
public class JavaCaller {
    public static void main(String[] args) {
        // @JvmStatic 덕분에 Greeter.Companion이 아닌 Greeter에서 직접 호출 가능
        String greeting1 = Greeter.greet("Kotlin User"); // prefix 기본값 사용
        String greeting2 = Greeter.greet("Java User", "Hi"); // prefix 지정

        System.out.println(greeting1); // Output: Hello, Kotlin User!
        System.out.println(greeting2); // Output: Hi, Java User!
    }
}

4. 컨버팅 시 주의사항 및 팁

  • Nullability 명시: 자동 변환된 코드의 String?Int? 등을 보고, 실제로 null이 될 수 있는지 없는지 신중하게 판단하여 타입을 확정하세요. lateinit이나 by lazy를 적절히 활용하는 것도 좋습니다.
  • 상수(Constants): Java의 public static final 상수는 Kotlin에서 const val 또는 companion object 내의 val로 대체됩니다.
    1
    2
    
    // Java: public static final String MY_CONSTANT = "value";
    // Kotlin: const val MY_CONSTANT = "value" // 최상위 또는 object 내에서
    
  • 예외 처리: Kotlin은 checked exceptionunchecked exception을 구분하지 않지만, Java 라이브러리를 사용할 때는 여전히 try-catch 블록으로 예외를 처리해야 할 수 있습니다. runCatching과 같은 Kotlin의 함수형 예외 처리도 고려해 보세요.
  • 빌더 패턴: Java에서 많이 사용되는 빌더 패턴은 Kotlin의 명명된 인자(Named Arguments)와 기본 인자(Default Arguments)를 활용하면 더 간결하게 만들 수 있습니다. 또는 apply 스코프 함수를 사용하여 빌더와 유사한 문법을 구현할 수도 있습니다.
  • 제네릭(Generics): Java의 와일드카드(? extends, ? super)는 Kotlin의 out (공변성, covariant)과 in (반공변성, contravariant) 키워드로 대체됩니다.
  • 코드 스타일: Kotlin은 Java와 다른 코드 컨벤션을 가집니다. IntelliJ IDEA의 코드 포맷팅 (Ctrl + Alt + L 또는 Cmd + Alt + L)을 사용하여 Kotlin 스타일에 맞게 정리하는 습관을 들이세요.

This post is licensed under CC BY 4.0 by the author.