Gpt 정보 - 100% 신뢰 할 수 없는 정보 / 기본 정보만 확인

 

CountDownLatch

 

CountDownLatch는 Java에서 스레드 동기화를 위해 사용되는 유틸리티 클래스입니다. java.util.concurrent 패키지에 포함되어 있으며, 특정 작업들이 완료될 때까지 기다리도록 다른 스레드를 블로킹하는 용도로 사용됩니다.


✅ 기본 개념

  • CountDownLatch는 초기 카운트를 가지고 생성됨.
  • 스레드들이 작업을 마친 후 countDown()을 호출해서 카운트를 감소시킴.
  • 다른 스레드는 await()을 호출해 카운트가 0이 될 때까지 기다림.
  • 한 번 카운트가 0이 되면, 다시 초기화할 수 없음.

✅ 주요 메서드

countDown() 카운트를 1 감소시킴
await() 카운트가 0이 될 때까지 현재 스레드를 대기시킴

✅ 사용 예제

import java.util.concurrent.CountDownLatch;

public class CountdownExample {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(3); // 카운트 3 설정

        for (int i = 0; i < 3; i++) {
            int workerId = i;
            new Thread(() -> {
                System.out.println("Worker " + workerId + " is working...");
                try {
                    Thread.sleep((long) (Math.random() * 2000));
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
                System.out.println("Worker " + workerId + " finished.");
                latch.countDown(); // 카운트 감소
            }).start();
        }

        System.out.println("Main thread waiting for workers...");
        latch.await(); // 모든 워커가 끝날 때까지 대기
        System.out.println("All workers finished. Main thread proceeds.");
    }
}

✅ 활용 사례

  • 여러 스레드가 병렬로 작업하고, 메인 스레드가 결과를 기다릴 때
  • 테스트 환경에서 비동기 작업이 모두 끝난 후 검증할 때
  • 서비스 초기화 과정에서 모든 의존 모듈이 준비될 때까지 대기할 때

 

CountDownLatch와 CyclicBarrier나 Semaphore의 차이점
 

 

🔁 CountDownLatch vs CyclicBarrier vs Semaphore

기능/구조 CountDownLatch CyclicBarrier Semaphore
기본 목적 하나 이상의 스레드가 작업이 완료되길 기다림 지정된 수의 스레드가 모두 도달할 때까지 대기 허용된 수만큼의 스레드가 리소스에 접근하도록 제한
카운트 재사용 가능 여부 재사용 불가 (일회성) 재사용 가능 (자동 reset) ✅ 사용자가 release()로 조절 가능
동작 방식 한 스레드가 await() → 다른 스레드들이 countDown() 여러 스레드가 await() → 지정된 수만큼 도달해야 모두 진행 여러 스레드가 acquire() → 리소스 점유 후 release()
주 용도 작업 완료 기다리기 (예: 비동기 작업 완료 후 처리) 모든 스레드가 동시에 시작하거나 동기화 필요할 때 리소스 접근 제한 (예: DB 커넥션, 네트워크 소켓 등)
예시 상황 메인 스레드가 백그라운드 작업들 기다릴 때 경기 시작 전 선수들 모두 준비됐는지 확인 동시에 3개만 연결 가능한 API 제한 등

✅ 예시

CountDownLatch

CountDownLatch latch = new CountDownLatch(3);

Thread1: doWork(); latch.countDown();
Thread2: doWork(); latch.countDown();
Thread3: doWork(); latch.countDown();

MainThread: latch.await(); // 3명 다 끝날 때까지 기다림

CyclicBarrier

CyclicBarrier barrier = new CyclicBarrier(3);

Thread1: barrier.await(); // 모두 도달할 때까지 대기
Thread2: barrier.await();
Thread3: barrier.await(); // → 여기서 3명 동시에 진행됨 (재사용 가능)

Semaphore

Semaphore sem = new Semaphore(2); // 최대 2개 동시 접근

Thread1: sem.acquire(); // 진입
Thread2: sem.acquire(); // 진입
Thread3: sem.acquire(); // 대기 (다른 2개가 release() 해야 진입 가능)

✨ 요약하면...

  • ✅ CountDownLatch: 비동기 완료 기다릴 때
  • ✅ CyclicBarrier: 모두 동시에 출발할 때
  • ✅ Semaphore: 리소스 접근 수 제한할 때

 

