πŸ“• Backend/Java

[Effective Java] Item 3. private μƒμ„±μžλ‚˜ enum νƒ€μž…μœΌλ‘œ μ‹±κΈ€ν†€μž„μ„ 보μž₯ν•˜μž.

Dongwoongkim 2024. 1. 24. 15:42

μ‹±κΈ€ν†€μ΄λž€?

μ‹±κΈ€ν†€μ΄λž€ μΈμŠ€ν„΄μŠ€λ₯Ό 단 1개만 생성할 수 μžˆλŠ” 클래슀λ₯Ό λ§ν•©λ‹ˆλ‹€. 

ex) λ¬΄μƒνƒœ 객체, 섀계 상 μœ μΌν•΄μ•Ό ν•˜λŠ” μ‹œμŠ€ν…œ μ»΄ν¬λ„ŒνŠΈ (service, repository)

 


싱글톀 μž₯점

  • ν•œ 번의 객체 μƒμ„±μœΌλ‘œ μž¬μ‚¬μš©μ΄ κ°€λŠ₯ν•˜κΈ° λ•Œλ¬Έμ—, λ©”λͺ¨λ¦¬ λ‚­λΉ„λ₯Ό 쀄일 수 μžˆμŠ΅λ‹ˆλ‹€.
  • μ‹±κΈ€ν†€μœΌλ‘œ μƒμ„±λœ κ°μ²΄λŠ” 전역성을 띄기 λ•Œλ¬Έμ— λ‹€λ₯Έ 객체와 κ³΅μœ κ°€ μš©μ΄ν•©λ‹ˆλ‹€.

싱글톀 단점

  • ν΄λΌμ΄μ–ΈνŠΈλ₯Ό ν…ŒμŠ€νŠΈν•˜κΈ° μ–΄λ ΅μŠ΅λ‹ˆλ‹€.
  • private μƒμ„±μžλ₯Ό κ°€μ§€κ³  있기 λ•Œλ¬Έμ— 상속이 λΆˆκ°€λŠ₯ν•©λ‹ˆλ‹€.
  • μ„œλ²„μ—μ„œ 클래슀 λ‘œλ”λ₯Ό μ–΄λ–»κ²Œ κ΅¬μ„±ν•˜κ³  μžˆλŠ”μ§€μ— 따라 λ˜λŠ” μ—¬λŸ¬ 개의 JVM에 λΆ„μ‚°λ˜μ–΄ μžˆλŠ” 경우 싱글톀 ν΄λž˜μŠ€μ—¬λ„ ν•˜λ‚˜ μ΄μƒμ˜ μΈμŠ€ν„΄μŠ€κ°€ λ§Œλ“€μ–΄μ§ˆ 수 μžˆμŠ΅λ‹ˆλ‹€.
  • μ „μ—­ μƒνƒœλ‘œ μ‚¬μš©ν•  수 있기 λ•Œλ¬Έμ— λ°”λžŒμ§ν•˜μ§€ λͺ»ν•©λ‹ˆλ‹€.
    • 사싀 아무 κ°μ²΄λ‚˜ 자유둭게 μ ‘κ·Όν•˜κ³  μˆ˜μ •ν•˜κ³  κ³΅μœ ν•  수 μžˆλŠ” μ „μ—­ μƒνƒœλ₯Ό κ°–λŠ” 것은 객체 μ§€ν–₯ ν”„λ‘œκ·Έλž¨μ΄μ—μ„œλŠ” ꢌμž₯λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

싱글톀 λ§Œλ“œλŠ” 방식 1 - public static final

public class Elvis {

    public static final Elvis INSTANCE = new Elvis();
    private Elvis(){}
}
class ElvisTest {

