Effective Java 3/E - (2) 객체 생성과 파괴

신복호·2020년 12월 6일
0

Effactive JAVA 3/E

목록 보기
2/12
post-thumbnail

2장 객체 생성과 파괴

아이템 1. 생성자 대신에 정적 팩토리 메소드를 고려하자

정적 팩토리 메소드? (static factory method)

클래스의 인스턴스를 반환하는 단순한 정적 메소드

클래스의 인스턴스는 기본적으로 public 생성자를 통해서 얻을수 있다. 하지만 생성자와 별도로 정적 팩토리 메소드를 하면 아래와 같은 장점을 얻을수 있다.

정적 팩토리 메소드의 장점

  • 이름을 가질수 있다.
    • 이름을 직접 지을수 있으므로 객체에 대한 특성을 묘사할수 있음
  • 시그니처에 대한 제약이 없음
    • 생성자는 이름을 가질수 없으므로 오로지 파라미터로만 시그니처를 다르따라서 같은 타입의 파라미터를 받으면서 생성자를 다르게 하고 싶어도 할수가 없다.
  • 매번 인스턴스를 새로 만들지 않아도 된다
    • 인스턴스를 미리 만들어두거나 생성된 인스턴스를 캐싱 하여 재활용하는 방식으로서 불필요한 객체 생성을 줄일수 있다.
      -> 어느 시점에 어떤 인스턴스가 유효한지 제어할 수 있는 인스턴스 통제 클래스로 만들수 있습니다.
  • 반환타입의 하위 타입 인스턴스를 만들수 있다.
    • 반환할 객체의 클래스를 자유롭게 선택할수 있는 엄청난 유연성을 선물한다. API를 만들때 이 유연성을 응용하면 구현 클래스를 공개하지 않고도 그 객체를 반환할 수 있어 API를 작게 유지할수 있고 그에 따라 개념적인 무게, 즉 프로그래머가 API를 사용하기 위해 익혀야 하는 개념의 수와 난이도를 낮출수 있다.
  • 입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할수 있다.
    • ex)EnumSet의 원소가 64개 이하면 long 변수 하나로 관리하는 RegularEnumSet 으로 반환하고 65개 이상이면 long 배열로 관리하는 JumboEnumSet 을 반환한다.

정적 팩토리 메소드의 단점

  • static pulib 메소드만 제공하는 클래스는 상속할 수 없다.

  • 생성자는 javadoc에 명확하게 나오지만 static factory method는 일반 메소드일 뿐이므로 특별하게 취급하지 않는다. 알려전 규약에 따라 짓는 식으로 문제를 완화해야 한다.

    • from : 매개 변수를 받아서 해당 타입의 인스턴스를 반환

       Date date = Date.from(today);
    • of : 여러 매개변수를 받아서 인스턴스 반환

       Set<Rank> lol = EnumSet.of(iron,bronze,silver,gold,platinum,diamond,master,grandmaster,challenger);
    • valueOf : from 과 of의 더 자세한 버전

      BigInteger price = BigInteger.valueOf(Integer.MAX_VALUE);
    • instance / getInstance : 인스턴스를 반환(같은 인스턴스임을 보장 x)

      Users users = UserInstance.getInstance(User); 
    • Create / newInstance : 매번 새로운 인스턴스를 생성하여 반환함

       Object newArray = Array.newInstance(class, arrayLen);
      • getType : getInstance와 같으나 생성할 클래스가 아닌 다른 클래스에 팩토리 메소드를 정의할 때 사용
       File file = Files.getFilestore(path);
      • NewType : newInstance와 같으나 생성할 클래스가 아닌 다른 클래스에 팩토리 메소드를 정의할때 사용
       BufferedReader br = File.newBufferedReader(path);
    • type : getType과 newType의 간결한 버전

      List<custom> customList = Collections.list(list);

    무조건 생성자만을 사용하는 습관을 버리자

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

생성자와 정적 팩토리 메서드 모두 똑같은 단점이 있는데 Optional parameter가 많다면 불편해진다.

대안

  • 점층적 생성자 패턴 : 확장성이 좋지 않음

     User user = new User(1,"신복호",29);
  • 자바빈 패턴 : 일관성이 깨지며, 불변으로 만들수 없다.

    User user = new User();
     user.setId(1);
     user.setName("신복호");
     user.setAge(29);
  • 빌더패턴 : 점층적인 생성자 패턴의 안정성과 자바빈 패턴의 가독성을 함께 할수 있다.

    User user = new User().bulider(1)
                  .name("신복호")
                  .age(29);

    생성자나 정적 팩토리에 매개변수가 많다면 빌더 패턴을 사용하자. 매개변수 중 대부분이 필수가 아니거나 같은 타입이면 더욱 사용해야 한다.

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

singleton - 인스턴스를 오로지 하나만 생성할 수 있는 클래스를 말하며 아래 처럼 3가지 방법이 있다.

  • public static final을 사용하는 방법

    • 생성자는 private 로 감춰두고 인스턴스를 초기화 할때 딱 한번만 호출 시키도록 한다.
    public class User {
          public static final User INSTANCE = new User();
          private Users() {}
      }
  • 정적 펙토리 메소드를 제공하는 방식

    • 싱글턴이라는 것이 명확하게 드러나고 차후에 변경도 매우 유연하다.
    public class User {
          public static final User INSTANCE = new User();
          private Users() {}
          public static Users getInstance() { return INSTANCE }
      }

