본문 바로가기

이팩티브 자바

객체 생성과 파괴

728x90

머리말

자바 공부를 첫 시작하면서 이팩티브 자바 책을 선정하여 자바를 공부하려고 합니다. 저는 기존에 C/C++ Typescript 언어를 통하여 객체지향 개념이 어느정도 익숙하다고 생각하여 어느정도 난이도가 있는 책을 선정하게 되었습니다. 자바에서는 과연 객체를 어떻게 생성하고, 소멸시킬지 파해쳐 나아가 봅시다😀

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

매개 변수가 많은 생성자일경우 클라이언트가 어떤 생성자를 사용해야할지 명확하게 구분 할 수 없으며 또한 필요하지않는 파라미터까지 제공을 해야하는 문제가 발생한다면 정적 팩터리 매서드를 사용하여 해당 문제를 해결할 수 있다.

정적 팩터리 메서드 사용시 장점

  • 어떤 생성자를 생성할 것인지 메소드의 이름으로 유추를 할 수 있어 그 생성자가 어떤 역할을 하는지 명확하게 구분 시켜줄 수 있다.
  • 호출 될때마다 인스턴스를 생성하지 않도록 할 수 있다. ( 플라이웨이트 패턴 )
  • 반환 타입을 상위 타입으로 지정하여 하위타입도 반환 가능하게 만들 수 있다. ( 리스코프 치환 원칙 )
  • 매개 변수에 따라서 매번 다른 클래스의 객체를 반환 할 수 도 있다. ( 팩터리 메서드 패턴 )
  • 정적 팩터리 메서드를 작성하는 시점에는 반환할 객체의 클래스가 존재 하지 않아도 된다. ( 인터페이스나 추상클래스에 의존 )

정적 팩터리 메서드 사용시 자주 사용하는 함수

  • from : 매개변수를 하나 받아서 해당 인스턴스 타입으로 반환하는 형변환 메서드
  • of : 여러 매개변수를 받아 적합한 타입의 인스턴스로 반환하는 메서드
  • valueOf : from과 of의 더 자세한 버전
  • instance 혹은 getInstance : (매개변수를 받는다면) 매개변수로 명시한 인스턴스를 반환하지만, 같은 인스턴스임을 보장하지 않는다.
  • create 혹은 newInstance : instance 혹은 getInstance와 같지만, 매번 새로운 인스턴스를 생성해 반환함을 보장한다.
  • getType : getInstance와 같으나, 생성할 클래스가 아닌 다른 클래스에 팩터리 메서드를 정의할때 쓴다. “Type”은 팩터리 메서드가 반환할 객체의 타입이다.
  • newType : newInstance와 같으나, 생성할 클래스가 아닌 다른 클래스에서 팩터리 메서드를 정의할때 쓴다. “Type”은 팩터리 메서드가 반환할 객체의 타입이다.

<aside> ✏️ 핵심정리 정적 팩터리 메서드와 public 생성자는 각자의 쓰임새가 있으니 상대적인 장단점을 이해하고 사용하는 것이 좋다. 그렇다고 하더라도 정적 팩터리를 사용하는게 유리한 경우가 더 많으므로 무작정 public 생성자를 제공하던 습관이 있다면 고치자

</aside>

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

생성자 혹은 정적팩토리를 사용할때 공통된 제약사항이 있는데 바로 매개변수가 많아지면 적절하게 대응하기 어렵다. 이럴때 사용하것것이 빌더 패턴이다. 점층적 생성자 패턴을 사용 할 수 있지만, 매개변수가 많아진다면 클라이언트 코드는 코드를 작성하거나 읽기가 어렵다.

많은 매개변수가 필요하는 생성자가 필요할때 Lombok의 Builder 를 사용하여 빌더패턴을 사용하자.

<aside> ✏️ 핵심정리 생성자나 정적 팩터리가 처리해야 할 매개변수가 많다면 빌더 패턴을 선택하는게 더 낫다. 매개변수 중 다수가 필수가 아니거나 같은 타입이면 특히 더 그렇다. 빌더는 점층적 생성자보다 클라이언트 코드를 읽고 쓰기가 훨씬 간결하다.

</aside>

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

싱글턴이란 인스턴스를 오직 하나만 생성 할 수 있는 클래스를 말한다.

  1. public static final 를 사용하여 맴버변수로 제공한다.
  2. 정적 팩터리 메서드를 public static 멤버로 제공한다.
  3. 싱글턴을 만드는 방법중 하나인 원소가 하나인 열거형 타입으로 선언한다.

대부분의 상황에서 원소가 하나뿐인 열거 타입이 싱글턴을 만드는 가장 좋은 방법이다.

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

Java는 기본적으로 생성자를 명시하지 않는다면 기본 생성자를 제공한다. 의도치않게 인턴스화를 할 수 있다. 또한 private 으로 지정하지 않는다면 추상 클래스로 만드는 것으로 인스턴스를 막을 수 없다.

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

테스트하고 유지보수 하기 쉬운 코드를 작성하기위해서는 객체를 생성하고자 할때 만약 해당 클래스의 다른 클래스가 의존한다면구체에 의존하면 안되고 추상화에 의존해야한다. ( 의존성 역전 법칙 )

