Kotlin/Class and Interface

[Kotlin] internal 변경자 사용법 한 번에 정리하기

Dev.Cho 2024. 3. 25. 07:02

internal 변경자(Modifier)의 의의

internal 변경자는 코틀린에서 클래스 혹은 함수에 모듈 수준의 가시성을 설정하기 위해 사용하는 변경자이다. 모듈 수준의 가시성이란 모듈 내에서는 public처럼 어디에서나 접근 가능하지만, 모듈 외부에서 접근하지 못하는 변경자이다.
internal을 설정해야 하는 경우는 다양하다. 모듈 내부에서만 사용하는 함수에 internal을 설정해 외부에서 접근하지 못하도록 만드는 경우가 일반적이며, 외부에는 인터페이스만 공개하고 모듈 내부에서 인터페이스의 구현체(클래스)를 만들어 사용하는 경우에 유용하다.
 

internal 변경자 사용 예시

대표적으로 우리가 사용하는 젯브레인사에서 개발한 코루틴 라이브러리의 경우 Mutex 인터페이스의 구현체인 MutexImpl을 코루틴 라이브러리 내부에서만 사용할 수 있도록 internal로 설정하고 있으며, 외부에는 Mutex의 생성 함수만 공개해 외부에서 Mutex 인터페이스의 구현체를 직접 조작하지 못하도록 한다.
 

  • 외부에 공개되지 않는 Mutex 인터페이스의 구현체는 다음과 같다.
internal open class MutexImpl(locked: Boolean) : SemaphoreImpl(1, if (locked) 1 else 0), Mutex {
  ...
}

 

  • 외부에 공개되는 Mutex 생성 함수는 다음과 같다.
public fun Mutex(locked: Boolean = false): Mutex =
    MutexImpl(locked)

 

  • 이를 통해 외부에서는 내부의 구현을 모르게 하고 Mutex 인터페이스만 보고 그 Mutex 객체를 조작 할 수 있도록 만든다.
public interface Mutex {
    ...
    public val isLocked: Boolean

    ...
    public fun tryLock(owner: Any? = null): Boolean

    ...
    public suspend fun lock(owner: Any? = null)

    ...
    ...
    
    public fun unlock(owner: Any? = null)
}

 
외부에서는 Mutex 구현체의 존재를 알 필요 없이 사용법만 알면 되기에, 이 방법은 라이브러리를 사용하는 사람에게 간편함을 제공한다. 많은 라이브러리들이 이와 같은 방식을 취하고 있다.
 

internal 변경자의 동작 방식

internal 변경자가 어떻게 동작하는지 더욱 자세히 확인하기 위해 디렉토리 구조를 다음과 같이 만들어보자. 여기서 Main.kt 파일이 들어간 기본 모듈은 Printer 클래스가 있는 new-module에 의존성을 갖도록 만들었다.
 

그림1. 디렉토리 구조

 

클래스에 붙인 internal 변경자의 동작 방식

위의 구조에서 Printer 클래스를 다음과 같이 만들어보자.

internal class Printer {
  fun printFunction() {
    println("Function")
  }
  
  companion object {
    fun printStatic() {
      println("Static function")
    }
  }
}

 
그러면 기본 모듈에서 Printer 클래스로 접근을 시도했을 때 다음과 같은 컴파일 오류가 생기는 것을 볼 수 있다.

그림2. 컴파일 타임 오류
Cannot access 'Printer': it is internal in ''
Printer 클래스는 internal로 선언돼 접근하지 못한다.

 
클래스 수준의 변경자에 internal로 선언하면 이렇게 다른 모듈에서는 클래스에 접근조차 하지 못한다.
 
 

클래스 생성자에 붙인 internal의 동작 방식

이번에는 클래스 말고 클래스 생성자에 internal 변경자를 붙여보자.

class Printer internal constructor() {
  fun printFunction() {
    println("Function")
  }

  companion object {
    fun printStatic() {
      println("Static function")
    }
  }
}

 
그러면 Printer의 companion object에 선언된 정적 함수인 printStatic에는 접근 가능하지만, Printer의 생성자에는 접근할 수 없기 때문에 인스턴스를 만들지 못한다. 

그림3. 생정자에 붙인 internal의 동작 방식

 

함수에 붙인 internal 변경자의 동작 방식

이번에는 함수에 internal 변경자를 붙여 다음과 같이 만들어보자. 이 코드에서는 printFunction 함수에는 internal 변경자가 붙지 않았고, printStatic 함수에는 internal 변경자가 붙는다.

class Printer {
  fun printFunction() {
    println("Function")
  }

  companion object {
    internal fun printStatic() {
      println("Static function")
    }
  }
}

 
따라서 기본 모듈에서 printStatic 함수에는 접근하지 못하고, printFunction에는 접근이 가능하다.

그림4. 함수에 붙인 internal의 동작 방식

 

정리

  • 클래스에 internal 변경자가 붙으면, 외부 모듈에서 클래스는 물론 생성자에도 접근이 불가능하다.
  • 클래스의 생성자에 internal 변경자가 붙으면, 외부 모듈에서 클래스에는 접근이 가능하지만, 생성자에는 접근이 불가능하다.
  • 함수에 internal 변경자가 붙으면, 외부 모듈에서 internal 변경자가 붙은 함수에만 접근이 불가능하다.
반응형