Chapter 1 

1. 정적 팩터리 메서드의 장점1

class Order{
    
    private boolean prime;
    
    private boolean urgent;
    
    private String product;
    
    public Order(String product, boolean prime){
        this.product=product;
        this.prime=prime;
        this.urgent=false;
    }
    /* 같은 파라미터 타입으로 오버로딩 사용불가 
    public Order(String product, boolean urgent){
        this.product=product;
        this.urgent=urgent;
        this.prime=false;
    }
     */
     
     // 정적 팩토리 메서드 사용해서 해결 
     // 정적 메서드 이름으로 좀더 시그니쳐 하게 만들 수 있다. 
     public static Order primeOrder(String product){
        Order order = new Order();
        order.product=product;
        order.prime=true;
        order.urgent=false;
        return order;
    }
    
    public static Order urgentOrder(String product){
        Order order = new Order();
        order.product=product;
        order.urgent=true;
        order.prime=false;
        return order;
    }
}

2. 정적 팩터리 메서드의 장점2

class Order{

    private boolean prime;

    private static final Order order = new Order();
    
    private Order(){}

    // 객체의 무분별한 생성을 방지할 수 있다. 
    // 객체의 생성을 컨트롤할 수 있게 된다. 
    public static Order getInstance(){
        return order;
    }
}

3. 정적 팩토리 메서드의 장점3,4,5

5: 인터페이스만 있어도 만들 수 있다 -> 클래스와 인터페이스와의 의존성을 삭제할 수 있다. 

public interface AllOrder {
	
    // 인터페이스에 정적 메서드 사용할 수 있다. 
    static AllOrder createOrderStyle(String style){
        prepare();
        if(style.equals("1")){
            return new Style1Order();
        }
        else {
            return new Style2Order();
        }
    }
    
    // 외부에 정적 메서드를 숨기고 싶다면 private로 선언이 가능하다.
    private static void prepare(){
    	System.out.println("hi");
    }
}
class Style1Order implements AllOrder{


}

class Style2Order implements AllOrder{
    
}
public class OrderFactory {

    // 인터페이스와 정적 메서드를 활용해서 하위 클래스를 생성할 수 있다. 
    // 즉 유연성이 생긴다. 
    public static AllOrder createOrderStyle(String style){
        if(style.equals("1")){
            return new Style1Order();
        }
        else {
            return new Style2Order();
        }
    }
}

정적 팩터리 메서드의 장점 정리

1. 이름을 가질 수 있다(동일한 시그니처를 가질 수 없는 생성자의 문제 해결)

2. 호출될 때마다 인스턴스를 새로 생성하지 않아도 된다. (미리 만든 것을 return 하는 형태)

3. 반환 타입의 하위 타입 객체를 반환할 수 있는 능력이 있다.(인터페이스 기반 프레임워크, 인터페이스에 정적메소드)

4. 입력 매개변수에 따라 다른 클래스의 객체를 반환할 수 있다. (EnumSet은 hashing을 하지 않아서 더 빠른 성능을 보여준다.)

5. 정적 팩토리 메서드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도된다. (서비스 제공자 프레임워크 ->ServiceLoader를 이용하는 형태)


정적 팩토리 메서드의 단점

Java Doc을 만들기 어렵다. 그래서 이름 컨벤션을 잘 따라야 한다. 


1. 열거 타입은 인스턴스가 하나만 만들어짐을 보장한다.

- 상수 목록을 담을 수 있는 데이터 타입

- 타입-세이프티를 보장할 수 있다.

- 싱글톤 패턴을 구사할 수 있다. 

- 비교할 때 equals가 아니라 ==으로 해도 된다. 

- EnumSet, EnumMap은 hashing하지 않아 더 빠르다. set도 더 빠르다. 

2. 같은 객체가 자주 요청되는 상황이라면 플라이웨이트 패턴을 사용할 수 있다.

- 같은 객체가 여러번 사용된다면, 재사용을 하는 것이 모범적이며 플라이웨이 패턴이다. 

 

3. 인터페이스가 정적 메서드를 가질 수 있게 되었다.

