Item 3. Singleton

private ์ƒ์„ฑ์ž๋‚˜ ์—ด๊ฑฐ ํƒ€์ž…์œผ๋กœ ์‹ฑ๊ธ€ํ„ด์ž„์„ ๋ณด์ฆํ•˜๋ผ

์‹ฑ๊ธ€ํ„ด์ด๋ž€ ์ธ์Šคํ„ด์Šค๋ฅผ ์˜ค์ง ํ•˜๋‚˜๋งŒ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋Š” ํด๋ž˜์Šค๋กœ, ์‚ฌ์šฉํ•œ ์˜ˆ๋กœ๋Š” ํ•จ์ˆ˜์™€ ๊ฐ™์€ ๋ฌด์ƒํƒœ ๊ฐ์ฒด๋‚˜ ์„ค๊ณ„์ƒ ์œ ์ผํ•ด์•ผ ํ•˜๋Š” ์‹œ์Šคํ…œ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์žˆ๋‹ค.

์‹ฑ๊ธ€ํ„ด์„ ๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ•

์‹ฑ๊ธ€ํ„ด์„ ๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ•์€ ๋ณดํ†ต ๋‘ ๊ฐ€์ง€ ๋ฐฉ์‹์ด ์žˆ๋‹ค.

1. public static final ํ•„๋“œ ๋ฐฉ์‹

์œ„ ์ฝ”๋“œ์—์„œ ๋ณผ ์ˆ˜ ์žˆ๋“ฏ์ด static final ํ•„๋“œ์— ์ธ์Šคํ„ด์Šค๋ฅผ ๋งŒ๋“ค์–ด public์œผ๋กœ ์„ ์–ธํ•˜๊ณ , ์ƒ์„ฑ์ž๋ฅผ private์œผ๋กœ ์„ ์–ธํ•˜์—ฌ ์™ธ๋ถ€์—์„œ ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•˜์ง€ ๋ชปํ•˜๋„๋ก ํ•œ๋‹ค. ์ด ๋ฐฉ์‹์€ ํ•ด๋‹น ํด๋ž˜์Šค๊ฐ€ ์‹ฑ๊ธ€ํ„ด์ž„์ด API์— ๋ช…๋ฐฑํžˆ ๋“œ๋Ÿฌ๋‚˜๋Š” ์žฅ์ ์ด ์žˆ๋‹ค.

class Ogu {
    public static final Ogu INSTANCE = new Ogu();

    private Ogu() {
    }

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

2. ์ •์  ํŒฉํ„ฐ๋ฆฌ ๋ฉ”์„œ๋“œ ๋ฐฉ์‹

์•„๋ž˜ ์ฝ”๋“œ์—์„œ ๋ณผ ์ˆ˜ ์žˆ๋“ฏ์ด static ํŒฉํ„ฐ๋ฆฌ ๋ฉ”์„œ๋“œ๋ฅผ public์œผ๋กœ ์„ ์–ธํ•˜๊ณ , ์ƒ์„ฑ์ž๋ฅผ private์œผ๋กœ ์„ ์–ธํ•˜์—ฌ ์™ธ๋ถ€์—์„œ ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•˜์ง€ ๋ชปํ•˜๋„๋ก ํ•œ๋‹ค.

class Ogu {
    private static final Ogu INSTANCE = new Ogu();

    private Ogu() {
    }

    public static Ogu getInstance() {
        return INSTANCE;
    }

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

์ด ๋ฐฉ์‹์€ 1๋ฒˆ์˜ ๋ฐฉ๋ฒ•๊ณผ ๋‹ค๋ฅด๊ฒŒ ์•„๋ž˜์™€ ๊ฐ™์€ ์žฅ์ ์ด ์žˆ๋‹ค. ๋งŒ์•ฝ ์•„๋ž˜ ์žฅ์ ๋“ค์ด ํ•„์š”ํ•˜์ง€ ์•Š๋‹ค๋ฉด 1๋ฒˆ์˜ ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.

  • API๋ฅผ ๋ฐ”๊พธ์ง€ ์•Š๊ณ ๋„ ์‹ฑ๊ธ€ํ„ด์ด ์•„๋‹ˆ๊ฒŒ ๋ณ€๊ฒฝ ๊ฐ€๋Šฅ

class Ogu {

    private static boolean useSingleton = true; // ์‹ฑ๊ธ€ํ„ด ์‚ฌ์šฉ ์—ฌ๋ถ€ ํ”Œ๋ž˜๊ทธ ๊ฐ’

    private Ogu() {
    }

    public static Ogu getInstance() {
        if (useSingleton) {
            return SingletonHolder.INSTANCE;
        } else {
            return new Ogu();
        }
    }

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

