BE/Kotlin

[Kotlin] 코틀린 문법 5 (lateinit, lazy, companion object)

baek-dev 2025. 2. 19. 19:35

Kotlin에서는 지연 초기화(Lazy Initialization)와 관련하여 lateinitlazy를 제공하며, companion object를 통해 Java의 static 개념을 대체할 수 있음.

 

이를 Java와 비교하면서 차이점을 정리하고, lateinit vs lazy, companion object vs object 비교도 함께 제공함.

 

🛠️ 1. lateinit (자바의 일반적인 null 체크와 비교)

lateinit 키워드는 var로 선언된 비-Nullable 객체에 대해 나중에 초기화할 수 있도록 허용함.

 

✅ Java 코드 (일반적인 null 체크)

class User {
    private String name;

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

    public void printName() {
        if (name != null) {
            System.out.println("이름: " + name);
        } else {
            System.out.println("이름이 초기화되지 않음!");
        }
    }
}

User user = new User();
user.printName(); // "이름이 초기화되지 않음!"
user.setName("홍길동");
user.printName(); // "이름: 홍길동"

🔹 특징:

Java에서는 lateinit이 없기 때문에 null 체크를 직접 수행해야 함.

Kotlin의 lateinit을 활용하면 null 체크 없이 지연 초기화를 간편하게 구현 가능.


✅ Kotlin 코드 (lateinit)

class User {
    lateinit var name: String

    fun initUser() {
        name = "홍길동"
    }

    fun printName() {
        if (::name.isInitialized) {
            println("이름: $name")
        } else {
            println("이름이 초기화되지 않음!")
        }
    }
}

val user = User()
user.printName() // "이름이 초기화되지 않음!"
user.initUser()
user.printName() // "이름: 홍길동"

🔹 특징:

 lateinit var(mutable) 변수에만 사용 가능.

 Primitive 타입(Int, Double 등)에는 사용 불가능. (String, List 등 참조 타입만 가능)

 ::변수명.isInitialized로 초기화 여부 확인 가능.

 

🛠️ 2. lazy (자바의 싱글톤 및 getter 연산과 비교)

lazy 키워드는 val(불변) 변수에만 사용 가능하며, 해당 변수가 처음 사용될 때 초기화가 수행됨.

 

✅ Java 코드 (싱글톤 및 getter 활용)

class User {
    private String name;

    public String getName() {
        if (name == null) {
            System.out.println("초기화 수행됨!");
            name = "이순신";
        }
        return name;
    }
}

User user = new User();
System.out.println(user.getName()); // "초기화 수행됨!" 이후 "이순신" 출력
System.out.println(user.getName()); // "이순신" (이미 초기화됨)

🔹 특징:

Java에서는 lazy가 없으므로 getter에서 null 체크를 통해 동적 초기화해야 함.

Kotlin의 lazy를 활용하면 더 간결하고 효율적인 코드 작성 가능.


✅ Kotlin 코드 (lazy)

class User {
    val name: String by lazy {
        println("초기화 수행됨!")
        "이순신"
    }
}

val user = User()
println(user.name) // "초기화 수행됨!" 이후 "이순신" 출력
println(user.name) // "이순신" (이미 초기화되었으므로 다시 초기화되지 않음)

🔹 특징:

 lazy val(불변) 변수에만 사용 가능.

 최초 접근 시점에서 한 번만 초기화되며 이후에는 캐싱됨.

 자주 사용되지 않는 무거운 객체 초기화 시 유용함.

 

🔥 lateinit vs lazy 비교표

기능 lateinit lazy
사용 가능 변수 타입 var (mutable) val (immutable)
초기화 시점 직접 할당 시점 최초 접근 시점
null 허용 여부 ❌ (String? 불가능) ✅ 가능 (lazy { null } 가능)
재할당 여부 ✅ 가능 (나중에 값 변경 가능) ❌ 불가능 (최초 값 유지)
Primitive 타입 사용 가능 여부 ❌ 불가능 ✅ 가능
초기화 여부 확인 가능 여부 ✅ (::변수명.isInitialized) ❌ (불가능)
예외 발생 가능성 초기화 전에 접근하면 UninitializedPropertyAccessException 예외 없음 (사용 시 자동 초기화)
일반적인 사용 사례 DI, 객체 프로퍼티 초기화 무거운 객체의 지연 초기화
멀티스레드 안정성 보장되지 않음 (직접 관리) 기본적으로 동기화됨 (lazy(LazyThreadSafetyMode.SYNCHRONIZED))

 

