Try using it in your preferred language.

English

  • English
  • 汉语
  • Español
  • Bahasa Indonesia
  • Português
  • Русский
  • 日本語
  • 한국어
  • Deutsch
  • Français
  • Italiano
  • Türkçe
  • Tiếng Việt
  • ไทย
  • Polski
  • Nederlands
  • हिन्दी
  • Magyar
translation

AI가 번역한 다른 언어 보기

제이온

[이펙티브 자바] 아이템 1. 생성자 대신 정적 팩터리 메서드를 고려하라

  • 작성 언어: 한국어
  • 기준국가: 모든 국가 country-flag

언어 선택

  • 한국어
  • English
  • 汉语
  • Español
  • Bahasa Indonesia
  • Português
  • Русский
  • 日本語
  • Deutsch
  • Français
  • Italiano
  • Türkçe
  • Tiếng Việt
  • ไทย
  • Polski
  • Nederlands
  • हिन्दी
  • Magyar

durumis AI가 요약한 글

  • 정적 팩터리 메서드는 public 생성자와 함께 클래스의 인스턴스를 얻는 방법으로, 생성자보다 유연하고 효율적인 장점을 제공합니다.
  • 정적 팩터리 메서드를 사용하면 인스턴스를 통제하고, 반환 타입의 하위 타입 객체를 반환하며, 리플렉션을 사용하지 않고도 유연하게 구현체를 제공할 수 있습니다.
  • 하지만, 상속 시 제약이 있고, 프로그래머가 찾기 어려운 단점이 있으므로 적절하게 사용해야 합니다.

개요

클래스의 인스턴스를 얻는 전통적인 수단은 public 생성자다.


public class Member {

    private String name;

    private int age;

    private String hobby;

    private MemberStatus memberStatus;

    public Member(String name, int age, String hobby, MemberStatus memberStatus) {
        this.name = name;
        this.age = age;
        this.hobby = hobby;
        this.memberStatus = memberStatus;
    }
}

public enum MemberStatus {

    ADVANCED,
    INTERMEDIATE,
    BASIC;
}


일반적으로는 public 생성자만으로도 충분하지만, 생성자 외에 정적 팩터리 메서드(static factory method)를 제공하면 사용자 입장에서 의도한 대로 인스턴스를 만들기 쉬워지는 경우가 종종 있다.


정적 팩터리 메서드의 대표적인 예시는 Boolean의 valueOf() 메서드가 있다.


public static Boolean valueOf(boolean b) {
    return b ? Boolean.TRUE : Boolean.FALSE;
}


위 메서드는 기본 타입인 boolean 값을 받아서 Boolean 객체로 만들어서 반환해 주고 있다.


정적 팩터리 메서드의 장점

이름을 가질 수 있다.

생성자에 넘기는 매개변수와 생성자 자체만으로는 반환될 객체의 특성을 제대로 설명하지 못한다. 가령 위의 Member 클래스의 메인 생성자(name, age, hobby, memberStatus)만 보고 어떤 특성을 가진 Member인지 파악하기 어렵다.


또한, 하나의 시그니처로는 하나의 생성자를 만들 수 있는데, 정적 팩터리 메서드는 이름을 가질 수 있으므로 하나의 시그니처로 여러 개의 정적 팩터리 메서드를 만들어서 인스턴스를 반환할 수 있다.


public class Member {

    private String name;

    private int age;

    private String hobby;

    private MemberStatus memberStatus;

    public Member(String name, int age, String hobby, MemberStatus memberStatus) {
        this.name = name;
        this.age = age;
        this.hobby = hobby;
        this.memberStatus = memberStatus;
    }

    public static Member basicMember(String name, int age, String hobby) {
        return new Member(name, age, hobby, MemberStatus.BASIC);
    }

    public static Member intermediateMember(String name, int age, String hobby) {
        return new Member(name, age, hobby, MemberStatus.INTERMEDIATE);
    }