    // ์‹ฑ๊ธ€ํ†ค ์ธ์Šคํ„ด์Šค๋ฅผ ๋ณด์œ ํ•  ๋‚ด๋ถ€ ํด๋ž˜์Šค
    private static class SingletonHolder {
        private static final Ogu INSTANCE = new Ogu();
    }
}

class Main {
    public static void main(String[] args) {
        Ogu singletonInstance = Ogu.getInstance();
        singletonInstance.something();

        // ์‹ฑ๊ธ€ํ„ด ์‚ฌ์šฉ ์—ฌ๋ถ€ ํ”Œ๋ž˜๊ทธ ๊ฐ’์„ false๋กœ ๋ณ€๊ฒฝ
        Ogu.useSingleton = false;
        Ogu nonSingletonInstance = Ogu.getInstance(); // ์ƒˆ๋กœ์šด ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ
        nonSingletonInstance.something();
    }
}
  • ์ •์  ํŒฉํ„ฐ๋ฆฌ๋ฅผ ์ œ๋„ค๋ฆญ ์‹ฑ๊ธ€ํ„ด ํŒฉํ„ฐ๋ฆฌ๋กœ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Œ

class SingletonFactory<T> {
    private final Supplier<T> supplier;
    private T instance;

    private SingletonFactory(Supplier<T> supplier) {
        this.supplier = supplier;
    }

    public static <T> SingletonFactory<T> create(Supplier<T> supplier) {
        return new SingletonFactory<>(supplier);
    }

    public T getInstance() {
        if (instance == null) {
            instance = supplier.get();
        }
        return instance;
    }
}

class Main {
    public static void main(String[] args) {
        // Supplier๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ String ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•˜๋Š” SingletonFactory๋ฅผ ์ƒ์„ฑ
        SingletonFactory<String> stringFactory = SingletonFactory.create(() -> "Hello, Singleton!");

        // ์‹ฑ๊ธ€ํ„ด ์ธ์Šคํ„ด์Šค๋ฅผ ์–ป๊ณ  ์‚ฌ์šฉ
        String singletonString = stringFactory.getInstance();
        System.out.println(singletonString); // Hello, Singleton!

        // Supplier๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ Integer ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•˜๋Š” SingletonFactory๋ฅผ ์ƒ์„ฑ
        SingletonFactory<Integer> integerFactory = SingletonFactory.create(() -> 42);

        // ์‹ฑ๊ธ€ํ„ด ์ธ์Šคํ„ด์Šค๋ฅผ ์–ป๊ณ  ์‚ฌ์šฉ
        Integer singletonInteger = integerFactory.getInstance();
        System.out.println(singletonInteger); // 42
    }
}
  • ์ •์  ํŒฉํ„ฐ๋ฆฌ์˜ ๋ฉ”์„œ๋“œ ์ฐธ์กฐ๋ฅผ ๊ณต๊ธ‰์ž(supplier)๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Œ

class Ogu {
    private static final Ogu INSTANCE = new Ogu();

    private Ogu() {
    }

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