톱레벨 클래스와 인터페이스에 package-private 또는 public을  쓸 수 있다.
  • public으로 선언하면 API가 되므로 하위 호환성을 유지하려면 영원히 관리해야 한다.
  • 패키지 외부에서 쓰지 않을 클래스나 인터페이스라면 package-private로 선언한다.
  • 한 클래스에서만 사용하는 package-private 클래스나 인터페이스는 해당 클래스에 private static으로 중첩 시키자.

 

/*
    Member를 위한 서비스 인터페이스 구현체 DefaultMemberService는
    다른 패키지에서 사용되지 않으므로 public 접근 제어자를 가질 필요가 없다.
    Member package-private 접근 제어자로 충분하다.
*/
class DefaultMemberService implements MemberService {

    private String name;

    // PrivateStaticClass는 DefaultMemberService에서만 사용함
    // PrivateStaticClass에서 DefaultMemberService을 사용하지 않음
    // 따라서 private static으로 중첩 시킴
    private static class PrivateStaticClass {
        // 외부 인스턴스를 참조하지 않음
        void doPrint() {
            // 불가능
            //System.out.println(name);
        }
    }

    private class PrivateClass {
        // 외부 인스턴스를 참조하는 필드가 자동으로 생성
        void doPrint() {
            System.out.println(name);
        }
    }

    public static void main(String[] args) {
        Arrays.stream(PrivateClass.class.getDeclaredFields()).forEach(System.out::println);
    }

}

 

멤버(필드, 메서드, 중첩 클래스/인터페이스)의 접근 제한자 원칙
  • private과 package-private는 내부 구현
  • public 클래스의 protected와 public은 공개 API
  • public 클래스의 인스턴스 필드는 되도록 public이 아니어야 한다.

 

자바 9 모듈
  • JSR-376 스팩으로 정의한 자바의 모듈 시스템
  • 안정성: 순환 참조 허용하지 않음, 실행 시 필요한 모듈 확인, 한 패키지는 한 모듈에서만 공개할 수 있음
  • 캡슐화: public 인터페이스나 클래스라 하더라도 공개된 패키지만 사용할 수 있다. 내부 구현을 보호하는 수단으로 사용할 수 있다.
  • 확장성: 필요한 자바 플랫폼 모듈만 모아서 최적의 JRE를 구성할 수 있다. 작은 기기에서 구동할 애플리케이션을 개발할 때 유용하다.

 

출처 : 백기선 이펙트브 자바 강의

finalizer와 cleaner는 즉시 수행된다는 보장이 없다.
public class FinalizeObject {

    @Override
    protected void finalize() throws Throwable {
        System.out.print("");
    }
}

public class FinalizeExample {

    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        int i = 0;
        while (true) {
            i++;

            new FinalizeObject();

            if ((i % 1_000_000) == 0) {
                Class<?> finalizerClass = Class.forName("java.lang.ref.Finalizer");
                Field queue = finalizerClass.getDeclaredField("queue");
                queue.setAccessible(true);
                ReferenceQueue<Object> referenceQueue = (ReferenceQueue) queue.get(null);

                Field queueLength = ReferenceQueue.class.getDeclaredField("queueLength");
                queueLength.setAccessible(true);
                long length = (long) queueLength.get(referenceQueue);
                System.out.format("There are %d references in the queue\n", length);
            }
        }
    }
}
  • finalizer 동작 중에 예외가 발생하면 정리 작업이 처리되지 않을 수 있다.
  • finalizer는 보안 문제가 있다.. (구현체의 finalize 메서드를 통해 부모 객체의 메서드를 호출)
  • Deprecated since Java 9

 

public class CleanerObject {

    private List<String> resources;

    public CleanerObject(List<String> resources) {
        this.resources = resources;
    }

    public static class ResourceCleaner implements Runnable {

        private List<String> cleanableResources;

        public ResourceCleaner(List<String> cleanableResources) {
            this.cleanableResources = cleanableResources;
        }

        @Override
        public void run() {
            cleanableResources = null;
            System.out.println("Clean the resources");
        }
    }
}

public class CleanerExample {

    public static void main(String[] args) throws InterruptedException {
        Cleaner cleaner = Cleaner.create();

        List<String> resources = new ArrayList<>();
        CleanerObject cleanerObject = new CleanerObject(resources);
        cleaner.register(cleanerObject, new CleanerObject.ResourceCleaner(resources));

        cleanerObject = null;
        System.gc();
        Thread.sleep(3000L);
    }
}