    @Test
    void singleToneTest() {
        Elvis elvis = Elvis.INSTANCE;
        Elvis elvis2 = Elvis.INSTANCE;

        assertEquals(elvis, elvis2);
    }
}
  • 첫 번째 방법은, 클래슀 내뢀에 public static 멀버λ₯Ό ν•˜λ‚˜ μž‘μ„±ν•΄ 두고 final둜 μ„€μ •ν•˜μ—¬ μ‹±κΈ€ν†€μœΌλ‘œ κ΄€λ¦¬ν•˜λŠ” λ°©λ²•μž…λ‹ˆλ‹€.
    • μ—¬κΈ°μ„œ private μƒμ„±μžλŠ” public static final ν•„λ“œμΈ Elvis.INSTANCEλ₯Ό μ΄ˆκΈ°ν™” ν•  λ•Œ λ”± ν•œ 번 ν˜ΈμΆœλ©λ‹ˆλ‹€.
    • publicμ΄λ‚˜ protected μƒμ„±μžκ°€ μ—†μœΌλ―€λ‘œ λ‹€λ₯Έ νŒ¨ν‚€μ§€μ—μ„œ 생성할 수 μ—†μŒμ„ 보μž₯ν•΄μ£Όλ©°, μ΄ˆκΈ°ν™” ν•  λ•Œ λ”± ν•œ 번 ν˜ΈμΆœν•¨μœΌλ‘œμ¨ μΈμŠ€ν„΄μŠ€κ°€ 전체 μ‹œμŠ€ν…œμ—μ„œ ν•˜λ‚˜ λΏμž„μ΄ 보μž₯λ©λ‹ˆλ‹€.
  • 이 방식은 ν•΄λ‹Ή ν΄λž˜μŠ€κ°€ μ‹±κΈ€ν†€μž„μ„ ν™•μ‹€νžˆ λ“œλŸ¬λ‚΄κ³ , κ°„κ²°ν•˜λ‹€λŠ” 것이 μž₯점

 

μ˜ˆμ™Έ μΌ€μ΄μŠ€ : μ•„λž˜ μ½”λ“œμ™€ 같이 λ¦¬ν”Œλ ‰μ…˜ API ( AccessibleObject.setAccessible)을 μ‚¬μš©ν•΄ private μƒμ„±μžλ₯Ό ν˜ΈμΆœν•˜λŠ” 방법
@Test
void reflectionBreakSingleton()
        throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
    Elvis elvis = Elvis.INSTANCE;
    Constructor<Elvis> constructor = (Constructor<Elvis>) elvis.getClass().getDeclaredConstructor();
    constructor.setAccessible(true);

    Elvis elvis2 = constructor.newInstance();
    assertNotSame(elvis, elvis2);
}
  • 이런 곡격을 막기 μœ„ν•΄, μƒμ„±μžλ₯Ό μˆ˜μ •ν•˜μ—¬ 두 번째 객체가 μƒμ„±λ˜λ € ν•  λ•Œ μ˜ˆμ™Έλ₯Ό 던짐으둜써 막을 수 μžˆκ² μŠ΅λ‹ˆλ‹€.
public class Presley {

    public static final Presley INSTANCE = new Presley();

    private Presley() {
        if (INSTANCE != null) {
            throw new RuntimeException("μƒμ„±μžλ₯Ό ν˜ΈμΆœν•  수 μ—†μŠ΅λ‹ˆλ‹€.");
        }
    }
}

 


싱글톀 λ§Œλ“œλŠ” 방식 2 - 정적 νŒ©ν† λ¦¬ λ©”μ†Œλ“œλ₯Ό public static λ©€λ²„λ‘œ μ œκ³΅ν•˜κΈ°

public class Greenday {

    private static final Greenday INSTANCE = new Greenday();

    private Greenday() {}

    public static Greenday getInstance() {
        return INSTANCE;
    }
}
class GreendayTest {