    public static Member advancedMember(String name, int age, String hobby) {
        return new Member(name, age, hobby, MemberStatus.ADVANCED);
    }

}


위와 같이 생성자로 MemberStatus를 구분하는 것보다 같은 시그니처를 가진 여러 개의 정적 팩터리 메서드를 만들면, 사용자 입장에서 혼동의 여지 없이 특정 실력을 가진 Member 인스턴스를 생성할 수 있게 된다.

JDK에서 정의된 라이브러리를 보자면, BigInteger의 정적 팩터리 메서드인 probablePrime() 이 존재한다.


public static BigInteger probablePrime(int bitLength, Random rnd) {
    if (bitLength < 2)
        throw new ArithmeticException("bitLength < 2");

    return (bitLength < SMALL_PRIME_THRESHOLD ?
            smallPrime(bitLength, DEFAULT_PRIME_CERTAINTY, rnd) :
            largePrime(bitLength, DEFAULT_PRIME_CERTAINTY, rnd));
}


BigInteger의 일반 생성자와 정적 팩터리 메서드인 probablePrime() 을 비교했을 때, 값이 소수인 BigInteger를 반환한다.라는 문장은 당연히 후자가 더 잘 설명할 것이다.


호출될 때마다 인스턴스를 새로 생성하지 않아도 된다.

public static Boolean valueOf(boolean b) {
    return (b ? Boolean.TRUE : Boolean.FALSE);
}


Boolean의 valueOf() 메소드는 인스턴스를 미리 캐싱해뒀다가 반환해 주는 것을 알 수 있다. 이러한 특성은 생성 비용이 큰 객체가 자주 요청되는 상황이라면 성능을 상당히 높여줄 수 있고, 플라이웨이트 패턴도 이와 비슷한 기법으로 볼 수 있다.


반복되는 요청에 같은 객체를 반환하는 식으로 정적 팩터리 메서드 방식을 사용하는 클래스는 인스턴스의 생명 주기를 통제할 수 있으므로 인스턴스 통제 클래스 라고 한다. 인스턴스를 통제하면, 싱글톤 클래스를 만들거나 인스턴스화 불가 클래스를 만들 수 있다. 또한 불변 값 클래스에서 같은 인스턴스가 단 하나임을 보장할 수 있다.

인스턴스 통제는 플라이웨이트 패턴의 근간이 되며, 열거 타입은 인스턴스가 하나만을 만들어짐을 보장한다.


예제

마인크래프트에서 나무를 심어야 한다. 만약 나무 객체 하나 당 새로 생성하면, 메모리 오버 플로우가 발생할 여지가 있다.

따라서 위와 같이 빨간 나무와 연두색 나무 객체는 저장해 놓고, 위치만 바꾸어서 반환하면 된다. 물론 색은 2가지 색 외에 더 늘어날 수 있으니, Map 같은 자료 구조에 색깔에 따라 나무를 저장해 놓으면 효율적일 것이다.


public class Tree {

    // 나무는 아래와 같이 3개 정보를 가지고 있다.
    private String color;
    private int x;
    private int y;

    // 색상으로만 생성자를 만들어 준다.
    public Tree(String color) {
        this.color = color;
    }

    public void setX(int x) {
        this.x = x;
    }

    public void setY(int y) {
        this.y = y;
    }

    // 나무를 심을 때
    public void install(){
        System.out.println("x:"+x+" y:"+y+" 위치에 "+color+"색 나무를 설치했습니다!");
    }
}