Clean을 해주는 Runnable 구현체에서 정리하려는 객체를 참조해서는 안된다.

 

반납할 자원이 있는 클래스는 AutoCloseable을 구현하고 close()를 호출하거나 try-with-resource를 사용
public class CloseableObject implements AutoCloseable{

    // try-with-resources에서 실행됨
    @Override
    public void close() {
        System.out.println("Clean the resources");
    }
}

public class CloseableExample {

    public static void main(String[] args) {
        try(CloseableObject closeableObject = new CloseableObject()) {
            // 자원 반납 처리 됨
        }
    }
}

 

출처 : 백기선 이펙트브 자바 강의

불필요한 인스턴스 생성을 피하라
public class Strings {

    public static void main(String[] args) {
        String hello = "hello";

        // 같은 객체의 인스턴스를 생성해 불필요하게 메모리를 사용한다.
        String hello2 = new String("hello");

        String hello3 = "hello";

        System.out.println(hello == hello2);
        System.out.println(hello.equals(hello2));
        System.out.println(hello == hello3);
        System.out.println(hello.equals(hello3));
    }
}

 

값비싼 객체를 재사용해 성능을 개선
public class RomanNumerals {

    static boolean isRomanNumeralSlow(String s) {
        return s.matches("^(?=.)M*(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
    }

    // 값비싼 객체를 재사용해 성능을 개선한다.
    private static final Pattern ROMAN = Pattern.compile("^(?=.)M*(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");

    static boolean isRomanNumberalFast(String s) {
        return ROMAN.matcher(s).matches();
    }

    public static void main(String[] args) {
        boolean result = false;

	// 패턴 인스턴스를 비교시 마다 생성
        result = isRomanNumeralSlow("MCMLXXXVI");
        System.out.println(result);

        result = isRomanNumberalFast("MCMLXXXVI");
        System.out.println(result);
    }
}

자바 정규식이 사용될만한 곳(주의)

  1. String.matches()
  2. String.split()
  3. String.replaceAll()

 

불필요한 오토박싱, 언박식을 발생에 주의하라
public class Sum {

    private static long sum() {
        Long sum = 0L;
        for (long i = 0; i <= Integer.MAX_VALUE; i++) {
            // 불필요한 오토박싱이 발생한다.
            // long to Long
            sum += i;
        }
        return sum;
    }

    public static void main(String[] args) {
        long x = 0;

        for (int i = 0; i < 10; i++) {
            long start = System.nanoTime();
            x += sum();
            long end = System.nanoTime();
            System.out.println((end - start) / 1_000_000. + " ms.");
        }

        if (x == 42) {
            System.out.println();
        }
    }
}

 

Deprecation
  • 사용 자제를 권장하고 대안을 제시하는 방법
  • @Deprecated: 컴파일시 경고 메시지를 통해 사용 자제를 권장하는 API라는 것을 클라이언트에 알려줄 수 있다.
  • @deprecated: 문서화(Javadoc)에 사용
public class RomanNumerals {

    /**
     * @deprecated in favor of
     * {@link #isRomanNumberalFast(String)}
     */
    @Deprecated(forRemoval = true, since = "1.1")
    static boolean isRomanNumeralSlow(String s) {
        return s.matches("^(?=.)M*(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
    }

    private static final Pattern ROMAN = Pattern.compile("^(?=.)M*(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");

    static boolean isRomanNumberalFast(String s) {
        return ROMAN.matcher(s).matches();
    }
}

 

출처 : 백기선 이펙트브 자바 강의

자원을 직접 명시하지 말고 의존 객체 주입을 사용하라

 

자원을 직접 명시하면 유연성과 재사용성이 떨어진다.
public class EnglishNamingService {

    public boolean isEnglishName(String name) {
        return "English".equals(name);
    }
}

public class KoreanNamingService {

    public boolean isKoreanName(String name) {
        return "한글".equals(name);
    }
}

public class NamingUtils {

    private static final KoreanNamingService koreanNamingService = new KoreanNamingService();

    private NamingUtils() {}

    public static boolean isValid(String name) {
        return koreanNamingService.isKoreanName(name);
    }
}
  • NamingUtils에서 한글 네이밍 기능이 필요하지 않고 영어 네이밍 기능이 필요하다면 NamingService를 EnglishNamingService로 수정이 필요하다. (유연성이 떨어짐)
  • 같거나 비슷한 기능이라도 isKoreanName, isEnglishName과 같이 메서드명이 다를 수 있기 때문에 NamingUtils의 코드를 수정해야한다. (재사용성이 떨어짐)

 

팩터리 메서드 패턴을 사용
public interface NamingService {

