본문 바로가기

카테고리 없음

[Kotlin] 객체지향 프로그래밍 - Companion Object

개요

오늘은 코틀린의 Companion Object에 대해서 알아보도록 하겠습니다.

Java와 같은 언어로 개발된 프로그램은 운영체제로부터 메모리(RAM)의 공간을 할당받아야 합니다.

이러한 메모리의 할당 및 관리는 운영체제의 역할이자 중요한 작업 중 하나입니다.

 

하지만, 이러한 메모리는 무제한으로 쓸 수 없고 한정된 공간을 가지고 있습니다.

 

그러므로 프로그램을 개발할 때는 메모리를 최대한 효율적으로 사용하도록 프로그램을 설계해야 합니다.

만약 그렇지 않고 프로그램을 잘못 만들게 되면, 

프로그램의 성능 저하와 때에 따라 프로그램이 비정상 종료되는 문제를 마주할 수 있습니다.

 


자바 메모리 구조

코틀린의 Companion Object를 살펴보기 전, 잠시 자바의 메모리 구조를 살펴보도록 하겠습니다.

코틀린의 부모 격에 해당할 수 있는 프로그래밍 언어 Java는 아래와 같이 메모리 영역이 구성되어 있습니다.

 

그림 1. 자바 메모리 구조

 

이 메모리 영역은 JVM이 컴파일된 클래스(.class) 파일을 읽어 들이면서 관리하게 되고,

각각의 영역에는 프로그램에서 작성한 코드에 따라 서로 다른 영역에 분할 저장 및 관리 됩니다.


예컨대, 아래와 같은 자바(Java) 함수가 있다고 가정하겠습니다.

 

public int addNumber(int x, int y){
    int result = x + y;
    return result;
}

 

위 메서드 addNumber에는 변수 result가 있고 파라미터 x와 y를 더해서 결괏값을 반환합니다.

이처럼 메서드 addNumber와 같이 내부에서 선언된 변수를 지역 변수라(Local Variables)고 하며

지역 변수는 메소드 호출 시마다 해당 메소드에 대한 정보가 스택 프레임(stack frame)에 저장되어 관리됩니다.

 

이와 같이 개발자가 작성한 자바 코드는 컴파일 과정을 거쳐 JVM이 읽어 들인 뒤,

코드에 명시된 모든 데이터를 위와 같은 메모리 영역으로 분류되어 관리를 받게 됩니다.

 

마찬가지로 코틀린도 컴파일 과정을 거치면 궁극적으로 JVM에 의해 메모리 관리를 받게 됩니다.

그렇기에 코틀린으로 만든 프로그램도 JVM에 의해  <그림 1>처럼 크게 세 가지 영역에서 관리받게 됩니다.

 

따라서, 지금까지 코틀린으로 작성했던 수많은 코드는 결과적으로 지역 변수라면 스택(Stack) 영역에서,

객체였다면 힙(Heap) 영역에서  그리고 함수 즉 메소드 였다면 메소드(Method) 영역에서 관리받습니다.

 

한 가지 상황을 예시로 들어보겠습니다.

 

만약 너무나도 잘 만든 슈퍼 클래스 A가 하나 있는데

이 클래스에 있는 어떤 기능이 너무 좋아서 다른 클래스에서 자주 호출하고 사용한다고 가정해 보겠습니다.


이때 개발자는 직관적으로 해당 클래스의 인스턴스를 생성해서 

클래스 A의 기능이 필요한 다른 클래스나 메서드에서 사용하게 할 수 있습니다.

그런데, 이 클래스 A가 너무나 훌륭해서 수없이 많은 곳에서 사용해야 한다고 하면 어떻게 될까요?

물론 아주 극단적인 예시이지만 모든 곳에서 클래스 A의 인스턴스를 생성하고 사용해야 한다면,

컴퓨터의 메모리는 당연히 많이 필요로 하게 되고 결국에는 메모리 부족으로 프로그램이 마비되는 상황이 발생할 수 있습니다.

만약 이 기능이 사칙 연산과 같이 단순한 작업이었다면, 이 문제는 더욱 아쉬운 상황이 됩니다.

또한, JVM은 가비지 컬렉터(Garbage Collector)라는 것을 가지고 있는데