public class TreeFactory {
    // HashMap 자료구조를 활용해서 만들어진 나무들을 관리한다.
    public static final Map treeMap = new HashMap<>();
    
   
    public static Tree getTree(String treeColor){
        // Map에 입력받은 색상의 나무가 있는지 찾는다. 있으면 그 객체를 제공한다.
        Tree tree = (Tree)treeMap.get(treeColor); 

       // 만약 아직 같은 색상의 나무가 Map에 없다면 새로 객체를 생성해 제공한다.
        if(tree == null){
            tree = new Tree(treeColor);
            treeMap.put(treeColor, tree);
            System.out.println("새 객체 생성");
        }

        return tree;
    }
}

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        
        System.out.println("원하는 색을 입력해주세요 :)");
        for(int i=0;i<10;i++){
            // 나무 색 입력받기
            String input = scanner.nextLine();
            // 팩토리에서 나무 하나 공급받기
            Tree tree = (Tree)TreeFactory.getTree(input);
            // 나무 x,y 설정하고
            tree.setX((int) (Math.random()*100));
            tree.setY((int) (Math.random()*100));
            // 나무 설치하기
            tree.install();
        }
    }
}


싱글톤 패턴과의 차이

싱글톤 패턴은 나무 클래스에 단 한 개의 나무만 만들 수 있다. 따라서 싱글톤 패턴을 사용한다면, 만들어진 단 하나의 객체의 색깔을 바꿔야 한다. 즉, 싱글톤 패턴은 종류 상관 없이 단 하나만 가질 수 있다.


사용 사례

Java의 String Constant Pool에서 플라이웨이트 패턴이 사용된다.


반환 타입의 하위 타입 객체를 반환할 수 있다.

Arrays 유틸 클래스의 asList() 메서드를 사용한 적이 있다면 해당 장점을 이해할 수 있다.


public static  List asList(T... a) [
    return new ArrayList<>(a);
}


List의 하위 구현체인 ArrayList로 값을 래핑하여 반환하는데, 사용자는 이러한 구현체까지 알 필요가 없다. 즉 반환 객체의 클래스를 자유롭게 선택할 수 있다는 유연성은 개발자가 구현체를 공개하지 않고 구현체를 반환할 수 있으므로 API를 작게 유지할 수 있다.


자바 인터페이스의 정적 메서드 관련 이야기

자바 8 전에는 인터페이스에 정적 메서드를 선언할 수 없어서 이름이 “Type”인 인터페이스를 반환하는 정적 메서드가 필요하면, “Types”라는 인스턴스화 불가인 동반 클래스를 만들어 그 안에 메서드를 정의하였다.


대표적인 예시로 JCF가 제공하는 45개의 유틸리티 구현체가 있는데, 이 구현체 대부분을 단 하나의 동반 클래스인 java.util.Collections 에서 정적 팩터리 메서드를 통해 얻도록 했다. 특히 이 구현체 중에는 public이 아니라서 오로지 정적 팩터리 메서드에 의해서만 인스턴스를 만들 수 있는 구현체도 존재한다. (이 구현체는 당연히 상속을 받을 수 없음.)


또한, 45개의 구현체를 공개하지도 않기 때문에 API를 훨씬 작게 만들 수 있었다.


// 인터페이스와 동반 클래스의 예
List empty = Collections.emptyList();


하지만, Java 8부터는 인터페이스에 바로 정적 메서드를 추가할 수 있기 때문에 동반 클래스를 따로 정의하지 않아도 된다.


입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할 수 있다.

단순히 하위 타입을 반환한다는 점을 넘어 파라미터의 값에 따라 다른 하위 타입을 반환할 수 있다. 가령, 점수에 따라 MemberStatus를 다르게 반환하고 싶다면, 아래와 같이 정적 팩터리 메서드를 만들고 그 안에 비교 로직을 세우면 된다.


public enum MemberStatus {

    ADVANCED(80, 100),
    INTERMEDIATE(50, 79),
    BASIC(0, 49);

    private final int minScore;
    private final int maxScore;

    MemberStatus(int minScore, int maxScore) {
        this.minScore = minScore;
        this.maxScore = maxScore;
    }

    public static MemberStatus of(int score) {
        return Arrays.stream(values())
                .filter(decideMemberStatus(score))
                .findAny()
                .orElseThrow(() -> new NoSuchElementException("해당하는 MemberStatus 객체가 없습니다."));
    }