🛠️ 3. companion object (static과 비교)

Kotlin에서는 static 키워드가 없기 때문에, companion object를 사용하여 정적 멤버를 정의함.

 

✅ Java 코드 (static)

class User {
    static final String DEFAULT_NAME = "Guest";

    static User create() {
        return new User();
    }
}

System.out.println(User.DEFAULT_NAME); // "Guest"
User user = User.create(); // 정적 메서드 호출

🔹 특징:

Java에서는 static을 사용하여 정적 멤버를 선언함.

Kotlin의 companion object와 같은 역할을 함.

 

✅ Kotlin 코드 (companion object)

class User {
    companion object {
        val DEFAULT_NAME = "Guest"

        fun create(): User {
            return User()
        }
    }
}

println(User.DEFAULT_NAME) // "Guest"
val user = User.create() // 정적 메서드 호출

🔹 특징:

 companion object 내부의 변수/메서드는 클래스명으로 직접 접근 가능.

 Java의 static 멤버와 동일한 역할을 수행함.

 

🛠️ 4. companion object vs object

Kotlin에서 object싱글톤 객체를 만들 때 사용하며, companion object클래스 내부의 정적 멤버 역할을 수행함.

 

✅ companion object (클래스 내부에서 static 역할)

class User {
    companion object {
        fun create() = User()
    }
}

val user = User.create() // 정적 메서드 호출

→ companion object는 static 역할을 수행하는 정적 메서드/변수를 포함하는 데 사용됨.

 

✅ object (싱글톤)

object Singleton {
    val name = "Singleton Object"
}

println(Singleton.name) // "Singleton Object"

→ object는 프로그램 내에서 유일한 인스턴스를 가지는 싱글톤 객체를 만들 때 사용됨.

 

🔥 companion object vs object 비교표

기능 companion object object
접근 방식 클래스 내부에서 정적 멤버 제공 싱글톤 객체 생성
사용 용도 Java의 static 대체 글로벌 싱글톤
생성 방식 특정 클래스 내부에 정의 별도의 객체로 선언
인스턴스 생성 필요 없음 (User.create())
가능 (클래스 인스턴스 생성 가능)
필요 없음 (Singleton.name)
불가능 (이미 하나의 객체)
접근 방식 클래스명.메서드()
User.create()
객체명.메서드()
Singleton.name
클래스 내부 사용 ✅ 클래스 내부에서 정적 멤버처럼 사용 ❌ 클래스 외부에서 바로 사용
생성자 사용 ❌ 생성자 없음 ❌ 생성자 없음
멀티톤 패턴 ✅ 클래스마다 다르게 생성 가능 ❌ (싱글톤만 가능)

 

Object - 싱글톤 객체

특징:
 클래스가 아니라 곧바로 하나의 객체
✅ 프로그램 실행 시 자동 생성되며, 전역적으로 사용 가능
✅ 생성자가 없음 (object는 인스턴스를 만들 수 없음)

 자바에서는 static 멤버를 이용해 싱글톤을 구현해야 하지만, 코틀린에서는 object 키워드로 쉽게 가능
 클래스가 아니라 곧바로 객체(Singleton) 자체를 생성하기 때문에, new 키워드 없이 사용 가능

 

companion object - 클래스 내부 정적 멤버

특징:
 클래스 내부에 하나만 존재할 수 있음
 static처럼 정적 멤버를 선언하는 용도
✅ 클래스의 인스턴스를 생성하지 않아도 접근 가능 (클래스명.함수명())
✅ 하지만 object와 다르게, 클래스와 연결된 멤버처럼 동작

 자바에서 static 키워드를 사용했던 방식 대신, 코틀린에서는 companion object를 활용하여 정적 멤버 선언 가능
 클래스 내부에서 정적 메서드처럼 사용할 때 companion object가 적합

 

🎯 결론

lateinit은 var 변수에 대해 초기화를 나중에 할 수 있도록 허용.

lazy는 val 변수에 대해 최초 접근 시점에만 초기화 수행.

companion object는 Java의 static을 대체하며, 정적 멤버를 포함.

object는 싱글톤 패턴을 구현할 때 사용.

 

 

 

 

출처 : ChatGPT