ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 12-1. 자바 상호 운용성 : 자바 코드를 코틀린에서 사용하기
    Study(종료)/Kotlin 22.09.13 ~ 12.18 2022. 12. 3. 14:32

    12장

     

    12-1. 자바 상호 운용성 : 자바 코드를 코틀린에서 사용하기

    12-2.  자바 상호 운용성 : 코틀린 코드를 자바에서 사용하기


    12장은 자바와 코틀린의 상호 운용성에 관한 내용이다.

    이런 주제는 자바 코드와 코틀린 코드가 공존해야 하는 프로젝트에서 중요한 역할을 한다고 한다.

    코틀린과 자바 타입이 서로 어떻게 매핑되는지 살펴보고, 코틀린 선언을 자바에서 보면

    어떤 식으로 보이는지, 반대로 자바 선언을 코틀린에서 어떻게 바라보는지 설명한다.

    자바 / 코틀린 상호 운용성을 제어할 때 도움이 되는 언어적 특징도 작성해보겠다.

     

    12-1. 자바 코드를 코틀린에서 사용하기

     

    코틀린은 JVM을 주 대상으로 설계됐기 때문에 상당히 쉽게 자바 코드를 코틀린에서 사용할 수 있다.

    주로 발생하는 문제들은 자바에 없는 코틀린 기능으로 인한 것이다.

    예를들어 자바는 널 안전성을 타입 시스템을 통해 제공하지 않지만,

    코틀린은 항상 어떤 타입에 대해 널 가능 여부를 지정해야 한다.

    자바 타입은 보통 이런 정보가 부족하다.

    자바와 코틀린 양쪽에서 해결할 수 있는 방법에 대해 작성해보겠다.

     

    1) 자바 메서드와 필드

    대부분의 경우 자바 메서드를 아무 문제 없이 코틀린 함수처럼 노출시킬 수 있다.

    캡슐화되지 않은 자바 필드를 코틀린에서는 명확한 접근자가 있는 프로퍼티처럼 쓸 수 있다.


    2) Unit와 Void

    코틀린에는 반환값이 없음을 나타내는 void 키워드가 없다.

    따라서 자바에서 void함수는 코틀린에서 Unit을 반환하는 함수로 보인다.

    이런 함수를 호출하고 호출 결과를 저장하며 컴파일러가 Unit 객체에 대한 참조를 생성한 후 저장해줄 것 이다.

     

    연산자 관습

    Map.get() 같은 몇몇 자바 메서드는 코틀린의 연산자 관습을 만족한다.

    이런 자바 메서드에는 operator 키워드가 붙어있지 않지만,

    코틀린에서는 이들을 마치 연산자 함수인 것 처럼 연산자를 통해 사용가능하다.

    예를들어 자바 리플렉션 API의 Method 클래스에는 invoke() 메서드가 있기 때문에

    이 클래스를 마치 함수처럼 호출할 수 있다.

    val length = String::class.java.getDeclaredMethod("length")
    println(length("AABBCC")) // 6

    하지만 중위 호출 문법을 자바 메서드에 적용할수는 없다.


    3) 합성 프로퍼티

    자바에는 합성 프로퍼티가 없고, 게터와 세터를 사용하는 일이 많다.

    이로 인해 코틀린 컴파일러는 자바 게터나 세터를

    일반적인 코틀린 프로퍼티처럼 쓸 수 있게 합성 프로퍼티를 노출시켜준다.

    (컴파일러가 합성 프로퍼티를 만들어줄 수 있으려면) 접근자는 다음 관습을 따라야만 한다.

     

    ● 게터는 파라미터가 없는 메서드여야 하며, 메서드 이름이 get으로 시작해야 한다.

    ● 세터는 파라미터가 하나만 있는 메서드여야 하며, 메서드 이름이 set으로 시작해야 한다.

     

    예를들어 다음 Person.java 클래스가 있다고 하고,

    // Person.java
    public class Person{
        private String name;
        private int age;
    
        public Person(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;
        }
    }

    코틀린에서 호출하게 되면

    // main.kt
    import Person
    
    fun main() {
        val person = Person("John", 25)
        person.name = "Harry"
        person.age = 30
    
        println("${person.name}, ${person.age}") 
        // Harry, 30
    }

    이 클래스의 인스턴스를 마치 name과 age라는 가편 프로퍼티가 정의된 것 처럼 사용가능하다.

     

    이 관습은 게터 메서드만 있는 경우에도 동작한다.

    이 경우 자동으로 만들어지는 프로퍼티는 불변 프로퍼티가 된다.

    코틀린은 쓰기 전용 필드를 제공하지 않으므로, 자바 클래스에 세터 메서드가 있지만

    게터가 없는 경우 아무 프로퍼티도 노출되지 않는다

     

    다른 관습으로 게터 이름이 is로 시작할 수도 있다.

    이 경우 합성 프로퍼티는 게터와 이름이 같다.

    Person 클래스를 변경해 접근자가 있는 boolean 필드를 추가해 살펴보자

    public class Person{
        
        ...
    
        private boolean isEmployed;
        
        public boolean isEmployed(){
            return isEmployed;
        }
    
        public void setEmployed(boolean employed) {
            isEmployed = employed;
        }
        
        ...
    }

    코틀린 코드는 이 접근자를 isEmployed 프로퍼티로 사용할 수 있다.


    4) 플랫폼 타입

    자바가 nullable / not-null type을 구분하지 않기 때문에 일반적으로 코틀린 컴파일러는

    자바 코드에서 비롯된 객체의 널 여부에 대해 아무런 가정도 할 수 없다.

    따라서 코틀린 컴파일러는 자바 코드가 노출하는 타입에 대한 널 안전성 검사를 완화시켜

    자바 타입을 명확한 널 가능성이 지정되지 않은 타입인 것 처럼 취급한다.

    코틀린에서 자바 코드로부터 비롯된 객체는 플랫폼 타입(platform type)이라는 특별한 타입에 속한다.

    플랫폼 타입은 nullable / not-null type 둘 다 될 수 있으며

    이런 타입에 대한 타입 안전성 보증은 자바와 동일하다.

    // Person.java
    public class Person{
        private String name;
        private int age;
        
        public Person(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; }
    }
    // main.kt
    fun main() {
        val person = Person("John", 25)
        println(person.name.length) // 4
    }

    위 코드에서 person.name은 컴파일러가 널 가능성을 판단할 수 없는 플랫폼 타입이다.

    그래서 코드는 컴파일이 되지만,

    프로그램이 length에 접근하는 부분에 대한 널 검사는 런타임으로 미뤄진다. 

    그래서 아래 코드는 컴파일은 되지만 런타임 에러가 발생한다.

    fun main() {
        val person = Person(null, 25)
        println(person.name.length) // RUNTIME ERROR
        // NullPointerException
    }

    intellij에서 ctrl + shift + p 를 person.name 식에서 실행하면 String! 이라는 타입을 볼 수 있는데,

    이는 표시된 타입이 String?일 수도 있고 String일 수도 있다는 의미이다.


    플랫폼 타입의 식을 변수에 대입하거나 명시적인 타입을 지정하지 않고

    함수에서 반환하면 플랫폼 타입이 전파된다.

    타입을 명시적으로 지정하면, 플랫폼 타입을 nullable / not-null 타입으로 강제할 수 있다.

    fun Int.toBigInt1() = BigInteger.valueOf(toLong())
    fun Int.toBigInt2(): BigInteger = BigInteger.valueOf(toLong())
    
    fun main() {
        val num1 = 123.toBigInt1() // BigInteger! type
        val num2 = 123.toBigInt2() // BigInteger(not-null) type
    }

    5) 널 가능성 어노테이션

    자바 세계에서 널 안전성을 보장하는 일반적인 방법은 어노테이션을 쓰는 것 이다.

    이 어노테이션을 사용하면 해당 자바 타입은

    코틀린에서 nullable / not-null tpye 중 하나로 정해지며 플랫폼 타입으로 지정되지 않는다.

    // Person.java
    import org.jetbrains.annotations.NotNull;
    public class Person{
        @NotNull private String name;
        private int age;
    
        public Person(@NotNull String name, int age){
            this.name = name;
            this.age = age;
        }
    
        @NotNull public String getName(){ return name; }
        public void setName(@NotNull String name){ this.name = name; }
        public int getAge() { return age; }
        public void setAge(int age) { this.age = age; }
    }

    이 코드에 대응하는 타입을 코틀린 코드로 보면 아래와 같이 Stirng으로 바뀐다.

    코틀린 컴파일러가 지원하는 널 가능성 어노테이션은 아래에서 확일할 수 있다.

    https://kotlinlang.org/docs/java-interop.html#nullability-annotations

     

    타입 파라미터에 어노테이션이 붙지 않으면 코틀린 컴파일러는 플랫폼 타입을 사용한다.

    // Person.java
    public class Person{
        ...
        @NotNull private Set<Person> friends = new HashSet<>();
        @NotNull public Set<Person> getFriends() { return friends; }
        ...
    }
    //main.kt
    fun main() {
        val person = Person("John", 25)
        person.friends // (Mutable)Set<Person!> 
    }

    따라서 위 person.friends는 (Mutable)Set<Person!>이 된다.


    6) 자바 / 코틀린 타입 매핑

    자바와 코틀린에서 비슷한 의미를 나타내는 타입이 있다.

    자바 원시 타입이나 원시 타입에 상응하는 박싱 타입은 코틀린에 기본 타입에 대응하며

    이 매핑은 역방향으로도 적용된다.

    자바 타입 코틀린 타입
    byte / Byte Byte
    short / Short Short
    int / Integer Int
    long / Long Long
    char / Charactor Char
    float / Float Float
    double / Double Double

     


    java.lang 패키지에 들어있는 원시 타입이 아닌 내장 클래스중 일부도

    kotlin 패키지에 상응하는 클래스로 매핑되며, 자바 / 코틀린의 클래스 이름이 같다

    이에 해당하는 내장 클래스들은 다음과 같다. 유일한 차이는 코틀린 Any로 매핑되는 자바의 Object이다.

     ● Object / Any                                                  

     ● Cloneable

     ● Comparable

     ● Enum

     ● Annotation 

     ● CharSequence

     ● String

     ● Number

     ● Throwable

     

    코틀린으로 매핑된 자바 클래스의 정적 멤버를 코틀린쪽 동반 객체에서 직접 언급할 수 없으며

    이를 사용하려면 자바 클래스의 전체 이름을 언급해야 한다.

    val n = java.lang.Long.bitCount(234)

    코틀린 표준 컬렉션 타입은 java.util 패키지에 있는 대응되는 컬렉션 타입으로 매핑된다.

    하지만 자바 > 코틀린 방향으로의 매핑은 플랫폼 타입을 만들어내는데,

    이유는 자바 컬렉션은 자바 컬렉션은 불변 / 가변 구현이 같은 API를 사용하기 때문이다.

    매핑되는 타입은 다음과 같다.

     ● Iterable / Iterator / ListIterator

     ● Colletion

     ● Set

     ● List

     ● Map / MapEntry


    제네릭 타입의 매핑은 코틀린 / 자바 제네릭 구문의 차이 때문에 복잡한 변환이 필요하다.

     ● 자바의 extends 와일드카드는 코틀린 공변 프로젝션으로 변환된다.

     ○ 예를들어 TreeNode<? extends Person>은 TreeNode<out Person>으로 바뀐다.

     ● 자바의 super 와일드카드는 코틀린 반공변 프로젝션으로 변환된다.

     ○ 예를들어 TreeNode<? super Person>은 TreeNode<int Person>으로 바뀐다.

     ● 자바의 로우 타입(raw type)는 코틀린 스타 프로젝션으로 변환된다.

     ○ 예를들어 TreeNode는 TreeNode<*>으로 바뀐다.


    7) 단일 추상 메서드 인터페이스

    추상 메서드가 하나뿐인 자바 인터페이스가 있다면(=SAM, 단일 추상 메서드)

    이 인터페이스는 기본적으로 코틀린 함수 타입처럼 작동한다.

    코틀린은 자바 SAM 인터페이스가 필요한 위치에

    람다를 넘길 수 있게 지원하는데, 이를 SMA 변환이라고 한다.

    더 추가적인 내용들이 있는데 단일 추상 메서드 인터페이스는 코드를 이해하지 못해서 여기까지만 작성하겠다.


    다음글

    12-2.  자바 상호 운용성 : 코틀린 코드를 자바에서 사용하기

    반응형

    댓글

Designed by Tistory.