    private static Predicate decideMemberStatus(int score) {
        return element -> element.minScore <= score && element.maxScore >= score;
    }
}

@DisplayName("MemberStatus 테스트")
class MemberStatusTest {

    @ParameterizedTest
    @CsvSource(value = {"0:BASIC", "30:BASIC", "50:INTERMEDIATE", "70:INTERMEDIATE", "80:ADVANCED", "100:ADVANCED"}, delimiter = ':')
    @DisplayName("점수에 따라 MemberStatus를 다르게 반환한다.")
    void of(int input, MemberStatus expected) {
        assertThat(MemberStatus.of(input)).isEqualTo(expected);
    }
}


정적 팩터리 메서드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도 된다.

위 문장에서 객체의 클래스는 우리가 작성하는 클래스 파일이 맞다.

참고로 Class는 클래스 로더가 클래스를 로딩할 때 힙 영역에 할당하는 Class 객체를 의미한다. 이 Class 객체는 우리가 작성한 클래스의 다양한 메타 데이터를 담고 있다.


package algorithm.dataStructure;

public abstract class StaticFactoryMethodType {

    public abstract void getName();

    public static StaticFactoryMethodType getNewInstance() {
        StaticFactoryMethodType temp = null;
        try {
            Class childClass = Class.forName("algorithm.dataStructure.StaticFactoryMethodTypeChild"); // 리플렉션
            temp = (StaticFactoryMethodType) childClass.newInstance(); // 리플렉션

        } catch (ClassNotFoundException e) {
           System.out.println("클래스가 없습니다.");
        } catch (InstantiationException  e) {
            System.out.println("메모리에 올릴수 없습니다.");
        } catch (IllegalAccessException  e) {
            System.out.println("클래스 파일 접근 오류입니다.");
        }

        return temp;
    }
}


위 코드를 보면 인터페이스 구현체의 위치를 통해 Class 객체를 생성하고, 리플렉션 기술을 사용하여 실제 구현체를 초기화하는 것을 확인할 수 있다. 이때 정적 팩터리 메서드를 작성하는 시점에는 StaticFactoryMethodTypeChild 클래스는 존재하지 않아도 된다.


만약 정적 팩터리 메서드를 사용하는 시점에 algorithm.dataStructure.StaticFactoryMethodTypeChild 경로에 대해 구현체가 없다면 에러가 발생하겠지만, 정적 팩터리 메서드를 작성하는 시점에는 문제가 없으므로 유연하다고 하는 것이다.


public interface Test {

    int sum(int a, int b);

    // Test는 인터페이스고 구현체가 없더라도 정적 팩터리 메서드 작성 시점에는 문제가 발생하지 않는다.
    static Test create() {
        return null;
    }
}

public class Main {

    public static void main(String[] args) {
        Test test = Test.create();
        System.out.println(test.sum(1, 2)); // NPE 발생
    }
}


리플렉션을 사용하지 않더라도 똑같은 유연함을 얻을 수 있다. Test의 정적 팩터리 메서드인 create() 를 보면, 구현체가 없더라도 작성 시점에는 문제가 발생하지 않는다. 물론 실제 사용 시점에는 NPE가 발생하니, 나중에 구현체를 반환해 주어야 한다.


이러한 유연함은 서비스 제공자 프레임워크를 만드는 근간이 되는데, 대표적으로 JDBC가 있다. JDBC 서비스 제공자 프레임워크의 제공자는 서비스의 구현체고, 이 구현체들을 클라이언트에 제공하는 역할을 프레임워크가 통제하여, 클라이언트를 구현체로부터 분리한다.(DIP)


  • 서비스 제공자 프레임워크의 컴포넌트
    • 서비스 인터페이스
      • 구현체의 동작을 정의함
      • JDBC의 Connection
    • 제공자 등록 API
      • 제공자가 구현체를 등록함
      • JDBC의 DriverManager.registerDriver()
    • 서비스 접근 API
      • 클라이언트가 서비스의 인스턴스를 얻을 때 사용하며, 조건을 명시하지 않을 경우 기본 구현체 혹은 지원하는 구현체를 돌아가며 반환한다.
      • 정적 팩터리 메소드의 해당함
      • JDBC의 DriverManager.getConnection()
    • (옵션) 서비스 제공자 인터페이스
      • 이것이 없다면 각 구현체를 인스턴스로 만들 때 리플렉션을 사용해야 함
      • JDBC의 Driver


