이펙티브자바 정리 1

이봐요이상해씨·2021년 12월 7일
0

목록 보기
2/6

1장

자바 지원타입 : 인터페이스, 클래스, 배열 기본타입

어노테이션은 인터페이스 일종, 열거타입은 클래스 일종

인터페이스, 클래스, 배열은 참조타입

클래스 멤버는 필드 메서드 멤버 클래스, 멤버 인터페이스가 있음

2장 객체 생성과 파괴

1. 생성자 대신 정적 팩터리 메서드를 고려해라

클래스는 생성자 별도로 정적 팩토리 메서드를 제공한다

public static Boolean valueOf(boolean b){
		return b ? Boolean.TRUE : Boolean.FALSE;
	}

장점

  • 이름을 갖을 수 있다
  • 호출 때마다 인스턴스를 새로 생성하지 않아도 된다
    • 불필요한 객체 생성을 피할 수 있다
    • 플라이웨이트 패턴도 이와 비슷하다
    • 인스턴스를 언제까지 살려둘지 통제 가능하다
    • 인스턴스 통제를 하면 클래스를 싱글톤, 불가로 만들 수 있다.
  • 반환 타입의 하위 타입 객체를 반활할 수 있다.
    • 자바 8은 인터페이스에 public정적 멤버만, 자바 9부터 private 까지 가능
  • 입력 매개변수에 따라 매번 다른 클래스 객체를 반환할 수 있다
  • 정적 팩터리 메서드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도 된다

단점

  • 상속을 하려면 public이나 protected생성자가 필요하니 정적 팩터리 메서드만 제공하면 하위 클래스를 만들 수 없다
  • 정적 팩터리 메서드는 프로그래머가 찾기 어렵다
    • 정적 팩터리 메서드 명명방식
      • from : 매개 변수를 하나 받아서 해당 타입의 인스턴스를 반환하는 형변환 메서드
        Date d = Date.from(instant);
      • of : 여러 매개변수를 받아 적합한 타입의 인스턴스를 반환하는 집계 메서드
        Set<Rank> faceCards = EnumSet.of(jack, queen, king)
      • valueOf : from과 of의 더자세한 버전
        BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE)
      • instance 혹은 getInstance(매개변수를 받는 경우) 매개변수로 명시한 인스턴스를 반환하지만 같은 인스턴스임을 보장하진 않음
        StackWalker = luke = StackWalker.getInstance(options)
      • create 혹은 newInstance : Instance 혹은 getInstance와 같지만, 매번 새로운 인스턴스를 생성해 반환함을 보장한다
        Object newArray = Array.newInstance(classObject, arrayLen);
      • getType : getInstance와 같으나 생성할 클래스가 아닌 다른 클래스에 펙터리 메서드를 정의할 때 쓴다.
        FileStore fs = Files.getFileStore(path)
      • newType : newInstance 와 같으나, 생성할 클래스가 아닌 다른 클래스에 팩터리 메서드를 정의할 때 쓴다.
        BufferedReader br = Files.newBufferedReader(path)
      • type : getType과 newType의 간결한 버전
        List<Complaint> litany = Collections.list(legacyLitany)

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

package com.springandjava.Test.effectivejava.chapter2;

public class ex2 {
	private final int size;
	private final int servings;
	private final int fat;
	private final int sodium;
	private final int cal;
	
	public static class Builder {
		private final int size;
		private final int servings;
		private int fat = 0;
		private int sodium = 0;
		private int cal = 0;
		
		public Builder(int size, int servings){
			this.size = size;
			this.servings = servings;
		}
		
		public Builder calories(int val){
			cal = val;
			return this;
		}
		public Builder fat(int val){
			fat = val;
			return this;
		}
		public Builder sodium(int val){
			sodium = val;
			return this;
		}
		
		public ex2 build() {
			return new ex2(this);
		}
	}
	
	private ex2(Builder builder) {
		servings = builder.servings;
		size = builder.size;
		fat = builder.fat;
		cal = builder.cal;
		sodium = builder.sodium;
	}
}

3. private 생성자나 열거 타입으로 싱글턴임을 보증해라

싱글톤 : 인스턴스를 오직 하나만 생성할 수 있는 클래스를 말함

클래스를 싱글톤으로 만들면 이를 사용하는 클라이언트를 테스트하기가 어려워짐

일반적으로 생성자는 private으로 만들고, 접근 수단으로 public static을 만든다

  1. public static final 필드 방식의 싱글턴
public class ex3 {
	public static final ex3 INSTANCE = new ex3();
	
	public ex3(){}
	
	public void sss(){}

}
  • 장점
    • 싱글턴임이 명백히 들어남
    • 간결함
  1. 정적 팩터리 방식의 싱글턴