    @Test
    void singletonTest() {
        Greenday greenday1 = Greenday.getInstance();
        Greenday greenday2 = Greenday.getInstance();

        assertEquals(greenday1, greenday2);
    }
}
  • 두 번째 방법은 첫 번째 λ°©μ‹μ˜ 싱글톀 객체λ₯Ό κ°€μ Έμ˜€λŠ” 방식을 정적 νŒ©ν† λ¦¬ λ©”μ†Œλ“œ λ°©μ‹μœΌλ‘œ μ •μ˜ν•œ λ°©μ‹μž…λ‹ˆλ‹€.
  • 이 λ°©μ‹μ˜ μž₯점은 싱글톀 방식을 ν’€κ³  싢을 λ•Œ APIλ₯Ό λ°”κΎΈμ§€ μ•Šκ³ λ„ λ³€κ²½ν•  수 μžˆλ‹€λŠ” μ μž…λ‹ˆλ‹€.
    • μ•„λž˜ μ½”λ“œμ²˜λŸΌ λ‚΄λΆ€μ—μ„œ INSTANCEκ°€ μ•„λ‹Œ μƒˆ μΈμŠ€ν„΄μŠ€λ₯Ό 생성해주면 λ˜κ² μŠ΅λ‹ˆλ‹€.
public class Greenday {

    private static final Greenday INSTANCE = new Greenday();

    private Greenday() {}

    public static Greenday getInstance() {
        return new Greenday();
    }
}
class GreendayTest {

    @Test
    void singletonTest() {
        Greenday greenday1 = Greenday.getInstance();
        Greenday greenday2 = Greenday.getInstance();

        assertNotEquals(greenday1, greenday2);
    }
}

 

  • 두 번째 μž₯점은 μ›ν•œλ‹€λ©΄ 정적 νŒ©ν† λ¦¬λ₯Ό 싱글톀 νŒ©ν† λ¦¬λ‘œ λ§Œλ“€ 수 μžˆλ‹€λŠ” μ μž…λ‹ˆλ‹€. (Item 30 μ°Έκ³ )
  • 정적 νŒ©ν† λ¦¬μ˜ λ©”μ†Œλ“œ μ°Έμ‘°λ₯Ό κ³΅κΈ‰μžλ‘œ μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€. (Item 43, Itme 44 μ°Έκ³ )
    • Greenday::getInstanceλ₯Ό Supplier<Greenday>둜 μ‚¬μš©ν•΄μ„œ μ•„λž˜μ²˜λŸΌ μ‚¬μš©ν•  수 있음
Supplier<Greenday> greendaySupplier = Greenday::getInstance;
Greenday greenday = greendaySupplier.get();

 


μœ„ 두가지 λ°©μ‹μ˜ 문제점 

https://data-flair.training/blogs/serialization-and-deserialization-in-java/

 

μœ„ 두 κ°€μ§€ λ°©μ‹μ˜ λ¬Έμ œμ μ€ 각 클래슀λ₯Ό 직렬화(객체λ₯Ό μ €μž₯, 전솑할 수 μžˆλŠ” νŠΉμ • 포맷으둜 λ³€ν™˜ν•˜λŠ” κ³Όμ •)ν•œ ν›„ 역직렬화(νŠΉμ • 포맷 μƒνƒœμ˜ 데이터λ₯Ό λ‹€μ‹œ 객체둜 λ³€ν™˜)ν•  λ•Œ μƒˆλ‘œμš΄ μΈμŠ€ν„΄μŠ€λ₯Ό λ°˜ν™˜ν•œλ‹€λŠ” μ μž…λ‹ˆλ‹€.

  • μ—­μ§λ ¬ν™”λŠ” κΈ°λ³Έ μƒμ„±μžλ₯Ό ν˜ΈμΆœν•˜μ§€ μ•Šκ³  μΈμŠ€ν„΄μŠ€μ˜ κ°’을 λ³΅μ‚¬ν•΄μ„œ μƒˆλ‘œμš΄ μΈμŠ€ν„΄μŠ€λ₯Ό λ°˜ν™˜ν•©λ‹ˆλ‹€.