서비스 제공자 프레임워크 패턴은 여러 변형이 있으며, 브릿지 패턴, 의존 객체 주입 프레임워크 등이 있다.


전형적인 JDBC 예제

Class.forName("oracle.jdbc.driver.OracleDriver"); 
Connection connection = null; 
connection = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:ORA92", "root", "root"); 

// 각종 Statement를 활용한 sql 로직


일반적으로 JDBC는 위와 같이 작성한다. Class.forName() 을 통해 Driver의 구현체 중 하나인 OracleDriver를 등록하고, DriverManager.getConnection() 을 통해 Connection의 구현체 중 하나인 OracleDriver용 Connection을 가져온다.


여기서 Connection은 서비스 인터페이스, DriverManager.getConnection() 은 서비스 접근 API, Driver는 서비스 제공자 인터페이스임을 알 수 있다. 하지만 제공자 등록 API인 DriverManager.registerDriver() 는 사용되지 않았다. 그럼에도 불구하고, 우리는 Class.forName() 만 가지고도 Driver의 구현체인 OracleDriver를 등록할 수 있다. 어떻게 이것이 가능할까?


Class.forName()의 동작 원리

해당 메소드는 물리적인 클래스 파일 명을 인자로 넣어주면, JVM에게 이 클래스를 로드하라고 요청한다. 그러면 클래스 로더는 클래스의 메타 데이터를 메서드 영역에 저장하는 한편, Class 객체를 힙 영역에 할당하게 된다. 또한, 클래스 로딩이 끝나게 되면 static 필드 및 static 블록이 초기화되며, 이때 제공자 등록 API가 활용된다.


public class OracleDriver implements Driver {

    static {
        defaultDriver = null;
        Timestamp timestamp = Timestamp.valueOf("2000-01-01 00:00:00.0");
        try {
            if (defaultDriver == null) {
                defaultDriver = new OracleDriver();
                DriverManager.registerDriver(defaultDriver); // OracleDriver 등록
            }
        } catch (RuntimeException runtimeexception) {
        } catch (SQLException sqlexception) {
        }
    }

    ...
}


실제로 OracleDriver를 보면 static 블록 안에서 DriverManager.registerDriver() 를 활용하여 Driver의 구현체인 OracleDriver를 등록하는 것을 알 수 있다.


DriverManager 클래스 분석

public class DriverManager {

    private DriverManager() {
    }

    private static final Map drivers = new ConcurrentHashMap();
    public static final String DEFAULT_DRIVER_NAME = "default";

    public static void registerDefaultPrivider(Driver d) {
        System.out.println("Driver 등록");
        registerDriver(DEFAULT_DRIVER_NAME, d);
    }

    public static void registerDriver(String name, Driver d) {
        drivers.put(name, d);
    }

    public static Connection getConnection() {
        return getConnection(DEFAULT_DRIVER_NAME);
    }

    public static Connection getConnection(String name) {
        Driver d = drivers.get(name);
        if (d == null) throw new IllegalArgumentException();
        return d.getConnection();
    }
}


DriverManager 클래스는 실제로 훨씬 복잡하겠지만, 핵심만 간추려서 쉽게 보면 위와 유사하다. 위에서 설명한 대로 registerDriver() 를 OracleDriver의 static 블록에서 호출하여 OracleDriver를 등록하고, getConnection() 를 호출하여 사용자는 Connection의 구현체를 얻어올 수 있다.