    public int getLength(String name);
}

public interface NamingServiceFactory {

    NamingService getNamingService();
}

public class EnglishNamingFactory implements NamingServiceFactory{
    @Override
    public NamingService getNamingService() {
        return new EnglishNamingService();
    }
}

public class KoreanNamingFactory implements NamingServiceFactory{
    @Override
    public NamingService getNamingService() {
        return new KoreanNamingService();
    }
}

public class FactoryNamingUtils {

    private NamingService namingService;

    public FactoryNamingUtils(NamingServiceFactory namingServiceFactory) {
        namingService = namingServiceFactory.getNamingService();
    }

    public int getLength(String name) {
        return namingService.getLength(name);
    }
}

새로운 NamingService가 추가되더라도 팩토리를 사용하는 코드는 변경할 필요가 없다.

 

Spring IoC(Inversion of Control)

 

출처 : 백기선 이펙트브 자바 강의

생성자나 열거 타입으로 싱글턴임을 보증하라
열거 타입으로 싱글턴 보증
public enum Elvis implements IElvis {

    INSTANCE;

    public void leaveTheBuilding() {
        System.out.println("Leaving the building.");
    }

    @Override
    public void sing() {
        System.out.println("Singing");
    }

    public static void main(String[] args) {
        Elvis elvis = Elvis.INSTANCE;
        elvis.leaveTheBuilding();
    }
}

enum 타입은 생성자를 사용해 인스턴스를 생성할 수 없어 싱글턴을 보증한다.

 

메서드 참조

람다 표현식이 하는 일이 단순 메서드 호출하는 것 뿐이라면 메서드 참조로 람다 표현식을 줄여쓸 수 있다.

 

public class SingerImpl implements Singer {

    private static final SingerImpl INSTANCE = new SingerImpl();

    private SingerImpl() {}

    public static SingerImpl getInstance() {
        return INSTANCE;
    }

    @Override
    public void sing() {
        System.out.println("Hello!");
    }
    
     public static void main(String[] args) {
        Concert concert = new Concert(Elvis.INSTANCE);
        
        // 스태틱 메서드 참조 사용
        concert.start(SingerImpl::getInstance);
    }
}
  • 스태틱 메서드 레퍼런스
  • 인스턴스 메서드 레퍼런스
  • 임의 객체의 인스턴스 메서드 레퍼런스
    • 임의 객체의 인스턴스 메서드의 첫번째 인자는 자기자신(임의 객체)
  • 생성자 레퍼런스
    • 생성자의 인자가 확실하지 않는 경우 

 

함수형 인터페이스

람다 표현식과 메서드 참조에 대한 타겟 타입(Function<Input, Output>, Supplier<Output>, Consumer<Input>, Predicate<Input> 등..)을 제공한다.

 public static void main(String[] args) {
 
 	// 인자 없이 반환값만 있음
    Supplier<Singer> singerSupplier = SingerImpl::getInstance;
    
    // 가수의 이름을 받고 인스턴스를 생성해 반환
    Function<String, Singer> singerFuction = SingerImpl::getInstance;
    
    // Predicate의 반환 타입은 boolean
    Predicate<Singer> singerPredicate = SingerImpl::isMan;
 }

자바가 기본으로 제공해 주는 함수형 인터페이스들은 'java.util.function' 패키지 안에 있다.

직접 함수형 인터페이스를 정의하고 싶은 경우 인터페이스에 오직 하나의 메서드만 선언되어 있어야한다.(@FunctionalInterface 어노테이션 사용은 선택: 코드 오류를 컴파일 오류를 통해 확인 할 수 있다.)

@FunctionalInterface
public interface MyFunction {
	