public class ex3 {
	public static final ex3 INSTANCE = new ex3();
	
	public ex3(){}

	//추가
	public static ex3 getInstacne() {
			return INSTANCE;
}
	
	public void sss(){}

}

getInstance는 항상 같은 객체의 참조를 반환

  • 장점
    • api를 바꾸지 않고도 싱글턴이 아니게 변경 가능
      • 호출하는 스레드 별로 다른 인스턴스를 넘겨줄 수 있음
    • 정적 팩터리를 제네릭 싱글턴 팩터리로 만들 수 있음
    • 정적 팩터리의 메서드 참조를 공급자(Supplier)로 사용할 수 있음

위 두방식 모두 직렬화 구현시 Serializable 구현만으로 안됨

모든 인스턴스 필드를 일시적이라고 선언, readresolve 메서드를 제공해주어야함

안그러면 역직렬화 때마다 새로운 인스턴스가 만들어짐

//싱글턴임을 보장하는 readResolve 메서드
private Object readResolve(){
	//진짜 반환하고 가짜는 GC에 맡긴다
	return INSTANCE;
}
  1. 싱글턴을 만드는 세번째 방법
public enum ex {
	INSTANCE;
	public void sss(){}
}

원소가 하나인 열거 타입을 선언

  • 장점
    • 간결함
    • 직렬화시 문제 없음
    • 가장 좋은 방법

4. 인스턴스화를 막으려면 private 생성자를 사용해라

추상클래스로 만드는 것으로는 인스턴스화를 막을 수 없다

  • 하위 클래스를 만들어 인스턴스화 하면 됨

⇒ 해결법 : private 생성자를 추가하여 클래스 인스턴스화를 막음

package com.springandjava.Test.effectivejava.chapter2;

public class ex4 {
	private ex4() {
		throw new AssertionError();
	}
}

명시적 생성자가 private → 밖에서 접근 불가, 상속 불가능

→ 사용처 → 정적 메서드만 따로 모아 놓은 클래스

5. 자원을 직접 명시하지 말고 의존 객체 주입을 사용해라

사용하는 자원에 따라 동작이 달라지는 클래스에는 정적 유틸리리 클래스나 싱글턴 방식이 적합하지 않음

→ 인스턴스를 생성할 때 생성자에 필요한 자원을 넘겨주는 방식으로 해결

public class ex5 {
	private final Lexicon dict;
	
	public ex3(Lexicon dict){
		this.dict = Objects.requiredNonNull(dict)
	}
}

의존 객체 주입으로 유연성을 부여

6. 불필요한 객체 생성을 피해라

비싼 객체 → 캐싱하여 재사용

public class ex6 {
	static boolean isRomanNumeral (String s){
		return s.matches("^(?=.)" + (X));
	}
}

String.matches 는 반복사용시 재새용 비용이 높음, pattern 인스턴스는 바로 GC 로 감

public class ex6 {
		private static final Pattern ROMAN = Pattern.compile(
			"^(?=.)");
	
	static boolean ex6 (String s){
		return ROMAN.matcher(s).matches();
	}
}

불변 pattern 인스턴스 클래스 초기화 과정(정적초기화) 직접 생성 및 캐싱, 나중에 ex6 매서드가 호출될 떄마다 이 인스턴스 재사용

불필요한 객체 생성 : 오토박싱

오토박싱이란? 기본 타입과 그에 대응하는 박싱된 기본타입의 구분을 흐려주지만 완전히 없애는 것은 아님

→ 박싱된 기본타입보다 기본타입을 사용하고, 의도치 않은 오토박싱이 숨어들지 않도록 주의하자

private static long sum(){
	Long sum = 0L;
	for(long i = 0; i<= Integer.MAX_VALUE; i++)
			sum +=i;
	return sum;
}

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

package com.springandjava.Test.effectivejava.chapter2;

import java.util.Arrays;
import java.util.EmptyStackException;

public class ex7 {
	private Object[] elements;
	private int size = 0;
	private static final int DEFAULT = 16;
	
	private ex7(){
		elements = new Object[DEFAULT];
	}
	
	public void push(Object e){
		ensureCapacity();
		elements[size++] = e;
	}
	
	public Object pop(){
		if (size == 0){
			throw new EmptyStackException();
		}
		return elements[--size];
	}
	
	private void ensureCapacity(){
		if (elements.length == size)
			elements = Arrays.copyOf(elements, 2 * size + 1);
	}
}

스택에서 꺼내진 객체는 GC가 회수하지 않음

→ 왜? 그 객체들이 다 쓴 참조를 갖고 있기 때문