사용자 접근 API인 getConnetion() 을 자세히 보면, Driver 인터페이스로부터 Connection을 얻어오는 것을 확인할 수 있다. 만약 서비스 제공 인터페이스인 Driver가 없다면, 원하는 Connection 구현체를 반환하기 위해 Class.forName() 과 같은 리플렉션을 사용할 수 있다. 이때 Connection 구현체는 정적 팩터리 작성 시점에는 존재하지 않아도 된다.


대신 우리는 Driver 인터페이스를 사용하며, 동적으로 Driver의 구현체를 등록한 뒤, 이 Driver에 맞는 Connection 구현체를 쉽게 얻어올 수 있다.


참고로 DriverManager의 getConnection() 메서드의 실제 JDK 코드를 분석해 보았는데, 크게 관심이 없다면 건너뛰어도 무방하다.


@CallerSensitive
public static Connection getConnection(String url,
    String user, String password) throws SQLException {
    java.util.Properties info = new java.util.Properties();

    if (user != null) {
        info.put("user", user);
    }
    if (password != null) {
        info.put("password", password);
    }

    return (getConnection(url, info, Reflection.getCallerClass()));
}


먼저 public 정적 메서드인 getConnection() 이 호출되며, url, Properties 및 CallerClass가 private 정적 메서드인 getConnection() 의 인자로 넘어간다. 이때 Reflection.getCallerClass() 는 해당 public 정적 메서드인 getConnection() 을 호출한 클래스를 얻어 오는 역할을 한다. 만약 Car 클래스가 getConnection() 을 호출했다면, Reflection.getCallerClass() 에 의해 Class 객체를 얻어올 수 있다.


private static Connection getConnection(String url, java.util.Properties info, Class caller) throws SQLException {
    ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
    synchronized(DriverManager.class) {
        if (callerCL == null) {
            callerCL = Thread.currentThread().getContextClassLoader();
        }
    }

    if(url == null) {
        throw new SQLException("The url cannot be null", "08001");
    }

    SQLException reason = null;
    for(DriverInfo aDriver : registeredDrivers) {
        if(isDriverAllowed(aDriver.driver, callerCL)) {
            try {
                Connection con = aDriver.driver.connect(url, info);
                if (con != null) {
                    return (con);
                }
            } catch (SQLException ex) {
                if (reason == null) {
                    reason = ex;
                }
            }
        }
    }

    if (reason != null)    {
        throw reason;
    }
    throw new SQLException("No suitable driver found for "+ url, "08001");
}


callerCL은 클래스 로더 객체이며, caller 혹은 현재 스레드의 클래스 로더에 의해 만들어 진다. 이후 현 애플리케이션에서 등록된 Driver 목록인 registeredDrivers에서 aDriver를 하나씩 꺼내온다. 그리고 이 Driver가 isDriverAllowed() 에 의해 true가 나왔다면, 그 Driver로 Connection 객체를 얻어오고 이를 반환한다. isDriverAllowed() 는 caller에서 aDriver가 존재하는지 확인하는 역할을 한다.


JDBC 프레임워크의 장점

JDBC 프레임워크의 요점은 Driver, Connection 인터페이스와 그 실제 인터페이스를 구현하는 구현체 클래스가 완전히 분리되어 제공된다는 것이다. 인터페이스를 사용하여 틀을 만들어 놓고, 그 틀에 맞춰서 각각 구현 클래스를 만들면 되니 매우 유연하다는 장점이 있다.


그래서 다른 DBMS가 나와도 그 벤더사는 Driver와 Connection 인터페이스를 구현하여 제공하면, Java를 사용하는 개발자가 다른 DBMS 드라이버와 동일한 API를 사용할 수 있게 된다.


정적 팩터리 메서드의 단점

상속을 할 때 public or protected 생성자가 필요하므로 정적 팩터리 메서드만 제공하면 하위 클래스를 만들 수 없다.