    boolean isLessThanTen(Integer integer);
}

 

직렬화
  • 바이트스트림으로 변환한 객체를 파일로 저장하거나 네트워크를 통해 다른 시스템으로 전송할 수 있다.
  • 포맷. json, xml.
  • 필드에 transient 선언하면 직렬화 제외한다.
  • serialVersionUID 명시적으로 선언하지 않으면 런타임중에 임의로 만든다.(클래스가 변경되면 변경되는 값)
  • private static final long serialVersionUID을 선언하면 클래스가 변경되어도 직렬화-역직렬화가 동작한다.

 

출처 : 백기선 이펙트브 자바 강의

'JAVA' 카테고리의 다른 글

Effective Java 불필요한 객체 생성을 피하라  (1) 2023.04.17
Effective Java 의존 객체 주입  (0) 2023.04.13
Effective Java 싱글턴  (1) 2023.04.10
Effective Java 빌더  (0) 2023.04.07
Effective Java 리플렉션  (0) 2023.04.06
생성자나 열거 타입으로 싱글턴임을 보증하라.
private 생성자 + public static final 필드
public class Elvis {

    public static final Elvis INSTANCE = new Elvis();

    private Elvis() {}

    public void leaveTheBuilding() {
        System.out.println("Leaving the building.");
    }

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

    public static void main(String[] args) {
        Elvis elvis = Elvis.INSTANCE;
        elvis.leaveTheBuilding();
    }
}

 

인터페이스를 사용할 경우 테스트에 용이하다.
public class Concert {

    private boolean lightsOn;

    private boolean mainStateOpen;

    private IElvis elvis;

    public Concert(IElvis elvis) {
        this.elvis = elvis;
    }

    public void perform() {
        mainStateOpen = true;
        lightsOn = true;
        elvis.sing();
    }

    public boolean isLightsOn() {
        return lightsOn;
    }

    public boolean isMainStateOpen() {
        return mainStateOpen;
    }
}

public interface IElvis {

    void leaveTheBuilding();

    void sing();
}

public class MockElvis implements IElvis{

    @Override
    public void leaveTheBuilding() {

    }

    @Override
    public void sing() {
        System.out.println("Test singing.");
    }
}

class ConcertTest {

    @Test
    void perform() {
        Concert concert = new Concert(new MockElvis());
        concert.perform();

        assertTrue(concert.isLightsOn());
        assertTrue(concert.isMainStateOpen());
    }
}

Concert가 정상적으로 동작하는지 확인하는 테스트에 경우 불필요하게 싱글턴 인스턴스를 참조할 필요 없다. 인터페이스를 사용하여 가짜 객체를 생성 후 싱글턴 인스턴스를 대체 가능하다.

 

리플렉션을 통한 싱글턴 객체 인스턴스 생성을 막아야 한다.
public class Elvis implements IElvis, Serializable {

    public static final Elvis INSTANCE = new Elvis();

    private static boolean created;

    /**
     * 리플렉션을 통한 싱글턴 인스턴스 생성 방지
     * @see ElvisReflection#main(String[])
     */
    private Elvis() {
        if (created) {
            throw new UnsupportedOperationException("Can't be created by constructor.");
        }

        created = true;
    }

    public void leaveTheBuilding() {
        System.out.println("Leaving the building.");
    }

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

    public static void main(String[] args) {
        Elvis elvis = Elvis.INSTANCE;
        elvis.leaveTheBuilding();
    }
}

public class ElvisReflection {