이λ₯Ό λ°©μ§€ν•˜κΈ° μœ„ν•΄ readResolve λ©”μ†Œλ“œλ₯Ό ν•˜λ‚˜ λ§Œλ“€μ–΄ 싱글톀 μΈμŠ€ν„΄μŠ€λ₯Ό λ°˜ν™˜ν•˜κ³ , λͺ¨λ“  ν•„λ“œμ— transient ν‚€μ›Œλ“œλ₯Ό λ„£μ–΄μ£ΌλŠ” 방식이 μžˆμŠ΅λ‹ˆλ‹€.

 

transient ν‚€μ›Œλ“œ : ν•„λ“œμ˜ μƒνƒœκ°’μ„ μ „μ†‘ν•˜μ§€ μ•ŠλŠ”λ‹€λŠ” ν‚€μ›Œλ“œ
public class Greenday {

//    transient private static final Greenday INSTANCE = new Greenday();
    private static final Greenday INSTANCE = new Greenday();
    
    private Greenday() {}

    public static Greenday getInstance() {
        return INSTANCE;
    }
   
    private Greenday readResolve() {
        // 'μ§„μ§œ' Greendayλ₯Ό λ¦¬ν„΄ν•˜κ³ , κ°€μ§œ GreendayλŠ” κ°€λΉ„μ§€ 컬렉터에 λ§‘κΈ΄λ‹€.
        return INSTANCE;
    }
}
  • readResolve λ©”μ†Œλ“œλŠ” 역직렬화 κ³Όμ •μ—μ„œ λ°˜λ“œμ‹œ ν˜ΈμΆœλ©λ‹ˆλ‹€.
  • μ΄λ ‡κ²Œ μ„€μ •ν•˜λ©΄ 역직렬화 κ³Όμ •μ—μ„œ μ‹±κΈ€ν†€μœΌλ‘œ λ§Œλ“€μ–΄λ‘” μ§„μ§œ μΈμŠ€ν„΄μŠ€λ₯Ό λ°˜ν™˜ν•˜λ„λ‘ μ„€μ •λ˜μ–΄, 값을 λ³΅μ‚¬ν•œ μƒˆλ‘œμš΄ μΈμŠ€ν„΄μŠ€λŠ” κ°€λΉ„μ§€ μ»¬λ ‰ν„°λ‘œ λ„˜μ–΄κ°€κ²Œ λ©λ‹ˆλ‹€. 
  • λ°©μ–΄μ μœΌλ‘œ transient ν‚€μ›Œλ“œλ₯Ό λ„£μ–΄ 직렬화 κ³Όμ •μ—μ„œ ν•„λ“œμ˜ μƒνƒœκ°’ 전솑을 막아 역직렬화 κ³Όμ •μ—μ„œ 볡사할 κ°’ 자체λ₯Ό μ•„μ˜ˆ 보내지 μ•Šλ„λ‘ μ„€μ •ν•  μˆ˜λ„ μžˆκ² μŠ΅λ‹ˆλ‹€.

싱글톀 λ§Œλ“œλŠ” 방식 3 - enum νƒ€μž… 

public enum ColdBrew {
    
    INSTANCE;
}
  • μ„Έ 번째 방법은 enumνƒ€μž…μœΌλ‘œ 싱글톀을 μ μš©ν•˜λŠ” λ°©λ²•μœΌλ‘œ ,public ν•„λ“œ 방식과 λΉ„μŠ·ν•˜μ§€λ§Œ, 훨씬 κ°„κ²°ν•˜κ²Œ μ‚¬μš©ν•  수 μžˆλŠ” λ°©μ‹μœΌλ‘œ, λ³΅μž‘ν•œ 직렬화 μƒν™©μ΄λ‚˜ λ¦¬ν”Œλ ‰μ…˜ κ³΅κ²©μ—μ„œλ„ 제 2의 μΈμŠ€ν„΄μŠ€κ°€ μƒκΈ°λŠ” 일을 μ™„λ²½νžˆ λ§‰μ•„μ€λ‹ˆλ‹€.
    • μ—΄κ±°ν˜• νƒ€μž…μœΌλ‘œ 싱글톀을 μ μš©ν•˜λŠ” 것이 쑰금 어색할 순 μžˆμ§€λ§Œ, λŒ€λΆ€λΆ„ μƒν™©μ—μ„œλŠ” μ›μ†Œκ°€ ν•˜λ‚˜λΏμΈ μ—΄κ±° 탕비이 싱글톀을 λ§Œλ“œλŠ” κ°€μž₯ 쒋은 λ°©λ²•μž…λ‹ˆλ‹€.
  • λ§Œλ“œλ €λŠ” 싱글톀이 enumμ™Έμ˜ 클래슀λ₯Ό 상속해야 ν•œλ‹€λ©΄ 이 방법은 μ‚¬μš©ν•  수 μ—†μŠ΅λ‹ˆλ‹€. (μΈν„°νŽ˜μ΄μŠ€λŠ” κ΅¬ν˜„ν•˜λ„λ‘ μ„ μ–Έν•  μˆ˜λŠ” 있음)
    • public enum ColdBrew extends ~ (X), public enum ColdBrew implements ~ (O)