<aside> ✏️ 핵심정리 클래스가 내부적으로 하나 이상의 자원에 의존하고, 그 자원이 클래스에 동작의 영향을 준다면 싱글턴과 정적 유틸리티 클래스는 사용하지 않는것이 좋다. 이 자원들은 클래스가 직접 만들게 해서도 안된다. 대신 필요한 자원을 생성자에게 넘겨주자. 의존 객체 주입이라 하는 이 기법은 클래스의 유연성, 재사용성, 테스트 용이성을 기가막히게 개선해준다

</aside>

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

똑같은 기능의 객체를 매번 생성하는것보다 객체 하나를 재사용하는 편이 나을때가 많다.

String test = new String("test"); // 생성자가 2번 호출됨
String tset2 = "test2"

String.matches는 정규표현식으로 문자열 형태를 확인하는 가장 쉬운 방법이지만, 성능이 중요한 상황에서는 반복해 사용하기에 적합하지 않다. Patterns 인스턴스를 만들어서 static final 필드로 끄집어내고 이름을 지어주고 해당 필드를 활용한다.

코드를 복잡하게 만들고 성능은 크게 개선되지 않기때문에 보통은 지연초기화(Lazy Initialization)를 권장하지 않는다.

**오토박싱(auto boxing)**은 기본타입과 박싱된(Wrapping)된 기본 타입을 섞어쓸때 자동으로 상호 변환해주는 기술이다. 박싱된 타입 보다는 기본타입을 사용하고, 의도지않은 오토박싱이 숨어 들지 않도록 주의해야한다.

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

	return sum;
}

다 쓴 객체를 참조를 해제하라

자기 메모리를 직접 관리하는 클래스라면 프로그래머는 항시 메모리 누수에 주의 해야한다**. 다 쓴 참조(obsolete reference)란** 앞으로 다시 쓰지 않을 참조의 뜻이다. 자기 메모리를 관리하는 클래스라면 가비지 컬랙터에게 명시적으로 참조를 null값을 부여하여 가비지 컬렉터에게 알려야한다.

캐시 역시 메모리 누수를 일으키는 주범이고, WeakHashMap을 활용하여 캐시를 만들자. 캐시를 만들때 보통은 캐시 엔트리의 유효 기간을 정확이 정의하기 어렵기때문에 시간이 지날수록 엔트리의 가치를 떨어뜨리는 방식을 흔히 사용한다.(Scheduled ThreadPollExecutor 같은) 백그라운드 스레드를 활용하거나 캐시에 새 앤트리를 추가할 때 부수 작업으로 수행하는 방법이 있다.

메모리를 일으키는 주범은 **리스너(listner) 혹은 콜백(callback)**이다. 클라이언트가 콜백을 등록하고 명확히 해지하지 않는다면, 뭔가 조치해주지 않는 한 콜백은 쌓여갈 것이다. 이럴때 약한 참조(weak reference)로 지정하면 가비지 컬렉터가 즉시 수거해간다.

finalizer와 cleaner 사용을 피하라

finalizer는 가지비 컬랙터에 의해서 실행되기 때문에 예측 할수 없고 위험 할 수 있어 일반적으로 불필요하다. cleaner또한 덜 위험하지만, 여전히 예측 할 수 없고, 느리다 따라서 상태를 영구적으로 수정하는 작업은 절대로 finalizer와 cleaner에 의존해서는 안된다.

핵심정리

<aside> ✏️ cleaner는 안전망 역할이나 중요하지 않는 네이티브 자원을 회수하는 용도로만 사용하자. 물론 이런 경우라도 불확실성과 성능 저하에 주의해야한다.

</aside>

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

꼭 회수해야하는 자원을 다룰 때는 try-finally 말고, try-with-resource를 사용하자. 예외는 없다. 코드는 더 짧고 분명해지고, 만들어지는 예외 정보도 훨씬 유용하다.

<aside> ✏️ AutoCloseable 인터페이스를 구현해야 합니다.

</aside>

회고

GOF 디자인 패턴중 생성자 패턴에서 이야기하던 싱글턴 패턴과 팩터리 메서드 패턴, 빌더 패턴을 다시한번더 상기시키는 시간이 되었습니다. 최근에 사이드 프로젝트를 진행하면서 Lombok의 @Builder 어노테이션을 사용했었는데, 해당 어노테이션이 내부적으로 어떻게 구현되어있는지 살펴 볼 수 있는 시간이 되었습니다.

오토박싱이라는 개념을 이번에 알게되었는데 예컨대 기본타입인 int와 박싱타입(Wrapper)인 Integer는 다른타입이고 해당 타입을 섞어서 사용할경우 오토 박싱이 일어나고 연산이 느려진다는걸 알게되었습니다. 다음에 알고리즘 문제를 풀때 유의해야겠네요.

저는 회사에서 C/C++ 을 주로 개발하고있는데, 메모리가 언제 어디서 사라지는지 다른 언어 모델보다 더 잘 알고 있어야하는데, 자바또한 무시못할거 같습니다. 자바는 메모리를 관리해주는 언어이지만 가비지 컬렉터는 최후의 수단이라고 생각하고 코드를 짜야겠다고 생각했습니다. 특히나 메모리를 관리하는 객체라면요.

try-with-resource라는 개념을 알게되었고 나중에 기회가 된다면 AutoCloseable 인터페이스를 구현하여 반납 해주어야 하는 자원들에 한에서 공부를 더 해봐야할 것 같네요.

728x90

'이팩티브 자바' 카테고리의 다른 글

열거 타입과 에너테이션  (0) 2024.03.17
제네릭  (0) 2024.03.17
클래스와 인터페이스  (0) 2024.03.17
모든 객체의 공통 메서드  (0) 2024.03.17