    public static Ogu getInstance() {
        return INSTANCE;
    }
}

class Main {
    public static void main(String[] args) {
        // ์ •์  ํŒฉํ† ๋ฆฌ์—์„œ ๋ฉ”์†Œ๋“œ ์ฐธ์กฐ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ณต๊ธ‰์ž๋กœ ์‚ฌ์šฉ
        Supplier<Ogu> oguSupplier = Ogu::getInstance;

        // ๊ณต๊ธ‰์ž๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ธ์Šคํ„ด์Šค๋ฅผ ๊ฐ€์ ธ์˜ค๊ณ  ์‚ฌ์šฉ
        Ogu instance1 = oguSupplier.get();
        instance1.something();

        Ogu instance2 = oguSupplier.get();
        instance2.something();
    }
}

3. ์›์†Œ๊ฐ€ ํ•˜๋‚˜์ธ ์—ด๊ฑฐ ํƒ€์ž… ๋ฐฉ์‹

์—ด๊ฑฐ ํƒ€์ž…์€ ์‹ฑ๊ธ€ํ„ด์„ ๋งŒ๋“œ๋Š” ๊ฐ€์žฅ ์ข‹์€ ๋ฐฉ๋ฒ•์œผ๋กœ, ์ง๋ ฌํ™” ๋ฌธ์ œ๋„ ์ž๋™์œผ๋กœ ์ฒ˜๋ฆฌ๋˜๊ณ  ๋ฆฌํ”Œ๋ ‰์…˜ ๊ณต๊ฒฉ์—๋„ ์•ˆ์ „ํ•˜๊ฒŒ ๋ณด์žฅ๋œ๋‹ค.

enum Ogu {
    INSTANCE;

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

๋‹จ, ๋งŒ๋“ค๋ ค๋Š” ์‹ฑ๊ธ€ํ„ด์ด Enum ์™ธ์˜ ํด๋ž˜์Šค๋ฅผ ์ƒ์†ํ•ด์•ผ ํ•œ๋‹ค๋ฉด ์ด ๋ฐฉ๋ฒ•์€ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋‹ค. ๋•Œ๋ฌธ์— ์ƒ์†์„ ํ•ด์•ผํ•˜๋Š” ์ƒํ™ฉ์ด ์•„๋‹ˆ๋ผ๋ฉด ์ด ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๊ฐ€์žฅ ์ข‹๋‹ค.

์‹ฑ๊ธ€ํ„ด ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค ๋•Œ ์ฃผ์˜ํ•  ์ 

์œ„์—์„œ๋„ ์–ธ๊ธ‰ํ–ˆ๋“ฏ์ด 1, 2๋ฒˆ ํŒจํ„ด์œผ๋กœ ๋งŒ๋“ค๊ฒŒ ๋˜๋ฉด ๊ธฐ๋ณธ์ ์œผ๋กœ ์‹ฑ๊ธ€ํ„ด ์ธ์Šคํ„ด์Šค๋ฅผ ๋ณด์žฅํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ์•„๋ž˜ ๋‘ ๊ฐ€์ง€ ์ƒํ™ฉ์—์„œ๋Š” ์‹ฑ๊ธ€ํ„ด์ด ๊นจ์งˆ ์ˆ˜ ์žˆ๋‹ค. ๋•Œ๋ฌธ์— ์™„๋ฒฝํ•˜๊ฒŒ ์‹ฑ๊ธ€ํ„ด์„ ๋ณด์žฅํ•˜๋ ค๋ฉด ์ง๋ ฌํ™”(Serialization)์™€ ๋ฆฌํ”Œ๋ ‰์…˜(Reflection)์„ ๊ณ ๋ คํ•ด์•ผ ํ•œ๋‹ค.

๋ฆฌํ”Œ๋ ‰์…˜์„ ํ†ตํ•œ ์˜ˆ์™ธ

๋ฆฌํ”Œ๋ ‰์…˜์„ ์ด์šฉํ•˜๋ฉด private ์ƒ์„ฑ์ž๋ฅผ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์ด๋Ÿฌํ•œ ๊ณต๊ฒฉ์„ ๋ฐฉ์–ดํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ์ƒ์„ฑ์ž์—์„œ ๋‘ ๋ฒˆ์งธ ๊ฐ์ฒด๊ฐ€ ์ƒ์„ฑ๋˜๋ ค๊ณ  ํ•  ๋•Œ ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ ์‹œํ‚ค๋Š” ๋ฐฉ์–ด ์ฝ”๋“œ๋ฅผ ๋„ฃ์–ด์•ผ ํ•œ๋‹ค.

class ReflectionExample {
    public static void main(String[] args) {
        try {
            // Ogu ํด๋ž˜์Šค์˜ ๋น„๊ณต๊ฐœ ์ƒ์„ฑ์ž ๊ฐ€์ ธ์˜ค๊ธฐ
            Constructor<Ogu> constructor = Ogu.class.getDeclaredConstructor();

            // ๋น„๊ณต๊ฐœ ์ƒ์„ฑ์ž์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋„๋ก ํ—ˆ์šฉ
            constructor.setAccessible(true);

            // ๋ฆฌํ”Œ๋ ‰์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ Ogu์˜ ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ
            Ogu oguInstance = constructor.newInstance();

            // ์ƒ์„ฑํ•œ ์ธ์Šคํ„ด์Šค๋กœ Ogu์˜ ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ
            oguInstance.something(); // something

            // ๋‹ค๋ฅธ ์ธ์Šคํ„ด์Šค์ธ์ง€ ํ™•์ธ
            System.out.println("Original INSTANCE: " + Ogu.INSTANCE); // Ogu@279f2327
            System.out.println("Reflectively created instance: " + oguInstance); // Ogu@2ff4acd0
            System.out.println("Are both instances same? " + (Ogu.INSTANCE == oguInstance)); // false
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

class Ogu {
    public static final Ogu INSTANCE = new Ogu();

    private Ogu() {
//        if (INSTANCE != null) {
//            throw new RuntimeException("Already initialized");
//        }
    }

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

์ƒ์„ฑ์ž์˜ ๋ฐฉ์–ด ์ฝ”๋“œ ์ฃผ์„์„ ํ•ด์ œํ•˜๋ฉด ์•„๋ž˜์˜ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด์„œ ๋‘ ๋ฒˆ์งธ ๊ฐ์ฒด๊ฐ€ ์ƒ์„ฑ๋˜์ง€ ์•Š๋Š”๋‹ค.

java.lang.reflect.InvocationTargetException
	at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77)
	at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499)
	at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:480)
	at ReflectionExample.main(test.java:14)
Caused by: java.lang.RuntimeException: Already initialized
	at Ogu.<init>(test.java:34)
	... 6 more

์ง๋ ฌํ™”๋ฅผ ํ†ตํ•œ ์˜ˆ์™ธ

1, 2๋ฒˆ ๋ฐฉ์‹์œผ๋กœ ๋งŒ๋“  ์‹ฑ๊ธ€ํ„ด ํด๋ž˜์Šค๋Š” ์ง๋ ฌํ™”ํ•œ ๋‹ค์Œ ์—ญ์ง๋ ฌํ™”ํ•˜๋ฉด ํ•ด๋‹น ๊ฐœ์ฒด์˜ ์ƒˆ ์ธ์Šคํ„ด์Šค๊ฐ€ ์ƒ์„ฑ๋˜๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ๋‹ค. ๋–„๋ฌธ์— ์‹ฑ๊ธ€ํ„ด ํด๋ž˜์Šค๋ฅผ ์ง๋ ฌํ™”ํ•˜๊ณ  ์—ญ์ง๋ ฌํ™”ํ•  ๋•Œ๋งˆ๋‹ค ์ƒˆ๋กœ์šด ์ธ์Šคํ„ด์Šค๊ฐ€ ์ƒ์„ฑ๋˜๋Š” ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” readResolve ๋ฉ”์„œ๋“œ๋ฅผ ์ œ๊ณตํ•ด์•ผ ํ•œ๋‹ค. readResolve ๋ฉ”์†Œ๋“œ๋Š” Java์˜ ์—ญ์ง๋ ฌํ™” ํ”„๋กœ์„ธ์Šค ์ค‘์— ์‚ฌ์šฉ๋˜๋Š” ํŠน์ˆ˜ ๋ฉ”์†Œ๋“œ๋กœ, ๊ตฌํ˜„ํ•ด์•ผ ํ•˜๋Š” ์ธํ„ฐํŽ˜์ด์Šค ๋ฉ”์„œ๋“œ๊ฐ€ ์•„๋‹Œ ์—ญ์ง๋ ฌํ™” ๋™์ž‘์„ ํด๋ž˜์Šค์—์„œ ์„ ํƒ์ ์œผ๋กœ ์ •์˜ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฉ”์„œ๋“œ์ด๋‹ค.

class Singleton implements Serializable {

