
자바에서 객체를 생성하는 일반적인 방법은 new 키워드를 사용하는 생성자다. 자바에서는 생성자 외에도 정적(static) 팩터리 메서드를 활용하여 객체를 생성할 수 있다. 여기서 말하는 정적 팩터리 메서드는 GoF의 Factory Method 패턴과는 다르며, 단순히 객체를 생성하고 반환하는 static 메서드를 말하는 것이다.
public static Boolean valueOf(boolean b) {
return b ? Boolean.TRUE : Boolean.FALSE;
}
위의 예제처럼 Boolean.valueOf()는 새로운 객체를 생성하는 것이 아니고 미리 생성된 Boolean.TRUE나 Boolean.FALSE를 반환하는 것 이다. 이렇게 하면 불필요한 객체 생성을 방지할 수 있다.
생성자는 클래스 이름과 동일해야 하지만, 정적 팩터리 메서드는 이름을 자유롭게 정할 수 있어 객체의 생성 방식을 명확하게 나타낼 수 있다.
public static Car createElectricCar() {
return new Car("Electric");
}
public static Car createGasolineCar() {
return new Car("Gasoline");
}
위처럼 생성자만 사용하면 new Car("Electric"), new Car("Gasoline")처럼 생성하는 객체의 특성을 설명하기 어렵다.
하지만 정적 팩터리 메서드를 사용하면 메서드 명을 통해서 객체의 특성을 확인할 수 있게 되어서 가독성이 훨씬 좋아진다.
정적 팩터리 메서드는 같은 객체를 여러 번 반환할 수 있어 객체 생성을 통제할 수 있다.
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return INSTANCE;
}
}
위 코드처럼 싱글턴 패턴(Singleton Pattern) 을 활용하면,
정적 팩터리 메서드를 통해 항상 동일한 인스턴스를 반환할 수 있다.
또한, Boolean.valueOf(true), Integer.valueOf(10) 같은 메서드도 객체를 캐싱해서 매번 새로운 객체를 생성하지 않고 이미 존재하는 객체를 재사용한다.
정적 팩터리 메서드는 반환 타입을 인터페이스나 추상 클래스로 지정할 수 있어서 실제 반환 객체는 하위 클래스일 수도 있다.
interface Animal {
void speak();
}
class Dog implements Animal {
public void speak() {
System.out.println("멍멍!멍멍!");
}
}
class Cat implements Animal {
public void speak() {
System.out.println("야옹~");
}
}
class AnimalFactory {
public static Animal createAnimal(String type) {
if ("dog".equals(type)) return new Dog();
else return new Cat();
}
}
위처럼 AnimalFactory.createAnimal("dog")를 호출하면 Dog 객체가 반환되지만 반환 타입은 Animal이므로 구체적인 구현체를 숨길 수 있게 된다. 이렇게 하면 API를 변경하지 않고도 내부 구현을 유연하게 변경할 수 있다.
입력값에 따라 다른 클래스의 객체를 반환하는 것도 가능하다.
장점 3번에서 작성한 예제에서 처럼 createAnimal("dog")는 Dog 객체를 반환하고, 매개변수에 "cat"을 작성하면 Cat 객체를 반환한다.
클라이언트는 정적 팩터리가 건네주는 객체가 어느 클래스의 인스턴스인지 알 수 없고 알 필요가 없다. 그저 하위 클래스이기만 하면 된다.
JDBC에서 DriverManager.getConnection()이 대표적인 예시ㅓ이다.
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb", "user", "password");
getConnection()의 반환 타입은 Connection 인터페이스이다. 하지만 실제 반환되는 객체는 MySQL, Oracle 등 데이터베이스 종류에 따라 다를 수 있다. - 즉, 정적 팩터리 메서드는 런타임에 실제 클래스를 결정할 수도 있다.
이러한 구조를 서비스 제공자 프레임워크라고 하며, 핵심 요소는 다음과 같다.
이 방식 덕분에 클라이언트는 특정 DB 구현체에 의존하지 않고도 Connection 인터페이스만으로 데이터베이스 작업을 수행할 수 있는 것이다.
정적 팩터리 메서드만 제공하게되면 public 또는 protected 생성자가 없기 때문에 하위 클래스를 만들 수 없다.
public class Parent {
private Parent() {}
public static Parent getInstance() {
return new Parent();
}
}
이 경우 Parent를 상속받아서 Child 클래스를 만들 수 없다.
생성자는 new 키워드로 쉽게 검색할 수 있지만 정적 팩터리 메서드는 이름이 제각각이라서 찾기가 어렵다.
예를 들어, 정적 팩터리 메서드의 이름은 of(), from(), valueOf(), getInstance(), newInstance(), getType() 등 다양할 수 있다.
LocalDate date = LocalDate.of(2025, 2, 17); // of()
Integer num = Integer.valueOf(Integer.MAX_VALUE); // valueOf()
따라서 코드 스타일을 일관되게 유지하는 것이 중요하다.
정적 팩터리 메서드는 생성자보다 많은 장점을 제공하지만 단점도 몇가지 존재한다.
정적 팩터리 메서드는 객체 생성을 통제하고, 불필요한 객체 생성을 줄이며, API 유연성을 높이는 데 큰 장점을 가진다.
그러나, 이름이 다양해서 찾기 어렵고 하위 클래스를 만들기 어렵다는 단점을 고려해야 한다.
하지만 정적 팩터리를 사용하는 게 유리한 경우가 더 많기 때문에 public 생성자를 남발하면 안된다고한다.