그러나 이러한 제약은 상속보다는 컴포지션을 유도하고, 불변 타입을 만들기 위해 이 제약을 지켜야한다는 점에서 오히려 장점이 될 수도 있다.


정적 팩터리 메서드는 프로그래머가 찾기 어렵다.

생성자처럼 API 설명에 명확히 드러나지 않기 때문에, 개발자는 API 문서를 잘 써 놓고 메서드 이름도 널리 알려진 규약을 따라 짓는 식으로 문제를 완화해야 한다.


정적 팩터리 메서드 명명 방식

  • from
    • 매개변수를 하나 받아서 해당 타입의 인스턴스를 반환
    • Date date = Date.from(instant);
  • of
    • 여러 매개변수를 받아 적합한 타입의 인스턴스를 반환
    • Set<Rank> faceCards = EnumSet.of(JACK, QUEEN, KING);
  • valueOf
    • from과 of의 더 자세한 버전
    • BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);
  • instance 혹은 getInstance
    • 매개변수로 명시한 인스턴스를 반환하지만, 같은 인스턴스임을 보장하지는 않는다.
    • StackWalker luke = StackWalker.getInstance(options);
  • create 혹은 newInstance
    • instance 혹은 getInstance와 같지만, 매번 새로운 인스턴스를 생성해 반환함을 보장한다.
    • Object newArray = Array.newInstance(classObject, arraylen);
  • getType
    • getInstance와 같으나, 생성할 클래스가 아닌 다른 클래스에 팩토리 메소드를 정의할 때 쓴다.
    • FileStore fs = Files.getFileStore(path);
  • newType
    • newInstance와 같으나, 생성할 클래스가 아닌 다른 클래스에 팩토리 메소드를 정의할 때 쓴다.
    • BufferedReader br = Files.newBufferedReader(path);
  • type
    • getType과 newType의 간결한 버전
    • List<Complaint> litany = Collections.list(legacyLitany);


정리

정적 팩터리 메서드와 public 생성자는 각자의 쓰임새가 있으니 적절하게 사용하자.


출처

제이온
제이온
제이온
제이온
[이펙티브 자바] 아이템 2. 생성자에 매개변수가 많다면 빌더를 고려하라 매개변수가 많은 클래스를 생성할 때 빌더 패턴을 사용하면 코드 가독성을 높이고, 안정적인 객체 생성을 보장할 수 있습니다. 빌더 패턴은 필수 매개변수를 이용하여 빌더 객체를 생성하고, setter 메서드를 통해 선택 매개변수를 설정하며, build() 메서드를 호출하여 최종 객체를 얻는 방식입니다. 빌더 패턴은 클라이언트 입장에서 코드 작성 및 읽기가 용이하며, 계층적으로 설계된 클래스와 잘 어울립니다.

2024년 4월 27일

[Java] Reflection 개념 및 사용 방법 리플렉션은 자바 프로그램 실행 중에 클래스 정보에 접근하여 클래스를 조작할 수 있도록 지원하는 API입니다. 런타임 시점에서 클래스를 생성하고 필드와 메소드에 접근할 수 있지만, 캡슐화를 저해하고 성능 저하를 유발할 수 있으므로 신중하게 사용해야 합니다. Spring 프레임워크와 같이 런타임 시 객체를 동적으로 생성하고 관리해야 하는 상황에서 유용하게 활용됩니다.

2024년 4월 25일

[이펙티브 자바] 아이템 5. 자원을 명시하지 말고 의존 객체 주입을 사용하라 클래스가 내부적으로 하나 이상의 자원에 의존하는 경우, 싱글턴과 정적 유틸리티 클래스 대신 의존 객체 주입을 사용하는 것이 좋습니다. 의존 객체 주입을 통해 클래스의 유연성, 재사용성, 테스트 용이성을 향상시킬 수 있습니다.

2024년 4월 28일