    public static final Singleton INSTANCE = new Singleton();

    private Singleton() {
    }

    public static Singleton getInstance() {
        return INSTANCE;
    }

    // ๊ธฐ์กด ์ธ์Šคํ„ด์Šค๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ์ •์˜ํ•˜์—ฌ, ์—ญ์ง๋ ฌํ™” ์ค‘์— ์ƒ๊ธด ์ƒˆ๋กœ์šด ์ธ์Šคํ„ด์Šค๋Š” GC์— ์˜ํ•ด ์ œ๊ฑฐ๋จ
    private Object readResolve() {
        return INSTANCE;
    }
}


class Main {
    public static void main(String[] args) {
        try {
            // ์‹ฑ๊ธ€ํ„ด ๊ฐ์ฒด ์ง๋ ฌํ™”
            ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("singleton.ser"));
            out.writeObject(Singleton.getInstance());
            out.close();

            // ์‹ฑ๊ธ€ํ„ด ๊ฐ์ฒด ์—ญ์ง๋ ฌํ™”
            ObjectInputStream in = new ObjectInputStream(new FileInputStream("singleton.ser"));
            Singleton deserializedSingleton = (Singleton) in.readObject();
            in.close();

            // ์‹ฑ๊ธ€ํ„ด ๊ฐ์ฒด์˜ ๋™์ผ์„ฑ ํ™•์ธ
            System.out.println(Singleton.getInstance()); // Singleton@27fa135a
            System.out.println(deserializedSingleton); // Singleton@27fa135a
            System.out.println(Singleton.getInstance() == deserializedSingleton); // true
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

๋งˆ์ฐฌ๊ฐ€์ง€๋กœ readResolve ๋ฉ”์„œ๋“œ ์ฃผ์„์„ ํ•ด์ œํ•˜๋ฉด ์ƒˆ๋กœ์šด ์ธ์Šคํ„ด์Šค๊ฐ€ ๋ฐ˜ํ™˜๋˜์–ด ์•„๋ž˜์™€ ๊ฐ™์€ ๊ฒฐ๊ณผ๊ฐ€ ์ถœ๋ ฅ๋œ๋‹ค.

Singleton@27fa135a
Singleton@6d5380c2
false

Last updated