- 기본 메서드와 정적 메서드를 가질 수 있다.

4. 서비스 제공자 프레임워크를 만드는 근간이 된다.

- 서비스 제공자 인터페이스와 서비스 제공자가 있다.

- 서비스 제공자 등록 API (서비스 인터페이스의 구현체를 등록하는 방법)

- 서비스 접근 API (서비스의 클라이언트가 서비스 인테페이스의 인스턴스를 가져올 때 사용하는 API)

- 브릿지 패턴 -> 구체적인 것과 추상적인 것을 나눈다. 

- 의존 객체 주입 프레임워크(Spring framework) 

 

5. 리플렉션

- 클래스로더를 통해 읽어온 클래스 정보를 사용하는 기술

- 애노테이션에 붙은 필드 또는 메소드 읽어오기가 가능하다.


Chapter 2

1. 생성자에 매개변수가 많다면 빌더를 고려하라.

일부 필드가 필수적이고, 일부는 선택적이라면 여러 개의 생성자를 만들어야 할 수 있다. 점층적 생성자를 사용하자.

매개 변수가 적은 생성자에서 많은 생성자로 호출 한다. 

class Person{
    String name; // 필수
    int age; // 선택 default =0
    int height; // 선택 default =0
    
    public Person(String name){
        this(name,0);
    }
    public Person(String name, int age){
        this(name, age, 0);
    }
    
    public Person(String name, int age, int height){
        this.name=name;
        this.age=age;
        this.height=height;
    }
}

 

 

 

1-1. 자바빈스

getter setter 등을 의미하며, 사용해서 객체의 값을 만든다. 단점으로는 필수적으로 필요한 필드값이나 그런 것을 알 수 없다. 따라서 점층적 생성자, 자바 빈스를 이용해서 만든다. 또한 불변 객체를 만들 수 없다.(setter를 사용하기 때문에)

public void setName(String name){
    this.name=name;
}

public String getName(){
    return this.name;
}

 

2. 빌더 패턴

클래스 빌더를 내부 클래스로 만들어놓는다. 이때 동일한 필드 값을 가진다. 빌더는 필수가 아니다. 코드의 복잡성이 올라가기 때문이다. (lombok을 사용한다면 괜찮다.) 

class Person{
    String name;
    int age;
    int height;


    public Person(Builder builder){
        this.name= builder.name;
        this.age=builder.age;
        this.height=builder.height;
    }
    public static class Builder{
        private String name;
        private int age=0; // default 값
        private int height=0; // default 값

        public Builder(String name){
            this.name=name;
        }
        public Builder age(int age){
            this.age=age;
            return this;
        }
        public Builder height(int height){
            this.height=height;
            return this;
        }

        public Person build(){
            return new Person(this);
        }

    }
}

 

 

 

3. 자바빈, 게터, 세터

자바빈이 지켜야 할 규약 -> 특정 프레임워크에서 사용되는 gui 같은 것들이 사용하는 필드에 접근 혹은 필드의 값을 가져오기 위해서 생긴 규약이다. 

- 아규먼트가 없는 기본 생성자

- getter 와 setter 메서드 이름 규약 get() set() is()

- Serializable 인터페이스 구현

 

4. 객체 얼리기(freezing)

체크하는 flag를 만들어서 필드를 얼린다. 

개발자의 코드에 혼란을 줘 사용하지 않는다. 

final Person person = new Person();
person = new Person(); // 오류
person.setName("hi"); // 내부 필드는 가능
person.list.add("hi"); // 추가 가능

5. IllegalArgumentException

잘못된 인자를 넘겨 받았을 때 사용할 수 있는 기본 런타임 예외

 

5-1. checked exception 과 unchecked exception의차이

복구할 수 있는 방법이 없을 때는 unchecked exception이이다.

예외를 회피, 복구, 처리 할 수 있을 때는 checked exception이다. 

 

5-2. 간혹 메서드 선언부에 unchecked exception을 선언하는 이유

클라이언트에게 명시적으로 예외가 발생함을 알려주고 싶을 때 사용한다. 