다쓴 참조란?

→ 다시 쓰지 않을 참조라는 뜻

가비지 컬렉션은 객체 참조 하나를 살려두면 그 연관된건 다 회수 못함

→ 해결법?

→ 해당 참조 다 썼을시 null 처리

여기서는 스택에서 꺼내지는 pop 메서드에 처리

public Object pop(){
		if (size == 0){
			throw new EmptyStackException();
		}
		Object result = elements[--size];
		elements[size] = null;
		return result;
	}

elemets[size] = null 처리 해준다

Null 처리 언제 해주어야 하나?(3가지 방법)

자기 메모리를 직접 관리하는 클래스라면(ex 스택) 메모리누수 주의

WeakHashMap

참조동안만 엔트리가 살아있는 캐시가 필요할 때 사용

Java - Collection - Map - WeakHashMap (약한 참조 해시맵) - 조금 늦은, IT 관습 넘기 (JS.Kim)


public class WeakHashMapTest {
 
    public static void main(String[] args) {
        WeakHashMap<Integer, String> map = new WeakHashMap<>();
 
        Integer key1 = 1000;
        Integer key2 = 2000;
 
        map.put(key1, "test a");
        map.put(key2, "test b");
 
        key1 = null;
 
        System.gc();  //강제 Garbage Collection
 
        map.entrySet().stream().forEach(el -> System.out.println(el));
 
    }
}
결과  (null 로 할당된 key1이 Map 내에서 사라졌다.)

2000=test b

Process finished with exit code 0

위 코드에서보면 null 처리된 key1 이 약한 참조가 되어 GC되고, 출력하면 key1 값은 안나옴

1) ScheduledThreadPoolExecutor

Java - ScheduledThreadPoolExecutor 사용 방법

2) LinkedHashmap의 removeEledestEntry

[java] HashMap 업그레이드 ! 순서 있는 HashMap ! LinkedHashMap 이란 ??

3)java.lang.ref

자바 레퍼런스와 가비지 컬렉션(Java Reference & Garbage Collection)

8.finalizer와 cleaner사용을 피해라

fiinalizer, cleaner → 객체 소멸자

c++ → destructor

  1. 예측 불가능
  2. 제때실행되어야하는 보장 못함
  3. 상태 연구 수정시 절대 사용 금지
  4. 보안에취약함
  5. 성능 하락

대체 방법?

AutoCloseable 구현, 인스턴스 종료시 close 메소드 호출 예외처리시 try-with-resource 사용

그럼 언제 사용?

대체망 역할 → 클라이언트가 늦게라도 자원 회수 처리 해줄 경우 , FileInputStream, FileOutputStream, ThreadPoolExecutor

package com.springandjava.Test.effectivejava.chapter2;

import java.lang.ref.Cleaner;

public class ex8 implements AutoCloseable {
	
	private static final Cleaner cleaner = Cleaner.create();
	

	//State -> runnalbe 구현
	private static class State implements Runnable {
		int numJunkPiles;
		
		State(int numJunkPiles){
			this.numJunkPiles = numJunkPiles;
		}
		
		@Override
		public void run(){
			System.out.println("방 청소");
			numJunkPiles = 0;
		}
	}
	
	//clenable과 광유
	private final State state;
	
	private final Cleaner.Cleanable cleanable;
	
	public ex8(int numJunkPiles) {
		state = new State(numJunkPiles);
		cleanable = cleaner.register(this, state);
	}
	
	//안전망 
	@Override
	public void close(){
		cleanable.clean();
	}
	
}

9. try-finally 보다는 try-with-resources를 사용해라

자원닫기중 대부분은 finalizer를 활용하지만 이는 좋지 못하다

대신 try-finally를 사용했다

하지만 자원이 2개 이상이면 가독성이 안좋음 → try-with-resources 사용

public class ex9 {

	static void copy(String src, String dst) throws IOException{
		try(InputStream in = new FileInputStream(src);
			OutputStream out = new FileOutputStream(dst)) {
			byte[] buf = new byte[BUFFER_SIZE];
			int n;
			while ((n = in.read(buf)) >= 0)
				out.write(buf, 0,n);
		}
	}
}

아래것을 try-with-resources로 구현
//
	// InputStream in = new FileOutputStream(dst);
	// try {
	// 	OutputStream out = new FileOutputStream(dst);
	// 	try{
	// 		byte[] buf = new byte[BUFFER_SIZE];
	// 		int n;
	// 		while ((n = in.read(buf)) >= 0)
	// 			out.write(buf,0,n);
	// 	} finally {
	// 		out.close();
	// 	} finally {
	// 		in.close();
	// 	}
	// }
}

0개의 댓글