    public static void main(String[] args) {
        try {
            // getConstructor: public 생성자만 접근 가능
            // getDeclaredConstructor: 선언되어 있는 생성자만 접근 가능
            Constructor<Elvis> defulatContConstructor = Elvis.class.getDeclaredConstructor();
            // 생성자의 접근 지시자가 private 이므로 접근 하기 위해 설정
            defulatContConstructor.setAccessible(true);
            Elvis elvis1 = defulatContConstructor.newInstance();
            Elvis elvis2 = defulatContConstructor.newInstance();
            System.out.println(elvis1 == elvis2);

        } catch (NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

인스턴스가 생성되었을때 변경되는 flag인 boolean 타입 created를 통해 생성자를 통한 인스턴스 생성을 방지함으로서 리플렉션을 통해 싱글턴 객체의 인스턴스 생성을 방지할 수 있다.

 

직렬화-역직렬화를 통한 싱글턴 인스턴스 생성을 막아야 한다.
public class Elvis implements IElvis, Serializable {

    public static final Elvis INSTANCE = new Elvis();

    private static boolean created;

    private Elvis() {
    }

    public void leaveTheBuilding() {
        System.out.println("Leaving the building.");
    }

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

    public static void main(String[] args) {
        Elvis elvis = Elvis.INSTANCE;
        elvis.leaveTheBuilding();
    }

    /**
     * 역직렬화할때 사용되는 메서드
     * 역직렬화를 통한 싱글턴 인스턴스 생성 방지
     * @see ElvisReflection#main(String[])
     * @return Elvis
     */
    private Object readResolve() {
        return INSTANCE;
    }
}

public class ElvisSerialization {

    // 직렬화 - 역직렬화로 여러 인스턴스를 생성할 수 있어 싱글턴을 보증하지 못한다.
    public static void main(String[] args) {
        try {
            ObjectOutput out = new ObjectOutputStream(new FileOutputStream("elvis.obj"));
            out.writeObject(Elvis.INSTANCE);
        } catch (IOException e) {
            e.printStackTrace();
        }

        try {
            ObjectInput in = new ObjectInputStream(new FileInputStream("elvis.obj"));
            Elvis elvis = (Elvis) in.readObject();
            System.out.println(elvis == Elvis.INSTANCE);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

역직렬화할때 사용되는 메서드인 readResolve 메서드를 오버라이딩함으로서 싱글턴 인스턴스를 반환한다.

 

정적 팩터리를 제너릭 싱글턴 팩터리로 만들 수 있다.
public class MetaElvis<T> {

    private static final MetaElvis<Object> INSTANCE = new MetaElvis<>();

    private MetaElvis() {
    }

    @SuppressWarnings("unchecked")
    public static <T> MetaElvis<T> getInstance() {
        return (MetaElvis<T>) INSTANCE;
    }

    public void sing(T t) {
        System.out.println(t);
    }

    public void leaveTheBuilding() {
        System.out.println("Leaving the building.");
    }

    // 제너릭 싱글톤 펙토리를 사용하면 동일한 인스턴스를 원하는 타입으로 형변환이 가능하다.
    public static void main(String[] args) {
        MetaElvis<String> elvis1 = MetaElvis.getInstance();
        MetaElvis<Integer> elvis2 = MetaElvis.getInstance();
    }
}

 

 

정적 팩터리의 메서드 참조를 공급자(Suppiler)로 사용할 수 있다.
public interface Singer {

    void sing();
}

public class SingerImpl implements Singer {

    private static final SingerImpl INSTANCE = new SingerImpl();

    private SingerImpl() {}

    public static SingerImpl getInstance() {
        return INSTANCE;
    }

    @Override
    public void sing() {
        System.out.println("Hello!");
    }
}

public class Concert {

    private boolean lightsOn;

    private boolean mainStateOpen;

    private IElvis elvis;

    public Concert(IElvis elvis) {
        this.elvis = elvis;
    }

    public void perform() {
        mainStateOpen = true;
        lightsOn = true;
        elvis.sing();
    }

    public boolean isLightsOn() {
        return lightsOn;
    }

    public boolean isMainStateOpen() {
        return mainStateOpen;
    }

    // 정적 팩터리의 메서드 참조를 공급자(Supplier)로 사용할 수 있다.
    public void start(Supplier<Singer> singerSupplier) {
        Singer singer = singerSupplier.get();
        singer.sing();
    }

    public static void main(String[] args) {
        Concert concert = new Concert(Elvis.INSTANCE);
        concert.start(SingerImpl::getInstance);
    }
}

 

출처 : 백기선 이펙트브 자바 강의

생성자에 매개변수가 많다면 빌더를 고려하라.
생성자 체이닝 사용
public class Member {

    private String name;

    private String loginId;

    private String loginPwd;

    private String email;

    private String phoneNumber;

    private String address;

    public Member() {}

    public Member(String name) {
        this.name = name;
    }

    public Member(String name, String loginId) {
        this.name = name;
        this.loginId = loginId;
    }

    public Member(String name, String loginId, String loginPwd) {
        this.name = name;
        this.loginId = loginId;
        this.loginPwd = loginPwd;
    }

    public Member(String name, String loginId, String loginPwd,
                  String phoneNumber) {
        this(name, loginId, loginPwd, phoneNumber, null);
    }

    public Member(String name, String loginId, String loginPwd,
                  String phoneNumber, String address) {
        this.name = name;
        this.loginId = loginId;
        this.loginPwd = loginPwd;
        this.phoneNumber = phoneNumber;
        this.address = address;
    }
}

public class LoginService {

    public void login() {
        Member member = new Member("test", "loginId", "loginPwd");
    }
}

파라미터가 많아지면 어떤 값이 필요한지 어떤 필드에 저장되는지 확인하기 어렵다.

 

자바빈즈 패턴 사용
public class Member {

    private String name;

    private String loginId;

    private String loginPwd;

    private String email;

    private String phoneNumber;

    private String address;

    public Member() {}

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

    public void setLoginId(String loginId) {
        this.loginId = loginId;
    }

    public void setLoginPwd(String loginPwd) {
        this.loginPwd = loginPwd;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public void setPhoneNumber(String phoneNumber) {
        this.phoneNumber = phoneNumber;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}

public class LoginService {

    public void login() {
        Member member2 = new Member();
        member2.setName("test2");
}

값 설정을 필수로하는 모든 필드가 설정되기 전에 사용될 가능성이 있다.

어떤 필드를 설정해야 되는지 모호하다.

 

생성자와 메서드 사용
public class Member {

    private String name;

    private String loginId;

    private String loginPwd;

    private String email;

    private String phoneNumber;

    private String address;

    public Member() {}

    public Member(String name) {
        this.name = name;
    }

    public Member(String name, String loginId) {
        this.name = name;
        this.loginId = loginId;
    }

    public Member(String name, String loginId, String loginPwd) {
        this.name = name;
        this.loginId = loginId;
        this.loginPwd = loginPwd;
    }

    public Member(String name, String loginId, String loginPwd,
                  String phoneNumber) {
        this(name, loginId, loginPwd, phoneNumber, null);
    }

    public Member(String name, String loginId, String loginPwd,
                  String phoneNumber, String address) {
        this.name = name;
        this.loginId = loginId;
        this.loginPwd = loginPwd;
        this.phoneNumber = phoneNumber;
        this.address = address;
    }

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

    public void setLoginId(String loginId) {
        this.loginId = loginId;
    }

    public void setLoginPwd(String loginPwd) {
        this.loginPwd = loginPwd;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public void setPhoneNumber(String phoneNumber) {
        this.phoneNumber = phoneNumber;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}

public class LoginService {

    public void login() {
        Member member3 = new Member("test3");
        member3.setEmail("email");
    }
}

필수 필드를 생성자로 강제성을 주고 선택적인 필드는 메서드를 통해 설정가능하다는 장점이 있지만 불변객체로 만들기 어렵다는 단점이 있다.

 

빌더 패턴 사용
public class Member {

    private String name;

    private String loginId;

    private String loginPwd;

    private String email;

    private String phoneNumber;

    private String address;

    public Member(Builder builder) {
    	name = builder.name;
        loginId = builder.loginId;
        loginPwd = builder.loginPwd;
        email = builder.email;
        phoneNumber = builder.phoneNumber;
        address = builder.address;
    }

    public static class Builder {
        private String name;

        private String loginId = "";

        private String loginPwd = "";

        private String email = "";

        private String phoneNumber = "";

        private String address = "";

        public Builder(String name) {
            this.name = name;
        }

        public Builder loginId(String val) {
            this.loginId = val;
            return this;
        }

        public Builder loginPwd(String val) {
            this.loginPwd = val;
            return this;
        }

        public Builder email(String val) {
            this.email = val;
            return this;
        }

        public Builder phoneNumber(String val) {
            this.phoneNumber = val;
            return this;
        }

        public Builder address(String val) {
            this.address = val;
            return this;
        }

        public Member build() {
            return new Member(this);
        }

    }
}

public class LoginService {

    public void login() {
        Member member4 = new Member.Builder("test4")
                .email("email")
                .build();
    }
}

계층적으로 설계된 클래스와 함께 사용하기 좋다.

점층적 생성자보다 코드를 읽고 쓰기가 간결하고 안전하다.

 

빌더를 만들기 위해 필드가 중복되는 단점이 있다.

 

롬복 @Builder 애노테이션 사용
package me.minseok.effectivejava.chapter02;

import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;

@Builder
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class Member {

    private String name;

    private String loginId;

    private String loginPwd;

    private String email;

    private String phoneNumber;

    private String address;

}

롬복이 제공하는 @Builder 애노테이션을 사용하면 모든 필드를 매개변수로 받는 생성자가 자동으로 생성된다. 이를 막기 위해 @AllArgsContructor 애노테이션 속성(access)을 AccessLevel.PRIVATE로 설정하면 외부에서 해당 생성자를 사용하지 못하도록 막을 수 있다.

 

출처 : 백기선 이펙트브 자바 강의

+ Recent posts