이펙티브 자바 완벽 공략 1부
아이템 1. 생성자 대신 정적 팩터리 메서드를 고려하라.
카테고리 : 이펙티브 자바 완벽 공략 1부
아이템 1. 생성자 대신 정적 팩터리 메서드를 고려하라.
핵심 정리
- 장점
- 이름을 가질 수 있다. (동일한 시그니처의 생성자를 두개 가질 수 없다.)
- 추상 팩토리 패턴, 팩토리 메서드 패턴이랑은 무관하다. 그냥 정적 팩토리 메서드이다
- 표현을 더 잘할 수 있다
- 호출될 때마다 인스턴트를 새로 생성하지 않아도 된다. (Boolean.valueOf)
- 여러개 인스턴스 생성을 제한 할때
- 생성자로는 인스턴스의 생성을 컨트롤할 수가 없다 생성자를 퍼플릭하게 제공하는 순간 부터 그 클래스들의 인스턴스는 기본 생성자가 있는 경우에는 객체 생성을 얼마든지 사용하는 쪽에서 원하는 대로 생성 할 수가 있다
- 매개변수에 따라 각기 다른 인스턴스를 리턴하는 기능도 정적 팩토리 메소드를 사용하면 가능하다
- 플라이웨이트 패턴은 우리가 자주 사용하는 인스턴스들, 가령 폰트라고 생각하면 폰트를 변경한다고 해서 매번 그 새로운 폰트를 만드는 게 아니라 미리 만들어져 있는 폰트 중에서 가져오는 것 그래서 플라이웨이트 패턴은 어떤 자주 사용하는 값들을 미리 캐싱해서 넣어놓고 거기서 꺼내다 쓰는 형식의 디자인 패턴이다
- 정적 팩토리 메서드에서 Flyweight 패턴이 언급이 되었냐면 인스턴스를 통제하는 방법이기 때문에 미리 자주 사용하는 객체들을 만들어 놓고 필요로 하는 인스턴스 꺼내다가 쓸 수 있게 해주는 Flyweight 패턴과 통용되는 개념이 있기 때문에 Flyweight 패턴이 언급된 것이다
- 반환 타입의 하위 타입 객체를 반환할 수 있는 능력이 있다. (인터페이스 기반 프레임워크, 인터페이스에 정적 메소드)
- 굉장히 유연함이 생긴다 고정적이지가 않다
- 입력 매개변수가 따라 매번 다른 클래스의 객체를 반환할 수 있다. (EnumSet)
- 정적 팩터리 메서드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도 된다. (서비스 제공자 프레임워크)
- 서비스 로더를 사용, 서비스 제공자의 구현체라고 볼 수 있다
- resources 밑에다가 meta-inf라는 디렉토리를 만들고 그 디렉토리 밑에다가 또 services라는 디렉토리를 만들어서 제공할 구현체의 인터페이스에 풀패키지 경로를 적어준다
- 서비스에 의존적이지가 않게 만들 수 있다
- 서비스 로더 이런 패턴은 조금 더 전문화된 스프링이나 Dependency Injection Framework을 사용할 수가 있게 되는 것이다
- 이름을 가질 수 있다. (동일한 시그니처의 생성자를 두개 가질 수 없다.)
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Optional;
import java.util.ServiceLoader;
public class HelloServiceFactory {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
ServiceLoader<HelloService> loader = ServiceLoader.load(HelloService.class);
Optional<HelloService> helloServiceOptional = loader.findFirst();
helloServiceOptional.ifPresent(h -> {
System.out.println(h.hello());
});
HelloService helloService = new ChineseHelloService();
System.out.println(helloService.hello());
// Class<?> aClass = Class.forName("me.whiteship.hello.ChineseHelloService");
// Constructor<?> constructor = aClass.getConstructor();
// HelloService helloService = (HelloService) constructor.newInstance();
// System.out.println(helloService.hello());
}
}
- 단점
- 상속을 하려면 public이나 protected 생성하기 필요하니 정적 팩터리 메서드만 제공하면 하위 클래스를 만들 수 없다.
- 정적 팩터리 메서드는 프로그래머가 찾기 어렵다.
- 메서드가 많아진다면 인스턴스를 생성해주는 용도의 메서드를 찾기가 어렵게 된다
- 그래서 책에서는 이제 흔히들 많이 사용하는 네이밍 패턴을 제안하고 있다
- of라는 이름을 사용했을 때는 어떤 매개변수들을 받아서 매개 변수들을 받아서 이제 뭔가를 만들 경우에는 오브를 쓰자 of 또는 valueOf
- 우리가 어떤 미리 만들어져 있는 인스턴스를 가져오는 경우에는 캐리 인스턴스 그리고 인스턴스를 쓰자
- 기존에 만들어 놓은 게 아니라 매번 이 팩토리 안에서 새로 만들 거라면 뉴 인스턴스 이렇게 뭐 Create, 이런 새로 뭔가 만든다는 느낌이 들어가 있는 단어들을 사용하자
- 본인이 아니라 정적 팩토리를 가지고 있는 클래스가 자기 자신의 인스턴스를 만들어 주는 게 아니라 바깥에서 서비스를 만들어 주는 경우라면 Get을 붙여서 뒤에다가 만들어 주는 이름을 명시하자
- 명시하는 그러한 네이밍 패턴을 사용해서 우리가 메소드 서머리에서 손쉽게 좀 찾아볼 수 있게끔 그나마 우리가 스태틱 메소드랑 컨크리트 메소드를 구분해 줄 수 있는 이런 탭이 있으니까 스태틱 메소드 탭에서 좀 더 찾아볼 수 있다
- 가장 좋은 방법은 문서화를 하는 것이다
완벽 공략
- p9, 열거 타입은 인스턴트가 하나만 만들어짐을 보장한다
- p9, 같은 객체가 자주 요청되는 상황이라면 플라이웨이트 패턴을 사용할 수 있다.
- p10, 자바 8부터는 인터페이스가 정적 메서드를 가질 수 없다는 제한이 풀렸기 때문에 인스턴스화 불가 동반 클래스를 둘 이유가 별로 없다.
- p11, 서비스 제공자 프레임워크를 만드는 근간이 된다.
- p12, 서비스 제공자 인터페이스가 없다면 각 구현체를 인스턴스로 만들 때 리플렉션을 사용해야 한다.
- p12, 브리지 패턴
- p12, 의존 객체 주입 프레임워크
아이템 1. 완벽 공략 1 - 열거 타입
- 상수 목록을 담을 수 있는 데이터 타입.
- 특정한 변수가 가질 수 있는 값을 제한할 수 있다.
타입-세이프티 (Type-Safety)
를 보장할 수 있다 싱글톤 패턴
을 구현할 때 사용하기도 한다.
특정 enum 타입이 가질 수 있는 모든 값을 순회하며 출력
Arrays.stream(OrderStatus.values()).forEach(System.out::println);
enum은 자바의 클래스처럼 생성자, 메소드, 필드를 가질 수 있다
public enum OrderStatus {
PREPARING(0), SHIPPED(1), DELIVERING(2), DELIVERED(3);
private int number;
OrderStatus(int number) {
this.number = number;
}
}
enum의 값은 == 연산자로 동일성을 비교
- equal 연산자와 == 중에서 == 연산자 쓰는 것을 권장한다
- ==이 어차피 jvm 레벨에서 딱 오로지 하나의 인스턴스만 있음을 보장하기 때문에 equals를 쓸 필요가 없다
- equals는 null을 가지고 있는 데다가 만약에 equals를 쓰면 null포인트 exception이 날 수가 있는데 그 부분을 피할 수 있다
enum을 key로 사용하는 Map을 정의, enum을 담고 있는 Set
- 일반적인 셋이나 맵으로 당연히 셋 또는 맵 해시셋 그리고 해시맵을 쓰면 틀린거다
- 이늄맵,이늄셋을 쓰는게 훨씬 더 효율적이다 훨씬 더 빨르다
- hashmap 은 key를 bucket에 저장하고각 bucket이 linked list를 참조 하고 있다. (linkedlist에는 hash(key)가 같은 element가 들어감) 그런데 enummap 의 경우 key로 사용할 값이 제한되어 있으므로, 그 갯수만큼 길이를 가진 array를 선언하고. 해당 index에 값을 넣으면 된다.
- hashset은 hashmap 과 같은데 map의 value가 있다 없다를 표현하는 지시자 같은 값이 들어간다. enumset은 값이 있다 없다만 표시하면 되니까 enummap 처럼 array로 구현하지 않고 10101011 같은 bitvector로 구현이 가능하다.
- EnumSet, EnumMap 은 배열을 지정하고 상수 index를 통해 Enum 인스턴스를 직접 매핑한다. 이런 구조 때문에 값을 넣고 빼는 것이 시간 측면에서 매우 효율적이다.
- 마찬가지로 배열을 지정하여 데이터를 저장하기 때문에 메모리가 절약된다. 그리고 Enum 상수의 개수는 결정되어있기 때문에 HashMap 과 HashSet과 달리 배열이 부족하여 늘려줘야 하는 일이 없다.
- null 값을 처리하지 않아도 된다. HashSet 과 HashMap의 값, key, value 는 null 이 될 수 있다. 하지만 Enum 상수는 절대 null 이 될 수 없기 때문에 null 값을 처리하는 작업을 하지 않아도 된다.
static EnumMap<OrderStatus, String> korMap = new EnumMap<>(OrderStatus.class) {
{
put(PREPARING, "준비중");
put(SHIPPED, "출고완료");
put(DELIVERING, "배송중");
put(DELIVERED, "배송완료");
}
};
public static void main(String[] args) {
OrderStatus.korMap.forEach((key, value) -> System.out.println(key + " " + value));
}
- EnumMap은 내부적으로 배열로 구현되어 있어, 매우 빠른 성능을 제공
- 특히 다른 맵보다 메모리 사용량이 적다.
static EnumSet<OrderStatus> allOrderStatus = EnumSet.allOf(OrderStatus.class);
public static void main(String[] args) {
allOrderStatus.forEach(System.out::println);
}
- EnumSet은 내부적으로 비트 벡터로 구현되어 있어 매우 효율적이고 작은 메모리를 사용.
- 비트연산을 통해 집합 연산을 매우 빠르게 수행
아이템 1. 완벽 공략 2 - 플라이웨이트 패턴
Flyweight (가벼운 체급)
- 플라이웨이트 패턴은 객체를 재사용하는 방법 중 하나이다
- 객체를 가볍게 만들어 메모리 사용을 줄이는 패턴
- 자주 변하는 속성(또는 외적인 속성, extrinsit)과 변하지 않는 속성(또는 내적인 속성, intrinsit)을 분리하고 재사용하여 메모리 사용을 줄일 수 있다.
- 변경되지 않는 것들을 따로 어딘가에 모아 놓고 그 Flyweight Factory라는 Factory에서 꺼내다가 쓰는 방법
- https://ysiksik.github.io/gof-design-pattern/2022-10-06-Structural-Patterns/#%ED%94%8C%EB%9D%BC%EC%9D%B4%EC%9B%A8%EC%9D%B4%ED%8A%B8-flyweight-%ED%8C%A8%ED%84%B4
아이템 1. 완벽 공략 3 - 인터페이스와 정적 메서드
자바 8과 9에서 주요 인터페이스의 변화
- 기본 메소드 (default method)와 정적 메소드를 가질 수 있다.
- 기본 메소드
- 인터페이스에서 메소드 선언 뿐 아니라, 기본적인 구현체까지 제공할 수 있다.
- 기존의 인터페이스를 구현하는 클래스에 새로운 기능을 추가할 수 있다.
- 정의를 하려면 default라는 키워드를 줘야 한다
- 정적 메소드
- 자바 9부터 private static 메소드도 가질 수 있다.
- 단, private 필드는 아직도 선언할 수 없다.
- 정적 메소드는 인터페이스에서 접근 지시자가 생략돼있다 그러면 기본적으로 모두 다 public이다
아이템 1. 완벽 공략 4 - 서비스 제공자 프레임워크
확장 가능한 애플리케이션을 만드는 방법
- 주요 구성 요소
- 서비스 제공자 인터페이스 (SPI)와 서비스 제공자 (서비스 구현체)
- 서비스 제공자 등록 API (서비스 인터페이스의 구현체를 등록하는 방법)
- 서비스 접근 API (서비스의 클라이언트가 서비스 인터페이스의 인스턴스를 가져올 때 사용하는 API)
- 다양한 변형
아이템 1. 완벽 공략 5 - 리플렉션
reflection
- 클래스로더를 통해 읽어온 클래스 정보(거울에 반사”된 정보)를 사용하는 기술
- 리플렉션을 사용해 클래스를 읽어오거나, 인스턴스를 만들거나, 메소드를 실행하거나, 필드의 값을 가져오거나 변경하는 것이 가능하다
- 클래스의 정보는 클래스 로더가 jvm에 들어있는 클래스 로더가 읽어드린다 모든 클래스들은 다 jvm이 안에 들어있는 클래스 로더를 통해서 클래스 정보를 읽어와서 해당하는 정보를 메모리에 어딘가에 둔다 그 정보가 곧 그렇게 읽어드린 클래스의 정보가 곧 거울에 비친 모습이라고 보면 된다. 그 안에는 어떤 컨스트럭터, 어떤 메소드, 어떤 필드들이 있는지 아주 자세하게 모든 정보들이, 거의 대부분의 정보들이 담겨져 있다.
- 언제 사용할까?
- 특정 애노테이션이 붙어있는 필드 또는 메소드 읽어오기 (JUnit, Spring)
- 특정 이름 패턴에 해당하는 메소드 목록 가져와 호출하기 (getter, setter)
- https://docs.oracle.com/javase/tutorial/reflect/
- https://ysiksik.github.io/the-java-code-manipulation/2022-08-29-reflection/