하지만 빈번히 사용하면 오히려 혼란을 줄 수 있다. 

 

5-3 checked exception은 왜 사용할까?

에러가 발생했을 때 클라이언트가 후속 작업을 원하는 경우에 사용한다.

즉 에러에 대해서 복구, 회복 같은 경우에 사용한다. try, catch 블럭


Chapter3

1. 생성자나 열거 타입으로 싱글턴임을 보장하라.

1-1. 간결하고, 싱글턴임을 API에 들어낼 수 있다. 

생성자를 private로 막는다.

class Person{
    String name;
    int age;
    int height;

    public static final Person INSTANCE = new Person();

    public void say(){
        System.out.println("hi i'm person");
    }

    private Person(){};
}

 

 

1-2. 싱글톤을 사용하는 클라이언트 테스트하기 어려워진다.

person 객체를 가지고 있는 클라이언트가 있을 때 클라이언트에서 person을 이용하는 테스트를 진행하고자 했을 때 비용이 많이든다. 즉 person 대신에 대체자를 사용해야 하는데 person이 구체 클래스라면 대체자를 구할 수 없다. 따라서 interface나 추상 객체를 사용해서 해결할 수 있다. 

 

1-3. 리플렉션으로 private 생성자를 호출할 수 있다.

setAccesible(true)로 설정해주면 private에 접근할 수 있다. 

public static void main(String[] args) throws IOException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    Constructor<Person> declaredConstructor = Person.class.getDeclaredConstructor();
    declaredConstructor.setAccessible(true);
   
   	Person person = declaredConstructor.newInstance();

    System.out.println(person==Person.INSTANCE);
}

해결 방법은 private에 flag를 하나 생성하며, 리플렉션으로 인한 새로운 인스턴스 생성을 방지한다. 

 

1-4. 역직렬화 할 때 새로운 인스턴스가 생길 수 있다. 

역직렬화할 때마다 새로운 인스턴스가 생성된다. 

public static void main(String[] args) throws IOException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, ClassNotFoundException {
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("person.obj"));
    objectOutputStream.writeObject(Person.INSTANCE);

    ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("person.obj"));
    Person person = (Person) objectInputStream.readObject();

    System.out.println(person==Person.INSTANCE);
}

 

오버라이딩은 아니지만, 하나의 메서드를 추가함으로써 해결할 수 있다. 

private Object readResolve(){
    return INSTANCE;
}

따라서 싱글턴으로 만들기에는 많은 제약이 있다.

 

2-1. 메서드를 통해서 인스턴스를 접근하도록 한다.

API를 바꾸지 않고도 싱글턴이 아니게 변경할 수 있다.

위에서 말한 리플렉션, 역직렬화의 단점을 극복하진 못한다. 

private static final Person INSTANCE = new Person();

public static Person getInstance(){
    return INSTANCE;
}

 

2-2. 장점으로 정적 팩토리를 제네릭 싱글톤 팩토리를 만들 수 있다. 

class Person<T>{

    private static final Person INSTANCE = new Person();

    public static <T> Person<T> getInstance(){ // 여기서의 T는 정적 스코프의 제네릭이다. 
        return (Person<T>) INSTANCE;
    }
    private Person(){}
}

 

2-3. 메서드 참조를 공급자로 사용할 수 있다. 

class Person{

    private static final Person INSTANCE = new Person();

    public static Person getInstance(){
        return INSTANCE;
    }
    private Person(){}

    public void say(){
        System.out.println("hi");
    }
}

class Concert{
    public void start(Supplier<Person> supplier){
        Person person = supplier.get();
        person.say();
    }
}

public class Main {

    public static void main(String[] args) throws IOException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, ClassNotFoundException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        Concert concert = new Concert();
        concert.start(Person::getInstance);
    }
}

 

3. 열거 타입 활용

쉽게 싱글턴을 보장받을 수 있다. 리플렉션, 직렬화에 안전하다. 

enum도 interface를 상속받을 수 있다.

enum Person{
    INSTANCE;
}


