-
아이템1. 생성자 대신 정적 팩터리 메서드를 고려하라Java/이펙티브 자바 2022. 10. 28. 10:47
장점
- 이름을 가질 수 있다. ( 동일한 시그니처의 생성자를 두개 가질 수 없다.)
- 호출될 떄마다 인스턴트를 새로 생성하지 않아도 된다. (Boolean.valueOf)
- 반환 타입의 하위 타입 객체를 반환할 수 있는 능력이 있다. ( 인터페이스 기반 프레임워크, 인터페이스에 정적 메소드 )
- 입력 매개 변수가 따라 매번 다른 클래스의 객체를 반환할 수 있다. (EnumSet)
- 정적 팩터리 메서드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도 된다. ( 서비스 제공자 프레임워크 )
단점
- 상속을 하려면 public이나 protected 생성하기 필요하니 정적 팩토리 메서드만 제공
- 정적 팩터리 메서드는 프로그래머가 찾기 힘들다.
// 잘못된 예시 public class Order { private boolean prime; private boolean urgent; private Product product; public Order(Product product, boolean prime) { this.product = product; this.prime = prime; } public Order(Product product, boolean urgent) { this.product = product; this.urgent = urgent; } }
- 생성자의 시그니처가 파라미터의 타입까지 보기에 완전한 동일한 시그니처의 생성자는 존재할 수 없다.
- 생성자는 이름이 고정이다.
정적팩터리메서드 장점1 : 표현을 더 잘할 수 있다.
// 정적팩터리메서드 장점1 : 표현을 더 잘할 수 있다. public class Order { private boolean prime; private boolean urgent; private Product product; public static Order primeOrder(Product product){ Order order = new Order(); order.prime = true; order.product = product; return order; } public static Order urgentOrder(Product product){ Order order = new Order(); order.urgent = true; order.product = product; return order; } }
- 표현을 더 잘할 수 있다.
- 생성자 시그니처가 중복될 경우에 고려해보자.
정적팩터리메서드 장점2 : 인스턴스 컨트롤(싱글톤)
// 정적팩터리메서드 장점2 : 인스턴스 컨트롤(싱글톤) public class Settings { private boolean useAutoSteering; private boolean useABS; private Difficulty difficulty; private Settings() { } private static final Settings SETTINGS = new Settings(); public static Settings newInstance() { return SETTINGS; } }
- 생성자로는 인스턴스의 생성을 컨트롤 할 수 없다.
ㆍ생성자를 public하게 제공하는 순간부터 그 클래스는 원하는 만큼 생성하게 된다.
- ex) Boolean.valueOf();
public static Boolean valueOf(boolean b) { return (b ? TRUE : FALSE); } public static final Boolean TRUE = new Boolean(true); public static final Boolean FALSE = new Boolean(false);
ㆍ매개변수에 따라 각각 다른 인스턴스를 리턴하는 기능
- 플라이웨이트 패턴 ( Flyweight pattern )
ㆍ자주 사용하는 인스턴스 ( ex. font ) 를 캐싱해서 넣어두고 꺼내서 쓰는 패턴
ㆍ인스턴스를 통제하는 방법
정적팩터리메서드 장점3 : 인터페이스 타입을 사용할 수 있다.
// 정적팩터리메서드 장점3 : 인터페이스 타입을 사용할 수 있다. public class HelloServiceFactory { public static HelloService of(String lang){ if(lang.equals("ko")){ return new KoreanHelloService(); }else{ return new EnglishHelloService(); } } } // 인터페이스에 static 메서드 선언 가능 // 팩터리메서드에 굳이 따로 별도로 만들어서 정적팩터리메서드를 많이 만들지 않아도 된다. public interface HelloService { String Hello(); static HelloService of(String lang){ if(lang.equals("ko")){ return new KoreanHelloService(); } else { return new EnglishHelloService(); } } }
- 클래스의 하위 클래스를 리턴할 수 있다.
* class에서 아무것도 붙이지 않으면 패키지 private 레벨
* interface에서 아무것도 붙이지 않으면 public
정적팩터리메서드 장점4 : 구현체를 숨길 수 있다.
// 정적팩터리메서드 장점4 : 구현체를 숨길 수 있다 public static void main(String[] args) { HelloService ko = HelloServiceFactory.of("ko"); }
정적팩터리메서드 장점5 : 정적팩터리메서드를 작성하는 시점에 굳이 메서드가 없어도 된다. (구현체가 없어도 된다.)
// 정적팩터리메서드 장점5 : 정적팩터리메서드를 작성하는 시점에 굳이 메서드가 없어도 된다. // (구현체가 없어도 된다.) public static void main(String[] args) { // 자바가 기본으로 제공해주는 정적팩터리메서드 ServiceLoader<HelloService> loader = ServiceLoader.load(HelloService.class); // load가 등록되어있는 구현체를 다 가지고 온다. Optional<HelloService> helloServiceOptional = loader.findFirst(); helloServiceOptional.ifPresent(h -> { System.out.println(h.Hello()); }); }
public static void main(String[] args) { // ChineseHelloService는 라이브러리로 추가 HelloService helloService = new ChineseHelloService(); }
- 위 코드는 구현체에 의존적이지 않다. 하지만 아래 코드는 구현체에 의존적이다.
- 어떤 구현체가 올지모르지만 인터페이스 기반으로 코딩을 할 때 사용한다.
- ex) jdbc 드라이버 ( 어떤 드라이버든 호환되는 드라이버가 있다면 동작하도록 하는 패턴 )
ㆍ실질적으론 ServiceLoader가 사용되지는 않음.
정적팩터리메서드 단점1 : 상속하기 어렵다.
// 정적팩터리메서드 단점1 : 상속을 하기 어렵다. public class Settings { private boolean useAutoSteering; private boolean useABS; private Difficulty difficulty; private Settings() { // private 이므로 상속 X } private static final Settings SETTINGS = new Settings(); public static Settings newInstance() { return SETTINGS; } }
- 정적 메서드만 사용하도록 만들기 위해 기본 생성자를 private 처리 시 상속 허용 X
// 상속을 이용하지 않아도 Settings의 기능 사용가능 public class AdvancedSettings { Settings settings; }
// 정적팩터리메서드를 만들지만 생성자를 허용하는 경우 : List static <E> List<E> of() { return ImmutableCollections.emptyList(); } // 실질적으로 구현체가 ArrayList가 아닐 수 있다. static <E> List<E> of(E e1, E e2, E e3, E e4) { return new ImmutableCollections.ListN<>(e1, e2, e3, e4); }
정적팩터리메서드 단점2 : 생성자가 없고 정적팩토리만 있다면 javaDoc으로 확인 시 인스턴스 만드는 방법을 찾기 힘들다.
- 해결방안1 : naming 패턴을 제한
ㆍof : 매개변수를 받아서 생성
ㆍgetInstance : 이미만들어진 인스턴스 사용
ㆍnewInstance : 새로 생성
- 해결방안2 : 문서화를 해라.
p9, 열거 타입은 인스턴스가 하나만 만들어짐을 보장한다
p9, 같은 객체가 자주 요청되는 상황이라면 플라이웨이트 패턴을 사용할 수 있다.
p10, 자바8부터는 인터페이스가 정적 메서드를 가질 수 없다는 제한이 풀렸기 때문에 인스턴스화 불가 동반 클래스를 둘 이유가 없다.
p11, 서비스 제공자 프레임워크를 만드는 근간이 된다.
p12, 서비스 제공자 인터페이스가 없다면 각 구현체를 인스턴스로 만들 때 리플렉션을 사용해야 한다.
p12, 브리지 패턴
p12, 의존 객체 주입 프레임워크
완벽공략 1. 열거타입 ( Enumeration )
- 상수 목록을 담을 수 있는 데이터 타입
- 특정한 변수가 가질 수 있는 값을 제한할 수 있다. 타입-세이브티 ( Type-Safety )를 보장할 수 있다
ㆍex) int 로 type값을 정의한다면 type값이외 값이 들어갔을때 valid check를 해줘야한다.
- 싱글톤 패턴을 구현할 때 사용하기도 한다.
- 질문1) 특정 enum 타입이 가질 수 있는 모든 값을 순회하며 출력하라
public static void main(String[] args) { Arrays.stream(OrderStatus.values()).forEach(System.out::println); }
- 질문2) enum은 자바의 클래스처럼 생성자, 메소드, 필드를 가질 수 있는가?
public enum OrderStatus { PREPARING(0), SHIPPED(1), DELIVERING(2), DELIVERED(3); private int number; OrderStatus(int number) { this.number = number; } }
- 질문3) enum의 값은 ==연산자로 동일성을 비교할 수 있는가?
public static void main(String[] args) { Order order = new Order(); if(order.orderStatus == OrderStatus.DELIVERED){ // equals를 쓴다면 nullpointException이 발생 System.out.println("delivered"); } }
- 과제) enum을 key로 사용하는 Map을 정의하세요. 또는 enum을 담고있는 Set을 만들어 보세요.
ㆍ힌트 : EnumMap, EnumSet
완벽 공략2. 플라이웨이트 패턴 Flyweight (가벼운 체급)
- 객체를 가볍게 만들어 메모리 사용을 줄이는 패턴
- 자주 변하는 속성(또는 외적인 속성, extrinsit)과 변하지 않는 속성(또는 내적인 속성, intrinsit)을 분리하고 재사용하여 메모리 사용을 줄일 수 있다.
public class FontFactory { private Map<String, Font> cache = new HashMap<>(); public Font getFont(String font) { if(cache.containsKey(font)) { return cache.get(font); } else { String[] split = font.split(":"); Font newFont = new Font(split[0], Integer.parseInt(split[1])); cache.put(font, newFont); return newFont; } } }
- 정적팩터리메서드를 이용한다면 하나의 인스턴스를 공유하므로 객체의 메모리 사용량을 줄일 수 있다.
완벽 공략3. 인터페이스에 정적메서드 : 자바8과 9에서 주요 인터페이스의 변화
static String hi(){ prepareMessage(); return "hi"; } default String bye() { // 기본메서드 : 이 인터페이스를 구현한 클래스들의 인스턴스들이 모두 사용가능 return "bye"; } static private void prepareMessage() { }
- 기본 메서드(default method)와 정적 메소드를 가질 수 있다
- 기본 메서드
ㆍ인터페이스에서 메소드 선언 뿐 아니라, 기본적인 구현체까지 제공할 수 있다
ㆍ기존의 인터페이스를 구현하는 클래스에 새로운 기능을 추가할 수 있다
ㆍ이 인터페이스를 구현한 클래스들의 인스턴스들이 모두 사용 가능
- 정적 메서드
ㆍ자바 9부터 private static 메서드도 가질 수 있다
ㆍ단, private 필드는 아직도 선언할 수 없다
- 질문1) 내림차순으로 정렬하는 Comparator를 만들고 List<Integer>를 정렬
- 질문2) 질문1에서 만든 Comparator를 사용해서 오름차순으로 정렬하라.
public static void main(String[] args) { List<Integer> numbers = new ArrayList<>(); numbers.add(100); numbers.add(20); numbers.add(44); numbers.add(3); Comparator<Integer> desc = (o1,o2) -> o2-o1; // Collections.sort(numbers, desc); numbers.sort(desc); numbers.sort(desc.reversed()); }
완벽 공략4. 서비스 제공자 프레임워크 : 확장 가능한 애플리케이션을 만드는 방법
- 주요 구성 요소
ㆍ서비스 제공자 인터페이스 (SPI)와 서비스 제공자 (서비스 구현체)
ㆍ서비스 제공자 등록 API (서비스 인터페이스의 구현체를 등록하는 방법)
ㆍ서비스 접근 API (서비스의 클라이언트가 서비스 인터페이스의 인스턴스를 가져올 때 사용하는 API )
- 다양한 변형
ㆍ브릿지 패턴 : 구체적인 것과 추상적인 것을 분리
public static void main(String[] args) { Champion kda아리 = new 아리(new KDA()); kda아리.skillQ(); kda아리.skillW(); Champion poolParty아리 = new 아리(new PoolParty()); poolParty아리.skillR(); poolParty아리.skillW(); }
ㆍ의존 객체 주입 프레임워크
ㆍjava.util.ServiceLoader
완벽 공략5. 리플렉션 (reflection)
- 클래스로더를 통해 읽어온 클래스 정보(거울에 반사된 정보)를 사용하는 기술
- 리플렉션을 사용해 클래스를 읽어오거나, 인스턴스를 만들거나, 메소드를 실행하거나, 필드의 값을 가져오거나 변경하는 것이 가능하다
- 언제 사용할까?
ㆍ특정 애노테이션이 붙어있는 필드 또는 메소드 읽어오기 (JUnit, Spring)
ㆍ특정 이름 패턴에 해당하는 메소드 목록 가져와 호출하기 (getter, setter)
* 문자열로 인스턴스 만들기
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { Class<?> aClass = Class.forName("me.whiteship.hello.Service"); Constructor<?> constructor = aClass.getConstructor(); Object o = constructor.newInstance(); }
'Java > 이펙티브 자바' 카테고리의 다른 글
아이템5. 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라 (0) 2022.11.01 아이템4. 인스턴스화를 막으려거든 private 생성자를 사용하라 (0) 2022.11.01 아이템3. 생성자나 열거 타입으로 싱글턴임을 보증하라 (0) 2022.10.31 아이템2. 생성자에 매개변수가 많다면 빌더를 고려하라 (0) 2022.10.31 자바 이펙티브 - 1. 들어가기 (0) 2022.06.10