<참고자료>

 

[μ΄νŽ™ν‹°λΈŒ μžλ°”] μ•„μ΄ν…œ 3. private μƒμ„±μžλ‚˜ μ—΄κ±° νƒ€μž…μœΌλ‘œ μ‹±κΈ€ν„΄μž„μ„ λ³΄μ¦ν•˜λΌ

μ‹±κΈ€ν„΄: μΈμŠ€ν„΄μŠ€λ₯Ό 였직 ν•˜λ‚˜λ§Œ 생성할 수 μžˆλŠ” 클래슀ex) ν•¨μˆ˜(λ¬΄μƒνƒœ 객체), 섀계상 μœ μΌν•΄μ•Ό ν•˜λŠ” μ‹œμŠ€ν…œ μ»΄ν¬λ„ŒνŠΈprivate μƒμ„±μžλ₯Ό κ°€μ§€κ³  있기 λ•Œλ¬Έμ— 상속할 수 μ—†λ‹€.private μƒμ„±μžλŠ” 였직 μ‹±

velog.io

 

 

[Java] 직렬화(Serialization)와 역직렬화(Deserialization)λž€? transient λ³€μˆ˜λž€?

직렬화(Serializaion)λž€? 역직렬화(Deserialization)λž€? μ§λ ¬ν™”λŠ” 객체λ₯Ό μ €μž₯ κ°€λŠ₯ν•œ μƒνƒœ(예λ₯Ό λ“€μ–΄ λ””μŠ€ν¬μ— 파일 ν˜•νƒœ λ“±) ν˜Ήμ€ 전솑 κ°€λŠ₯ν•œ μƒνƒœ(λ„€νŠΈμ›Œν¬ μƒμ˜ 데이터 슀트림 ν˜•νƒœ)둜 λ³€ν™˜ν•˜λŠ” 것을

code-lab1.tistory.com

 

 

 

[μ•„μ΄ν…œ 3] transientκ°€ μ—­μ§λ ¬ν™”μ‹œ μƒˆλ‘œμš΄ μΈμŠ€ν„΄μŠ€ 생성을 λ°©μ§€ν•˜λŠ” 이유 · Issue #4 · Java-Bom/ReadingR

24p 클래슀의 μΈμŠ€ν„΄μŠ€ ν•„λ“œλ₯Ό transient둜 μ„ μ–Έν•˜κ³  readResolve λ©”μ„œλ“œλ₯Ό μ œκ³΅ν•΄μ•Ό ν•œλ‹€. transientκ°€ μ§λ ¬ν™”μ‹œ μ œμ™Έμ‹œμΌœμ£ΌλŠ” 건 μ•Œκ² λŠ”λ°, 이게 μ™œ μΈμŠ€ν„΄μŠ€λ₯Ό μƒˆλ‘œ μƒμ„±ν•˜μ§€ μ•ŠλŠ”λ‹€λŠ”κ±Έ 보μž₯ν•˜λŠ”μ§€

github.com