public class Main {

    public static void main(String[] args) throws IOException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, ClassNotFoundException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        Person instance = Person.INSTANCE;
    }
}

 

4. 메서드 참조

메서드 하나만 호출하는 람다 표현식을 줄여쓰는 방법

생성자나, 정적 메서드를 사용할 수 있다. 

 

5. 함수형 인터페이스

기본 함수형 인터페이스를 제공해준다. 사용자는 하나의 메서드만 있는 인터페이스를 정의하면 커스텀 함수형 인터페이스를 사용할 수 있다. 

Predicate, Consume, Supplier, Function

 

6. 직렬화 

객체를 바이트스트림으로 상호 변환하는 기술

바이트스트림으로 변환한 객체를 파일로 저장하거나 네트워크를 통해 다른 시스템으로 전송할 수 있다. 

 

6-1. Serializable 인터페이스 구현

직렬화 역직렬화를 위해선 Serializable을 상속 받아야 한다. 

class Person implements Serializable{

    String name;
    int age;
    int height;

    public Person(String name, int age, int height) {
        this.name = name;
        this.age = age;
        this.height = height;
    }
}

6-2. transient를 사용해서 직렬화 하지 않을 필드 선언하기

class Person implements Serializable{

	static String address; // 정적 변수도 직렬화 되지 않는다. 
    String name;
    int age;
    transient int height; // 직렬화를 하지 않는다. 

    public Person(String name, int age, int height) {
        this.name = name;
        this.age = age;
        this.height = height;
    }
}

6-3. serialVersionUID는 언제 왜 사용하는가?

직렬화, 역직렬화를 수행하려는 class의 변화(필드의 삭제, 추가)에 따라 오류가 생긴다. 

하지만 serailVersionUID를 정의 한다면 사용 가능하다.

객체 직렬화 스펙, Externalizable인터페이스를 살펴보자.


Chapter6

1. 불필요한 객체 생성을 피하라

1-1. 문자열 -> 동일한 객체라서 매번 새로 만들 필요가 없다. 

String hello = "hello";
String hello2 = new String("hello");
String hello3 = "hello";

""로 생성하면 jvm이 캐싱기능 처럼 작용하지만, new를 사용하면 새로운 불필요한 객체가 생성되므로 이것을 생각하고 사용하자.

 

1-2. 정규식 -> 생성 비용이 비싼 객체라서 캐싱하여 재사용하는 것이 좋다.

 

1-3. 오토 박싱 -> 자동래퍼기능을 이용하는 과정에서 불필요한 객체가 생성될 수 있다.

 

 

2. 사용자제 API(Deprecation)

사용 자제를 권장하고 대안을 제시하는 방법이 있다.

 

2-1. @Deprecated 자바독에도 사용할 수 있다.

- 컴파일시 경고 메시지를 통해 사용 자제를 권장하는 API라는 것을 클라이언트에 알려줄 수 있다.

class Deprecation{

    @Deprecated(forRemoval = true,since = "1.2")
    public Deprecation(){}

    private String name;
}

 

 

3. 정규 표현식

정규식이 쓰이는 곳

 

3-1. String.matches(정규식)

3-2. String.split(패턴)

3-3. String.replace(패턴)

 

4. 가비지 컬렉션

Mart, Sweep, Compact 개념을 알아보자. 

Young Generation, Old Generation

 


Chapter 7 다 쓴 객체 참조를 해제하라.

1. 어떤 객체에 대한 레퍼런스가 남아있다면 해당 객체는 가비지 컬렉션의 대상이 되지 않는다.

2. 자기 메모리를 직접 관리하는 클래스라면 메모리 누수에 주의해야 한다.

3. 참조 객체를 null 처리하는 일은 예외적인 경우이며 가장 좋은 방법은 유효 범위밖으로 밀어내는 것이다. 

 

4-1. Stack 같은 경우에 element의 index를 조정할 뿐 참조 객체를 해제하지는 않는다. 따라서

null로 참조를 해제한다면, 가비지 컬렉션의 대상이 된다.

 

