코드에 BigInteger 객체를 이용하게 되었다. 오랜만에 사용하는 클래스는 한번씩 내부 코드를 보는 습관이 있는데
다양한 생성자들이 있었고
BigInteger bi1 = new BigInteger("12345678901234567890");
BigInteger bi4 = BigInteger.valueOf(123456789L);
BigInteger bi8 = new BigInteger(128, new Random());
byte[] byteArray = {1, 2, 3, 4, 5};
BigInteger bi5 = new BigInteger(byteArray);
그러다 'BigInteger.valueOf' 메서드가 눈에 띄었다.
import java.math.BigInteger;
public class BigIntegerTest {
public static void main(String[] args) {
BigInteger prime = BigInteger.valueOf(10);
BigInteger prime2 = BigInteger.valueOf(10);
BigInteger prime3 = new BigInteger("10");
BigInteger prime4 = new BigInteger("10");
System.out.println(prime.intValue() == prime2.intValue()); //true --- 1
System.out.println(prime == prime2); //true --- 2
System.out.println(prime3.intValue() == prime4.intValue()); //true --- 3
System.out.println(prime3 == prime4); //false --- 4
}
}
첫 번째와 세 번째 비교는 값이 같으므로 true를 반환하는 것이 직관적으로 이해된다.
하지만 prime과 prime2는 같은 객체로 판단되어 true를 반환하는 반면,
prime3과 prime4는 서로 다른 객체로 판단되어 false를 반환한다.
그 이유를 알기 위해 BigInteger.valueOf() 메서드 좀더 자세히 살펴볼 필요가 있었다.
/**
* Returns a BigInteger whose value is equal to that of the
* specified {@code long}.
*
* @apiNote This static factory method is provided in preference
* to a ({@code long}) constructor because it allows for reuse of
* frequently used BigIntegers.
*
* @param val value of the BigInteger to return.
* @return a BigInteger with the specified value.
*/
public static BigInteger valueOf(long val) {
// If -MAX_CONSTANT < val < MAX_CONSTANT, return stashed constant
if (val == 0)
return ZERO;
if (val > 0 && val <= MAX_CONSTANT)
return posConst[(int) val];
else if (val < 0 && val >= -MAX_CONSTANT)
return negConst[(int) -val];
return new BigInteger(val);
}
valueOf 메서드를 보면 알 수 있듯이, val의 값이 일정 범위 내에 있는 경우 posConst 혹은 negConst의 n번째 요소를 불러오게 되어있다. 그리고 해당 배열들을 살펴보면
// Constants
/**
* Initialize static constant array when class is loaded.
*/
private static final int MAX_CONSTANT = 16;
private static BigInteger posConst[] = new BigInteger[MAX_CONSTANT+1];
private static BigInteger negConst[] = new BigInteger[MAX_CONSTANT+1];
BigInteger객체를 요소로 갖는 static 배열이기 때문에 초기 생성된 배열 객체 요소를 가지고 오므로
같은 val에 의해 같은 객체가 되어 true를 반환한 것이었다.
이렇게 특정 Static Method를 통해 간접적으로 생성자를 호출하여 객체를 생성하는 방식을 '정적 팩토리 메서드 패턴' 이라 한다고 한다.
이번 기회로 '이펙티브 자바'라는 책을 알게 되었고, 운이 좋게 위 내용들은 이 책의 첫 장이었다. 책에 있는 내용들을 추가로 정리하며 이 글을 마무리하려고 한다.
정적 팩토리 메서드의 장점
- 다양한 이름을 가질 수 있다
생성자에 비해 정적 팩토리 메서드는 반환될 객체의 특성을 잘 나타내는 이름을 가질 수 있다. 생성자에 넘기는 매개변수와 생성자 자체만으로는 반환될 객체의 특성을 제대로 설명하지 못할 수 있기 때문이다. 이로 인해 코드의 가독성이 높아지고, 사용자가 메서드의 용도를 쉽게 이해할 수 있다.
.//true 혹은 false값을 가지는 Boolean객체를 반환한다 //메서드 이름이 객체의 성격을 명확하개 설명해준다 public class Boolean { public static Boolean valueOf(boolean b) { return b ? Boolean.TRUE : Boolean.FALSE; } }
- 호출될 때마다 인스턴스를 새로 생성하지 않아도 된다 (새로운 객체를 생성하지 않아도 된다)
필요에 따라 미리 생성된 인스턴스를 반환하거나, 또는 새로 생성한 인스턴스를 캐싱하여 재활용하는 식으로 불필요한 객체 생성을 피할 수 있다. 이로 인해 메모라 사용 효율을 높이고 성능을 개선할 수 있다
.//zero 메서드는 항상 같은 BigInteger 인스턴스를 반환한다 //이 방법으로, BigInteger.ZERO를 필요할 때마다 재사용할 수 있다 public class BigInteger { private static final BigInteger ZERO = new BigInteger(new byte[]{0}); public static BigInteger zero() { return ZERO; } }
- 반환 타입의 하위 타입 객체를 반환할 수 있다
선언된 반환 타입의 하위 타입의 객체를 반환할 수 있어 다양한 반환 타입에 유연하게 대응할 수 있다.
.//Animal 인터페이스의 of 메서드는 type에 따라 Dog 또는 Cat 인스턴스를 반환한다 public interface Animal { static Animal of(String type) { if ("dog".equals(type)) { return new Dog(); } else if ("cat".equals(type)) { return new Cat(); } throw new IllegalArgumentException("Unknown animal type"); } } class Dog implements Animal { ... } class Cat implements Animal { ... }
- 입력 매개변수에 따라 다른 클래스의 객체를 반환할 수 있다
입력 매개변수에 기반하여 각기 다른 클래스의 인스턴스를 반환할 수 있다.
.//ShapeFactory의 getShape 메서드는 주어진 shapeType에 따라 //Circle 또는 Rectangle 인스턴스를 반환한다 public class ShapeFactory { public static Shape getShape(String shapeType) { if ("CIRCLE".equalsIgnoreCase(shapeType)) { return new Circle(); } else if ("RECTANGLE".equalsIgnoreCase(shapeType)) { return new Rectangle(); } return null; } } interface Shape { ... } class Circle implements Shape { ... } class Rectangle implements Shape { ... }
- 정적 팩토리 메서드를 작성하는 시점에는 반환될 객체의 클래스가 존재하지 않아도 된다
구현 클래스를 런타임에 결정할 수 있다
//ServiceLoader.load 메서드는 주어진 서비스 인터페이스에 대한 구현체를 런타임에 찾아서 반환한다 public class ServiceLoader<S> { // 서비스 제공자 프레임워크의 일부로, S 타입의 서비스 인스턴스를 반환한다. public static <S> S load(Class<S> service) { // 런타임에 서비스 구현을 찾아서 반환하는 로직 } }
마지막으로 정적 팩토리 메서드에 사용하는 몇 가지 명명 규칙들을 알아보자.
이러한 명명 규칙들은 해당 메서드의 의도나 특성을 명확하게 전달한다.
- valueOf
설명: 매개변수 하나를 받아 해당 타입의 인스턴스를 반환한다. 주로, 타입 변환에 사용된다.
예시: Boolean.valueOf(String) - of
설명: valueOf와 비슷하지만, 더 간결한 이름을 가진다. 매개변수를 받아 적절한 타입의 인스턴스를 반환한다.
예시: EnumSet.of(E first, E... rest) - getInstance
설명: 매개변수에 기반하여, 매번 같은 인스턴스를 반환하지 않을 수도 있는 메서드다. 인스턴스를 새로 생성하거나 캐싱된 인스턴스를 반환할 수 있다.
예시: Calendar.getInstance() - create
설명: getInstance와 비슷하지만, 매번 호출할 때마다 새로운 인스턴스를 생성하여 반환한다는 점에서 차이가 있다.
예시: ObjectInputStream.create() - getSingleton
설명: 항상 같은 인스턴스를 반환하는 메서드. 싱글톤 패턴을 구현할 때 사용된다.
예시: Runtime.getSingleton() - newInstance
설명 : create와 비슷하지만, 매번 새로운 인스턴스를 생성하여 반환한다. 주로 리플렉션과 같이 동적으로 인스턴스를 생성할 때 사용된다.
예시: Array.newInstance(Class componentType, int length) - getType
설명: getInstance와 비슷하지만, 생성되는 인스턴스의 타입에 더 집중하는 경우 사용된다. 반환 타입이 매개변수에 의해 결정된다.
예시: Files.getType(Path path) - newType
설명: newInstance와 비슷하지만, 생성되는 인스턴스의 타입에 더 집중하는 경우 사용된다. 새로운 타입의 인스턴스를 생성하여 반환한다.
예시: Files.newBufferedReader(path) - type
설명 : getType과 newType의 간결한 버전.
예시 : Collections.list(legacyLitany) - from
설명 : 매개변수를 하나 받아서 해당 타입의 인스턴스를 반환하는 형변환 메서드.
예시 : Date.from(instant)
'Java' 카테고리의 다른 글
Singleton (0) | 2024.03.09 |
---|---|
불변 객체(Immutable Object) (0) | 2024.03.04 |
Builder Pattern (빌더 패턴) (0) | 2024.03.02 |
람다 표현식 - 2. 자바 함수형 인터페이스 (0) | 2023.12.05 |
람다 표현식 - 1. 람다 표현식 기본 개념 (0) | 2023.11.26 |