java-squid / effective-java

effective java 3e study
105 stars 38 forks source link

[아이템 06] 불필요한 객체 생성을 피하라 #6

Closed wooody92 closed 4 years ago

guswns1659 commented 4 years ago

p.32 (1번째 문단 마지막) Pattern은 입력받은 정규표현식에 해당하는 유한 상태 머신(finite state machine)을 만들기 때문에 인스턴스 생성 비용이 높다

정규표현식용 Patten 클래스와 유한 상태 머신이 낯선데 이 2가지 개념을 간략하게 설명해주시면 좋을 것 같습니다.

guswns1659 commented 4 years ago

p.33 (2번째 문단) 어댑터를 생각해보자. 어댑터는 실제 작업은 뒷단 객체에 위임하고, 자신은 제2의 인터페이스 역할을 해주는 객체다.

디자인 패턴 중 하나로 어댑터를 본 것 같은데 어댑터 패턴의 예시를 한번 보여주시면 좋을 것 같습니다.

david215 commented 4 years ago

p.32 (1번째 문단 마지막) Pattern은 입력받은 정규표현식에 해당하는 유한 상태 머신(finite state machine)을 만들기 때문에 인스턴스 생성 비용이 높다

정규표현식용 Patten 클래스와 유한 상태 머신이 낯선데 이 2가지 개념을 간략하게 설명해주시면 좋을 것 같습니다.

이거는 제가 요즘 수업에서 듣는 내용이라 예시와 함께 간단하게 설명해볼게요. 유한 상태 머신을 간단하게 설명하면:

  1. 시작, 도착 상태를 포함한 상태들 (그래프에서의 노드)
  2. 상태 사이의 전이 (그래프에서의 엣지)로 이루어져 있는데

쉽게 예를 들어 "squid"라는 패턴을 입력 문자열이 포함하고 있는지 확인하려면:

이런 식으로 동작을 하는 머신인데 보통 꽤 간단한 정규표현식이라도 유한 상태 머신으로 변환하면 엄청나게 사이즈가 커집니다. 예를 들어 0, 1로 이루어진 문자열 중에 마지막에서 네 번째 문자가 1인 문자열을 표현하는 정규표현식은 (0|1)*1(0|1){3}인데 유한 상태 머신으로 변환하면 screenshot-ivanzuzak info-2020 09 22-14_22_20 이렇게 됩니다... 일반적인 탐색 문제와는 다르게 모든 상태와 전이를 찾아놓고 매칭을 하기 때문에 생성비용이 높지만 생성 이후에는 매칭을 빠르게 할 수 있기 때문에 컴파일러를 만들 때 꼭 사용되는 개념입니다.

Java Pattern와 연결지어 생각해보자면 Pattern 객체를 사용할 때 한 번 compile()하고 반복적으로 사용할 수 있는 시나리오에서 사용하는 것이 좋습니다.

david215 commented 4 years ago

Generally speaking, however, maintaining your own object pools clutters your code, increases memory footprint, and harms performance.

아이템 마지막 부분에 데이터베이스 커넥션 객체를 예시로 들며 객체 풀을 직접 관리하는 것은 안 좋다고 하는데 그 이유를 잘 모르겠네요.

kses1010 commented 4 years ago

이걸 보니 지난번에 JK에 요청했던 오토마타이론이 생각나는 군요 그때 자료 보고 뭔소린지 몰랐는데 데이빗올린 걸 보니 어떤걸 의미하는지 알겠네요

kses1010 commented 4 years ago

@guswns1659

디자인 패턴 중 하나로 어댑터를 본 것 같은데 어댑터 패턴의 예시를 한번 보여주시면 좋을 것 같습니다.

출처

어댑터 패턴은 의외로 간단한 패턴이었습니다. image

다음 사진과 같은 클래스 구조를 나누고, 클래스를 간략하게 구현한다면 클라이언트 로직에서는 이런식으로 가능합니다.

public class AdapterPatternDemo {
   public static void main(String[] args) {
      AudioPlayer audioPlayer = new AudioPlayer();

      audioPlayer.play("mp3", "beyond the horizon.mp3");
      audioPlayer.play("mp4", "alone.mp4");
      audioPlayer.play("vlc", "far far away.vlc");
      audioPlayer.play("avi", "mind me.avi");
   }
}

출력

Playing mp3 file. Name: beyond the horizon.mp3
Playing mp4 file. Name: alone.mp4
Playing vlc file. Name: far far away.vlc
Invalid media. avi format not supported

어댑터 패턴의 가장 대표적인 예시는 바로 InputStreamReader 가 있습니다.

BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

BufferedReader 클래스를 까서 위 구문이 실행될때 사용되는 생성자를 보면 아래와 같이 Reader 타입을 받습니다.

public BufferedReader(Reader in) {
    this(in, defaultCharBufferSize);
}

하지만 System.in 은 InputStream 타입을 반환합니다.

public final static InputStream in = null;

자바의 InputStream 은 바이트 스트림을 읽습니다. 하지만, BufferedReader 는 캐릭터인풋 스트림을 읽습니다. 둘은 호환되지 않지만, 이 둘을 연결시켜 주는 어댑터가 InputStreamReader 클래스입니다. UML 로 보면 아래와 같은 구조. image

어댑터 패턴 정리

사용해야하는 인터페이스가 현재의 시스템과 호환되지 않는다고 해서 현재의 시스템을 변경을 해야하는 것은 아니다.

어댑터 추가

이러한 패턴은 외부 라이브러리를 사용할 때 활용 될 수 있다. 실제 비즈니스 로직을 구현하는데는 외부 라이브러리가 많이 혼재되어 있다면 굉장히 이해하기 어려울 것이다. 이런 경우 내가 사용하고자 하는 인터페이스를 잘 정의하고 그의 구현에서 외부 라이브러리의 클래스들을 사용한다면 훨씬 깔끔하게 우리의 비즈니스 로직을 구현할 수 있을 것이다.

kses1010 commented 4 years ago

@david215

아이템 마지막 부분에 데이터베이스 커넥션 객체를 예시로 들며 객체 풀을 직접 관리하는 것은 안 좋다고 하는데 그 이유를 잘 모르겠네요.

한국어 판은 그 다음 줄에 이런식으로 설명이 있습니다.

DB 연결 같은경우 생성비용이 비싸서 재사용하는 경우가 있다.

객체 풀은 객체를 필요로 할때 풀에 요청을 하고, 반환하고 일련의 작업을 수행하는 디자인 패턴으로 많은 수의 인스턴스를 생성할때 혹은 무거운 오브젝트를 매번 인스턴스화 할때 성능 향상을 위해서 사용합니다. 문제는 객체 풀이 코드를 헷갈리게 만들고 잘못 설정한 경우 메모리 사용량을 늘리고 성능을 떨어뜨립니다. 게다가 요즘 JVM의 가비지 컬렉터는 상당히 잘 최적화되어서 가벼운 객체용으로 다룰 때는 사용하지 않은게 더 낫다고 하네요.

kses1010 commented 4 years ago

찾아보니 주의 사항이 다음과 같습니다.

102092 commented 4 years ago

advanced 질문입니다.

코쿼 수업중에 한번 언급되었던 내용 같은데,, String을 생성하는 방법에 대해서 간단하게나마 알아보면 좋을듯 합니다. 예를 들면,

String str1 = new String("new!!");
String str2 = "new!!";

String contant pool과 관련 있을 것 같긴 한데... 그리고 위 두 방법 간에 어떻게 차이가 있는 지에 대해 알아보면 좋을 듯해요!

image