4-2. Weak 객체를 사용해보자. 

WeakHashMap으로 변경 시 gc가 동작된다. 

class PostRepository{

    private Map<CacheKey, Post> cache;
    public PostRepository(){
        this.cache = new HashMap<>();
    }

    public Post getPostById(Integer id){
        CacheKey cacheKey = new CacheKey(id);
        if(cache.containsKey(cacheKey)){
            return cache.get(cacheKey);
        }else{
            Post post = new Post();
            cache.put(cacheKey,post);
            return post;
        }
    }
}

 

5. NullPointerException

 

5-1. 예외를 던진다.

5-2. null을 리턴한다.

5-3. Optional을 리턴한다. 강추 리턴 타입으로만 사용하는 걸 권장

 

6. WeakHashMap

 

맵의 엔트리를 맵의 Value가 아니라 Key에 의존해야 하는 경우에 사용할 수 있다.

Key가 더이상 강하게 레퍼런스되는 곳이 없다면 해당 엔트리를 제거한다.

 

레퍼런스 종류 - Strong, Soft, Weak, Phantom

- Strong: 일반적인 경우

- Soft: gc의 대상이 된다. 메모리가 필요한 상황에!

- Weak: gc가 일어날 때 무조건 없어진다. 

- Phantom: gc가 일어날 때 팬텀 오브젝트를 큐에 넣어준다. 큐까지 클리어 해줘야 사라진다. 

 

캐시를 구현하는데 사용할 수 있지만, 캐시를 직접 구현하는 것은 권장하지 않는다.

 

7. 백그라운드 쓰레드

 

7-1. Thread, Runnable, ExecutorService

많은 쓰레드를 사용하면 자원 낭비가 심하다.

 

7-2. 쓰레드풀 의 개수를 정할 때 주의할 것

- CPU, I/O

 

만약 CPU를 많이 사용하는 작업이라면 CPU 개수를 넘어가도 처리를 다 하지 못한다.

- CPU 개수만큼 하는 것이 좋다.

 

 

만약 I/O가 많은 작업이라면 CPU 개수보다 약간 많이 잡아야한다.

- 정답은 없다. 성능 튜닝이 직접적으로 필요하다. 실행을 해봐야 알 수 있다. 

 

7-3. 쓰레드툴의 종류

- Single, Fixed, Cached, Scheduled

Executors.newFixedThreadPool(10); 을 통해서 ThreadPool에 10개의 Thread 가지고 작업을 실행한다. 강추

Executors.newCachedThreadPool() 놀고 있는 Thread가 있다면 그걸 사용하고, 더이상 필요없으면 삭제한다. 조심히 사용하자. 

Executors.newSingleThreadExecutor() 하나의 Thread로 실행한다. 굉장히 별로다.

Executors.newScheduledThreadPoll(10) 주기적으로 실행하고자 할 때 사용한다. 

 

7-4. Runnable, Callable, Future

Runnabla -> 리턴 타입이 없다. 작업을 돌리고 끝냄, 결과를 받고싶다면 Callable 사용

Callable -> 작업의 결과를 받을 수 있다. 

Future -> 블록킹이 되지 않는다. future.get()은 블로킹 코드이다.

 

7-5. CompletableFuture, ForkJoinPool

공부를 해보자.


Chapter 8  finalizer와 cleaner의 사용을 피해라

1. finalizer와 cleaner는 즉시 수행된다는 보장이 없다.

2. finalizer와 cleaner는 실행되지 않을 수도 있다.

3. finalizer 동작 중에 예외가 발생하면 정리 작업이 처리되지 않을 수도 있다.

4. finalizer와 cleaner는 심각한 성능 문제가 있다.

5. finalizer는 보안 문제가 있다. 

7. 반납할 자원이 있는 클래스는 AutoCloseable을 구현하고, 클라이언트에서 close()를 호출하거나 try-with-resource를 사용해야 한다. (강추)

 

finalizer 100만개 일때마다 사라지게 하는 것

