본문 바로가기
프로그래밍언어/Java

[JAVA] 객체 생성과 파괴

by Yikanghee 2022. 1. 6.
  • 객체를 만들어야 할때와 만들지 말아야 할때를 구별한다.
  • 유형별 올바른 객체 생성 방법, 불필요한 생성을 피하는 방법을 구별한다.
  • 객체의 유통기간을 보장하고 그 객체를 파괴하기 전 수행해야 할 작업을 관리한다.

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


클래스 인스턴스는 일반적으로 public 생성자를 통해 생성되나 이를 정적 팩터리 메서드(static factory method)를 사용했을 때 아래와 같은 강점이 존재한다.

장점 - 이름을 가질 수 있다.

  • 이름을 가질 수 있어 생성자 보다 명시적이다.
    • 소수 BigInteger 인스턴스를 반환하는 방법 비교
    • BigInteger(int, int, Random) 생성자 vs BigInteger.probablePrime

장점 - 매번 인스턴스를 새로 만들 필요가 없다.

  • 불변객체 참고(아이템 17)
  • 열거타입(아이템 34) 는 객체 인스턴스가 하나만 생성됨을 보장한다.
    • enum이 이에 해당된다.

장점 - 입력 매개변수에 따라 다른 클랙스의 객체를 반환 할 수 있다.

  • 반환타입이 하위타입일 경우 가능하다.
    • EnumSet 클래스(아이템 36)는 정적팩토리만 제공하며 생성자 매개변수가 64개 이하면 RegularEnumSet, 65개 이상이면 JumboEnumSet 인스턴스를 반환한다.

단점 - 생성자처럼 명확하게 java docs에 표현되지 않는다.

  • 정적 팩터리 메서드는 일반 메서드일뿐, 생성자처럼 java docs에 명확히 표현되지 않는다.
  • 이때문에 알려진 규약에 따라 생성해서 혼란을 줄여야 한다.
    • from: 매개변수를 받아 해당 타입 인서턴스를 반환
      • Date date = Date.from(instant);
    • of: 여러 매개변수를 받아 인스턴스 반환
      • LocalDate date = LocalDate.of(year, monthValue, dayOfMonth);
    • valueOf: from과 of의 자세한 버전
      • BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);
    • instance / getInstance : 인스턴스를 반환(같은 인스턴스라고 보장X)
      • StackWalker luke = StackWalker.getInstance(options);
    • create / newInstance: 매번 새로운 인스턴스를 생성해 반환한다.
      • 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(someList);

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


매개변수가 많은 정적팩터리와 생성자는 사용하기 어렵다.

매개변수가 4개를 넘거나, 대부분이 필수가 아니고 같은 타입 일 경우 빌더를 활용한다.

  • 점층적인 생성자 패턴
  • // 점층적인 생성자 패턴 Person person = new Person("김동혁", "Male", 31, "Korea"); // 자바빈 패턴 Person personDhkim = new Person(); personDhkim .setName("김동혁"); personDhkim .setGender("Male"); personDhkim .setAge(31); personDhkim .setCountry("Korea"); // 빌더패턴 Person person = new Person().builder() .name("김동혁") .gender("Male") .age(31) .country("Korea") .build(); Person person = new Person().Builder("김동혁", "Male") .age(31) .country("Korea") .build();

 

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


싱글턴(singleton) 방식으로 인스턴스를 하나만 생성해 관리한다.

public static final 필드 사용하는 방식

  • 생성자는 private로 선언해 감추고 Player.INSTANCE 를 초기화할때 호출한다.
public class Player {
	public static final Player INSTANCE = new Player();
	private Player() {}
}
  • 리플렉션 API를 사용할때 private 생성자 호출이 가능해 싱글턴이 깨질 수 있다.

정적 팩터리 메서드를 제공하는 방식

  • 싱글턴임을 명확히 표현하고, 차후 유연하게 변경할 수 있다.
  • 정적 팩터리를 제네릭 싱글턴 팩터리로 만들 수 있어 Player::getInstance 처럼 메서드 참조 방식으로 사용 가능하다.
public class Player {
	private static final Player INSTANCE = new Player();
	private Player() {}
	public static Player getInstance() { return INSTANCE };
}
  • 리플렉션 API를 사용할때 private 생성자 호출이 가능해 싱글턴이 깨질 수 있다.