-> 두가지 방식의 경우 리플랙션 API를 사용하는 경우 private 에 의해 싱글톤 방식이 깨질수 있다.

  • Enum을 사용하는 방식

    • private static final과 사용 방식이 비슷하나 매우 간결하며, 위에서 살펴본 리플렉션, 직렬화로 인한 문제를 막아 준다.
     public enum Users {
     	INSTANCE;
     }

    private 생성자나 Enum으로 싱글톤임을 보증하자

아이템 4. 인스턴스화를 막으려거든 private 생성자를 사용해라

  • 생성자를 명시하지 않으면 컴파일러가 기본 생성자를 생성한다. 따라서 인스턴스화를 막으려면 private 생성자를 명시적으로 생성해줘야 한다.
    혹시라도 클래스 내부에서 생성자를 호출하지 않도록 오류를 던지는것도 좋다.

    
    public class User {
    	private User{
                throw new AssertionError();
            } 
    
    }
  • 추상 클래스로 만드는 것은 자기 자신의 인스턴스화를 막을수는 있지만 하위 클래스를 만들어 인스턴스화를 할수 있음

인스턴스화를 막는 최고의 방법은 private 생성자를 만드는것이다.

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

대부분의 클래스가 하나 이상의 자원에 의존한다. 이런 클래스를 정적 유틸리티 클래스로 구현하면 유연하지 않고 테스트도 어렵다.

 public  class User {
 	private static final info = new Infomation();
   
   private User() {} // 객체 생성 방지
   
   public static boolean isUsers(String name) {/*구현 생략 */ }
   
   public static List<String> infoList(Integer id) { /* 구현 생략 */ }
 
 }
  • 인스턴스를 생성할때 필요한 자원을 넘겨주면 된다.

  • 불변을 보장하여 같은 자원을 사용하려는 여러 클라이언트가 의존 객체들을 안심하고 공유할수 있다.

  • 또한 생성자 뿐만 아니라 정적 팩토리, 빌더 모두 똑같이 응용이 가능하다.

  • 클래스가 하나 이상의 동작에 영향을 주는 자원에 의존한다면, 자원을 생성자 또는 정적 팩토리나 빌더에 넘겨주자

  public Users(Supplier<? extends info> infomation) {
   this.info = infomation.get();
  }

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

  • 생성자로 문자열을 만들어내는 경우 매번 새로운 인스턴스를 생성하게 된다.

String name = "Shin";
String name2 = "Hong";
boolean request = false;

if(name == name2 ) { request = true} // true;

String name3 = new String("Shin");
String name4 = new String("Hong");
boolean request2 = false;

if(name3 == name4 ) { request2 = true} // false;
  • 정적 팩토리 메소드에서도 불필요한 객체 생성을 줄일수 있다. (valueof / java9 -> Deprecated)

  • 생성 비용이 비싼 객체를 재사용하는 것도 중요하다.

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

  • 자바에서는 c,c++ 처럼 직접 메모리를 관리하지 않고 GC가 알사서 사용이 끝난 객체를 회수하나 그렇다고 메모리 관리 신경을 써야 한다.

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

  • 참조를 담은 변수의 유효 범위 밖으로 밀어내는 것이 가장 좋다.

  • 캐시 또한 메모리 누수를 일으키는 주범이다.
    -> WeakHashMap,LinkedHashMap. removeEldestEntity를 권장한다.

메모리 누수에 주의하고 예방법을 익혀 두자

아이템 8. finalizer와 cleaner사용을 피해라

  • 자바에서는 2가지 객체 소멸자를 제공한다. 첫번째 finalizer는 예측할수 없고 상황에 따라서 위험할수 있다.
    두번째 cleaner -> Deprecated된 finalizer의 대안으로 등장하여 덜 위험하지만 여전히 예측할 수 없다.

  • 사용해야 할때

    • clone 메소드를 호출하지 않는거에 대한 안전망 역할을 한다 (FileOutputStream, ThreadPollExecutor)

    • 네이티브 자바는 가비지 컬랙터가 회수하지 못한다. 그 대안으로 사용된다. (물론 성능 저하)

  • 그냥 사용하지 말자

아이템 9. try-finally 보다는 try-with-resource를 사용해라

  • 자바 라이브러리에는 close()라는 메서드를 통해 닫아야 하는 자원들이 있다. java7 이전에는 try-finally를 이용했다.
//생략
 BufferedReader br = new BufferedReader(new FileReader(path));
 try {
    return br.readLine();
 } finally{
    br.close();
 } 
//생략
  • 자바 7부터 try-with-resource가 등장하였다. -> 물론 AutoCloseable 인터페이스를 구현한 클래스만 사용할수 있다.
//생략
 
 try(BufferedReader br = new BufferedReader(new FileReader(path))) {
    return br.readLine();
 } finally{
    br.close();
 } 
//생략
  • 자바 9부터는 좀더 향상된 점을 쓸수 있다.
BufferedReader br = new BufferedReader(new FileReader(path));
BufferedReader br2 = new BufferedReader(new FileReader(path));
V
 try(br;br2) {
    return br.readLine() + br2.readLine();
 } finally{
    br.close();
    br2.close();
 } 
  • final이거나 초기화를 한 후에 절대 바뀌지 않는 effectively final 변수여야 한다.
profile
한참 열정이 가득한 백엔드 개발자입니다.

0개의 댓글