int i=0;
while(true){
    i++;
    new FinalizerSource();

    if((i%1_000_00)==0){
        Class<?>  finalizerClass= Class.forName("java.lang.ref.Finalizer");
        Field queueStaticField = finalizerClass.getDeclaredField("queue");
        queueStaticField.setAccessible(true);
        ReferenceQueue referenceQueue = (ReferenceQueue) queueStaticField.get(null);

        Field queueLengthField = ReferenceQueue.class.getDeclaredField("queueLength");
        queueLengthField.setAccessible(true);
        long queueLength = (long) queueLengthField.get(referenceQueue);
        System.out.format("there %d", queueLength);
    }
}

 

cleaner 사용 방법

class BigObject{
    private List<Object> resource;

    public BigObject(List<Object> resource){
        this.resource=resource;
    }

    public static class ResourceCleaner implements Runnable{
        private List<Object> resourceToClean;

        public ResourceCleaner(List<Object> resourceToClean) {
            this.resourceToClean = resourceToClean;
        }

        @Override
        public void run() {
            resourceToClean=null;
            System.out.println("cleaned up");
        }
    }
}
Cleaner cleaner = Cleaner.create();

List<Object> resourceToCleaner = new ArrayList<>();

BigObject bigObject = new BigObject(resourceToCleaner);

cleaner.register(bigObject,new BigObject.ResourceCleaner(resourceToCleaner));

bigObject=null;
System.gc();
Thread.sleep(3000L);

 

AutoClosable

class AutoClosableIsGood implements AutoCloseable{

    private BufferedInputStream inputStream;
    @Override
    public void close() throws Exception {
        try{
            inputStream.close();
        }catch(IOException e){
            throw new RuntimeException("failed to close"+ inputStream);
        }
    }
}
// try with resource이다. 
try(AutoClosableIsGood autoClosableIsGood = new AutoClosableIsGood()){

}

try with resource를 사용하면 된다. 


Chapter 11 equals를 재정의하려거든 hashCode도 재정의해라

HashMap에서 Hash할 때 hashcode를 사용하므로 equals를 재정의할 때 같이 정의하자.

 

정의하는 쉽고 적절한 방법

1. 핵심 필드 값을 고르고, Objects.hashCode()를 사용한다.

2. 31이라는 숫자가 해쉬 충돌이 가장 적다. Objects.hashCode()안에 구현되어 있다. 

3. Lombok 어노테이션을 사용하자.

 

멀티 스레드 환경에서 안전한 코드

 

1. public synchronized int hashCode(){}를 사용하면 되지만 성능상 문제가 있을 수 있다. 

 

두 번 채킹하는 방법도 있다. 

private volatile int hashCode;

public synchronized int hashCode(){

	if(this.hashCode!=0){
		return hashCode;

	}	

	synchronized(tihs){

	}

}

 

2. ThreadLocal 사용

 

3. 불변 객체 사용

 

4. synchronized 데이터 사용

 

5. concurrent 사용


Chapter12 toString은 항상 재정의 해라

Lombok을 사용해서 하는 것보단 자신이 필요한 부분을 정해서 정의하자. 


Chapter13 clone 재정의는 주의해서 진행하자.

clone 규약 (생성자는 사용해서 만들지 않는다.)

1. x.clone()!=x 반드시 true

2. x.clone().getClass() == x.getCalss() 반드시 true

3. x.clone().equals(x) true가 아닐 수도 있다. 

 

clone은 복잡하기 때문에 잘쓰지 않고, 생성자를 이용해서 만들어버리자. 

 

비검사 예외(언체크 예외) 였어야 했다는 신호에 대해서 알아보자

클라이언트가 복구할 수 있다는 것을 알려줄 수 있는 방법이 체크 예외이다. 

 

 

 

 

 

 

 

 

 

 

 

 

 

'JAVA' 카테고리의 다른 글

Java 8 -> 11 변경점  (0) 2022.12.07
Java Garbage Collection  (0) 2022.05.30
JAVA Overloading & Overriding  (0) 2022.02.26
JAVA Enum  (0) 2022.02.24
JAVA Stream  (0) 2022.02.23