Enum을 사용하는 방식

  • public static final 필드 방식과 유사하나 간결하고, 리플렉션API, 직렬화 문제를 방어 가능하다.
public enum Player {
	INSTANCE
}

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


생성자를 명시하지 않으면 컴파일러는 자동으로 기본 생성자를 생성한다.

컴파일러에 의한 인스턴스화를 막으려면 private 생성자를 명시적으로 생성해야 한다.

클래스 내부에서 생성자를 호출하지 않도록 오류를 발생시키는 것도 좋은 방법이다.

public class Player {
	private Player() {throw new CustomException()}
}

추상클래스로 만들면 자기 자신의 인스턴스화를 막을 수 잇으나, 이를 상속받은 하위 클래스에서 인스턴스화 할 수 있기에 인스턴스화를 완벽하게 방어할 수 없다.

그러나 위와 같이 private로 제한하면 상속이 방지된다.

모든 생성자는 명시적이든 묵시적이든 상위 클래스의 생성자를 호출(Object 클래스 참고)하는데 private는 외부에 공개되어 있지 않기에 호출할 수가 없다.

 

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


todo

대부분의 클래스는 하나 이상의 자원에 의존한다. 이런 클래스를 정적 유틸리티 클래스로 구현하면 데스트 하기에 유연하지 않다.

// 특정 사전 객체를 가져와 맞춤법 검사하는 클래스
public class SpellChecker {
    private static final Lexicon dictionary = new KoreanDictionary();
    private SpellChecker() {} // 객체 생성을 방지한다.
    
    public static boolean isValid(String word) { /* 구현 생략 */ }
    public static List<String> suggestions(String type) { /* 구현 생략 */ }
}

// 생성자에 필요한 자원 주입해서 객체 생성
public class SpellChecker {
    private final Lexicon dictionary;
    
    public SpellChecker(Lexicon dictionary) {
        this.dictionary = Objects.requireNonNull(dictionary);
    }
    // 그 외 메서드 생략
}

public SpellChecker(Supplier<? extends Lexicon> dicFactory) {
    this.dictionary = dicFactory.get();
}                                                                                                                    
  • 클래스가 하나 이상의 자원에 동작을 의존하고 있다면 자원을 생성자에 주입 또는 정적 팩터리나 빌더로 넘겨줘야 한다.

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


생성자로 객체를 만들면 매번 새로운 인스턴스를 생성한다.

String id1 = "Player";
String id2 = "Player"
System.out.println(id1 == id2); // true

String id3 = new String("Player");
String id4 = new String("Player");
System.out.println(id3 == id4); // false
  • 정적 팩터리 메서드로 불필요한 객체 생성을 줄일 수 있다.
  • 만약 String → Boolean으로 변환한다면 Boolean(String) 생성자 대신 Boolean.valueOf(String) 을 사용하는게 불필요한 인스턴스를 줄일 수 있다.

변수간 타입이 기본타입, 박싱타입으로 혼합되었을때 자동으로 변환해주는 오토 박싱(auto-boxing) 으로도 불필요한 객체가 만들어진다.

// long이 아닌 Long으로 선언되었다.
// 변수 i가 pdCnt에 더해질 때마다 불필요한 Long 인스턴스가 생성된다.
long pdCnt = 0L;
for (long i = 0; i < Integer.MAX_VALUE; i++) {
  pdCnt += i;
}

 

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


try-finally 구조는 여러 자원을 처리 할 경우 소스가 지저분해지는 경우가 발생한다.

또한 첫번재 예외가 두번째 에외를 먹어버리는 경우가 생긴다.

이를 방어하기 위해 try-with-resources를 사용하는 방법을 권고한다(Java7 이후)

'프로그래밍언어 > Java' 카테고리의 다른 글

스프링 회원 조회 기능 구현  (0) 2022.01.12
스프링 웹 방식  (0) 2022.01.11
[JAVA] OCR 기본활용  (0) 2021.12.29
SPRING MVC모델 게시판  (0) 2021.11.22
SPRING MVC모델 Overriding 활용  (0) 2021.11.11

댓글