[비전공, 개발자로 살아남기] 14. 신입 개발자 자주 묻는 기술면접 내용 요약 신입 개발자 면접에서 자주 나오는 기술 질문과 답변을 정리했습니다. 메모리 영역, 자료구조, 데이터베이스, 프로그래밍 패러다임, 페이지 교체 알고리즘, 프로세스와 스레드, OSI 7 계층, TCP와 UDP 등 다양한 주제를 다룹니다.
투잡뛰는 개발 노동자
투잡뛰는 개발 노동자
투잡뛰는 개발 노동자
투잡뛰는 개발 노동자

2024년 4월 3일

데이터 라벨링이란? 종류, 장점, 단점 데이터 라벨링은 컴퓨터가 데이터를 이해하고 활용할 수 있도록 데이터에 태그를 붙이는 과정입니다. 개와 고양이를 구별하는 작업을 예로 들면, 컴퓨터가 개와 고양이 사진을 구분하도록 각 사진에 "개" 또는 "고양이"라는 태그를 붙여주는 것입니다. 데이터 라벨링은 사물 인식, 텍스트 분류, 감정 분석, 음성 인식 등 다양한 분야에서 활용됩니다.
세상 모든 정보
세상 모든 정보
세상 모든 정보
세상 모든 정보

2024년 3월 29일

웹사이트를 반응형으로 기획하지 않아도 되는 이유 모바일 환경에 최적화된 웹사이트를 쉽게 만들 수 있는 리틀리 웹 빌더를 소개합니다. 데스크톱, 태블릿, 모바일 환경에 맞춰 별도로 기획할 필요 없이 단일 환경에서 최적화된 반응형 웹사이트를 구축하세요. 다양한 SNS 채널과의 연동, 간소화된 콘텐츠 관리, 효과적인 디자인 도구, 템플릿 등을 제공합니다.
리틀리 블로그|올인원 프로필 링크
리틀리 블로그|올인원 프로필 링크
리틀리 블로그|올인원 프로필 링크
리틀리 블로그|올인원 프로필 링크
리틀리 블로그|올인원 프로필 링크

2024년 5월 29일

[Javascript] Object의 구조 (V8) JavaScript에서 Object는 내부적으로 해시테이블과 유사한 방식으로 동작하지만, V8 엔진에서는 Hidden class를 이용하여 Fast 모드와 Dictionary 모드로 변환되어 성능을 최적화합니다. Hidden class는 객체의 구조를 정의하고 Fast 모드에서는 빠른 속도를 제공하지만, 키 추가 등의 변화가 발생하면 Dictionary 모드로 전환되어 해시테이블처럼 동작하며 성능이 저하될 수 있습니다.
곽경직
곽경직
곽경직
곽경직
곽경직

2024년 3월 18일

논리적 데이터 모델링 논리적 데이터 모델링은 개념적 데이터 모델링의 ERD를 관계형 데이터베이스 패러다임에 맞춰 변환하는 기계적인 과정입니다. 맵핑 룰을 기준으로 사각형 엔티티는 테이블로, 원형 속성은 컬럼으로, 관계는 PK 또는 FK로 변환됩니다. 1:1, 1:N, N:M 관계를 처리하는 방법과 제 1, 2, 3 정규화를 통해 테이블을 정제하는 과정을 설명합니다.
제이의 블로그
제이의 블로그
제이의 블로그
제이의 블로그
제이의 블로그

2024년 4월 9일

Rust가 동시성 버그를 방지하는 방법 Rust는 강력한 타입 시스템을 통해 동시성 프로그래밍에서 발생하는 일반적인 버그를 컴파일 타임에 감지하여 안전성을 높입니다. 특히, 스레드에 값을 전달할 때 move 클로저를 사용하여 값을 이동시켜야 하고, 여러 스레드에서 공유되는 변수는 Arc와 Mutex와 같은 내부 가변성 패턴을 활용하여 안전하게 관리할 수 있습니다.
곽경직
곽경직
곽경직
곽경직
곽경직

2024년 3월 28일