이는 JVM이 프로그램을 관리할 때 사용하지 않는 코드나 불필요한 자원들을 판별해서 정리하는 역할을 하고 있습니다.

JVM이 사용하지 않는 자원에 대해 정리하는 작업을 가비지 컬렉션(Garbage Collection)이라고 하는데, 

이 작업은 자바의 메모리 영역 중에서 힙(Heap) 영역에서 일어나게 됩니다.

그리고 이 작업은 프로그래머가 예측하기 매우 어려운 시점에 동작하게 됩니다.

즉, 자주 사용하는 어떤 기능을 클래스의 인스턴스 생성을 통해서만 종속적으로 사용할 수 있도록 한다면

첫째 자주 사용할수록 메모리의 사용량이 늘어나 프로그램의 성능 저하를 유발하고,

둘째 가비지 컬렉터나 운영체제에 의해 비정상적으로 종료 또는 정리될 수 있어 잠재적인 위험을 유발할 수 있습니다.

특히 클래스 A에서 만든 기능을 인스턴스의 생성을 통해서만 사용할 수 있다면,

이는 가비지 컬렉션(Garbage Collection)에 의해 정리될 때, 사용할 수 없는 기능이 되어버리고 맙니다.

 

이때 활용해볼 수 있는 것이 바로 정적(static) 메서드, 또는 정적(static) 변수입니다.

 

정적 메서드나 정적 변수는 static 키워드를 통해서 설정할 수 있습니다.

이는 클래스 내부에 있는 메서드를 인스턴스의 생성에 종속시키는 것이 아니라,

각각의 변수 또는 메서드 자체를 `메서드`영역(그림 1.)에서 변수나 메서드가 관리되도록 하는 것입니다.

 

자바에서는 static이라는 키워드를 통해 프로퍼티나 메서드를 정적 변수, 정적 메서드로 만들 수 있습니다.

그리고 이 메서드 영역은 JVM의 가비지 컬렉터(Garbage Collector)에 의해 

가비지 컬렉션(Garbage Collection)이 일어나지 않는 영역입니다.

따라서, 이 영역에 어떤 변수나 메서드를 올려두게 되면 JVM의 가비지 컬렉션으로부터 안전해질 수 있습니다.

 

그렇기에, 앞선 클래스 A의 상황 일 때 해당 기능 또는 변수에 static 키워드를 붙여주면

인스턴스의 생성 없이 사용할 수 있어서 메모리 낭비를 줄일 수도 있습니다.

하지만 근본적으로 이 메서드 영역도 또한 메모리가 넉넉한 것은 아니기 때문에

너무 정적(static) 변수나 정적(static) 메서드를 남용하게 되면 메모리의 누수라는 문제점을 초래할 수도 있습니다.


코틀린의 Companion Object

코틀린에서도 Java의 static 키워드처럼 메서드나 변수를 인스턴스의 생성 없이도

다른 클래스에서 자유롭게 참조 또는 사용할 수 있는 기능을 지원합니다.

이것이 바로 Companion Object입니다.

 

코틀린의 Companion Object는 아래와 같이 사용할 수 있습니다.

class MyObject {
    companion object {
        val commonValue1 = 123

        fun sayHello(){
            println("Hello Kotlin!");
        }
    }
    val otherValue1 = 456;
}

fun main() {

    val instance = MyObject(); // 인스턴스 생성

    println(MyObject().otherValue1); // 인스턴스를 생성한 것, 따라서 프로퍼티에 접근 가능!
    println(MyObject.commonValue1); // 인스턴스를 생성한 것이 아님!
    // println(MyObject.otherValue1); // 인스턴스를 생성한 것이 아니므로 프로퍼티 접근 불가...

    MyObject.sayHello(); // 인스턴스를 생성하지 않았음에도 메서드 사용 가능!
}

 


참고 자료

https://coding-factory.tistory.com/830

 

[Java] 메모리 구조 메소드(Method), 스택(Stack), 힙(Heap) 영역에 대하여

자바뿐만이 아니라 모든 프로그램이 구동되기 위해서는 프로그램 구동시에 운영체제로부터 메모리(RAM) 공간을 할당받아야 합니다. 이러한 메모리는 무제한이 아니라 한정되어 있습니다. 그렇

coding-factory.tistory.com