클린코드 5장(객체와 자료구조), 6장 (오류처리)

JAVA/클린코드|2020. 6. 29. 11:25

 

객체와 자료구조


  • 객체에서 자료를 세세하게 공개하는 것 보다 추상화를 통해 표현하는 것이 더 좋다.
  • 객체는 동작을 공개하고 자료를 숨긴다.
  • 복잡한 시스템을 짜다보면 새로운 함수가 아니라 새로운 자료타입이 필요한 경우가 발생하는데 이 때는 클래스와 객체 지향 기법이 적합하다. 하지만 새로운 함수가 필요하다면 절차지향 코드와 자료구조 형태가 더 적합한 코드이다.

 

 

 

 

오류처리


 

  • 오류를 일일히 처리하는 것보다 차라리 예외를 던저버리는게 더 깔끔하다.
  • 확인된 예외를 처리하기 위해서 하위 메소드에서 throws를 하게되면 상위 메소드에서 이 예외에 대한 명시가 되어야 하기 때문에 하위의 예외 때문에 상위 메소드가 수정되어야 하는 불상사가 발생하기 때문에 수정에 닫혀 있어야 하다는 OCP규칙을 위반한 것이다.
  • 예외에 의미있는 내용을 함께 던져서 의미 파악에 도움이 되게 하라.
  • 에러가 발생하였을 때 null을 반환하는 코드는 일거리를 늘릴 뿐만 아니라 호출자에게 일감을 더 주게 되는 문제가 있다.
  • 에러를 모두 한 곳에서 처리하게 되면 아래와 같이 귀찮게 된다. 이를 매번 open()을 사용하는 곳에서 처리하게 되면 엄청나게 귀찮게 되고 의존성이 있어 하나의 오류가 늘어나게 되면 모든 사용하는 곳에서 처리를 해줘야 한다. 그래서 이를 별도의 클래스를 만들어서 클래스 내부에서 innerPort.open()을 실행시키게 해서 그 곳에서만 에러 내용을 처리해주고 정재된 Exception만 다시 던지게 하는 것도 방법이다.
// 문제
public void open() {
	try { 
		innerPort.open();
	} catch (DeviceResponseException e) {
		...
  } catch (NetworkErrorException e) { 
		...
  } catch (BindingException e) {
    ....
  } catch ....

} 


// 감싸기 클래스 사용
public class LocalPort {

  private ACMEPort innerPort;

  public void open() {
		try { 
			innerPort.open();
		} catch (DeviceResponseException e) {
			// 내부에서 약속된 하나의 에러 패턴을 사용.
			throw new PortDeviceFailuer(e);
	  } catch (NetworkErrorException e) { 
			throw new PortDeviceFailuer(e);
	  } catch (BindingException e) {
			throw new PortDeviceFailuer(e);
	  } catch ....
	  }
	}
}

 

 

 

 

결론

에러처리를 잘못하면 코드가 굉장히 더러워진다. 실제로 많이 경험해봤고 아직도 어렵다. 중요한건 내부에서 발생한 에러로 인해 상위에서 호출하는 함수의 코드가 변해야 하는 OCP 위반 사항을 발생시키지 않도록 하는 것 같다.

댓글()

객체지향의 사실과 오해 1 ~ 2장

Book Review|2019. 10. 27. 17:45
객체지향의 사실과 오해
국내도서
저자 : 조영호
출판 : 위키북스 2015.06.17
상세보기

객체지향의 사실과 오해를 읽고 핵심적으로 생각되는 부분만 정리해봤다.

 

1. 협력하는 객체들의 공동체

현실세계의 객체

객체지향을 실세계와 대입하는 경우가 많다. (완벽하게 동일 시 할 수는 없지만 이해하기에는 편리함)

 

그럼 객체 지향을 현실세계에 대입했을 때 커피집을 생각해보면 손님, 캐리어, 바리스타는 개개인의 객체를 의미하고 각 객체는 서로간의 협력관계가 있고 그 속에서 자신의 책임을 다한다.

 

예를 들어보면 손님은 주문을 하고 캐리어는 계산을 받고 바리스타는 커피를 만드는 역할을 한다. 그리고 서로간의 협력 관계를 통해 주문을 하고 받고 커피를 만드는 작업을 진행한다.

 

이렇듯 객체지향에서 가장 중요한 개념은 각자의 역할, 책임 그리고 서로간의 협력이다.

그 중에서 협력은 객체지향에서 중요한 개념으로 서로간의 충분히 협력적이어야하고 다른 객체에 적극적으로 도움을 요청할 정도로 열린 마음을 가져야한다. 여기서의 협력적인 의미는 다른 객체에 수동적이라는 뜻이 아니라 요청에 응답하는 것을 의미하고 어떻게 응답하는 지는 객체 스스로 결정한다. 또한 객체는 충분히 자율적인 존재로 구성된 협력 공동체이다. 협력적일 수 있지만 결국 스스로 행동할 줄 알아야한다.

 

 

객체의 특징

- 객체는 상태와 행동을 가지고 있으며 스스로 자기 자신을 책임진다.

- 객체지향이 절차지향과 다른 가장 큰점은 실행시간에 어떤 행위를 할지 결정하는 것이다. 절차지향의 컴파일 시 결정되는 부분과 가장 큰 다른 점이다.

- 객체의 자율성으로 객체가 외부의 요청을 받는 메소드와 객체가 작업을 하는 구체적인 방법을 나눔으로써 매커니즘이 나눔으로써 매커니즘이 정해지는데 이게 바로 캡슐화이다.

- 객체는 다른 객체와 협력하기 위해 메시지를 전송하고 메시지를 수신한 객체는 메시지를 처리하는 데 적합한 메서드를 자율적으로 선택한다.

- 객체지향은 클래스 기반이 아니라 객체를 중심으로 바라봐야한다. 지나치게 클래스를 강조하는 프로그래밍 언어적인 관점은 객체의 캡슐화를 저해하고 클래스를 서로 강하게 결합시킨다. 어떤 클래스가 필요한가가 아니라 어떤 객체들이 어떤 메시지를 주고받으며 협력하는가를 중요시 여기자.

- 클래스의 구조와 메서드가 아니라 객체의 역할, 책임, 협력에 집중하라.

 

 

2. 이상한 나라의 객체

객체는 인간이 분명하게 구별할 수 있는 물리적인 또는 개념적인 경계를 지닌 어떤 것을 의미한다. 객체는 현실세계와 정확하게 같지 않고 모방하는 것이다. 그리고 그 현실세계를 기반으로 새로운 세계를 창조하는 것이 객체지향이다.

 

객체에 있는 상태를 특정시점에 객체가 가지고 있는 정보의 집합이다. 객체의 상태는 객체에 존재하는 정적인 프로퍼티와 동적인 프로퍼티로 구성된다. 프로퍼티는 단순한 값을 나타내는 속성과 다른 객체를 참조하는 Link로 구성된다. 

 

객체는 자율적인 존재로써 다른 객체가 값을 바꿀수 없다. 하지만 객체의 행동은 상태에 영향을 받고 변경시키는데 이러한 행동은 외부의 요청 또는 수신된 메시지에 응답하기 위해 동작하고 반응하는 활동이다. 행동의 결과로 객체는 자신의 상태를 변경하거나 다른 객체에 메시지를 전달할 수 있다.

 

객체의 상태는 캡슐속에 감춰두고 행동만 외부로 노출시켜서 그 행동으로 상태가 변경될 수 있도록 하는 것, 객체가 주체가 되어 행동하는 것을 캡슐화라고 한다.

 

 

 

1장과 2장에서 객체지향속에서 객체의 정확한 정의를 다시 정리할 수 있었던 것 같다. 

단순하게 코드로 보여주는게 아니라 이야기로 객체지향에 대해 이해하면서 볼 수 있어서 좋았다.

좋은 책이다.

 

출처 : 객체지향의 사실과 오해 (조영호)

댓글()

JPA 상속관계 매핑 전략

web/JPA|2018. 10. 31. 00:58

객체 지향으로 데이터베이스 중심 매핑을 변경하기 위해서 가장 애매한게 상속이다. 이런 상속관계속에서 테이블로 구현할 3가지 방법을 선택할 있다.


1) 각각의 테이블로 변환 : 각각을 모두 테이블로 만들고 조회할 조인을 사용.

2) 통합 테이블로 변환 : 테이블을 하나만 사용해서 통합 

3) 서브타입 테이블로 변환 : 서브 타입마다 하나의 테이블을 만드는 방식.



순서대로 하나씩 정리해보자.




각각의 테이블로 변환 (조인전략)

- 부모와 각각의 자식 엔티티를 모두 각자의 테이블로 만들고 부모의 기본키와 자식의 외래키를 사용하여 조인하여 사용한다.

-  자식 엔티티의 타입을 구별하기 위한 DTYPE 컬럼을 구분컬럼으로 추가하여 사용한다. (없어도 무관)


1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name = "DTYPE")
public abstract class Culture {
  
  @Id @GeneratedValue
  @Column(name = "CULTURE_ID")
  private Long id;
  
  private String name;
  private int price;
  
}
 
cs

-> 부모 추상 클래스 Culture 선언된 애노테이션에 대한 설명은 다음과 같다.


1
2
3
4
5
6
7
8
9
10
11
12
@Inheritance(strategy = InheritanceType.JOINED) : 조인전략 사용을 의미
@DiscriminatorColumn(name = "DTYPE") : 부모 클래스에 구분 컬럼으로 써 자식 테이블을 구분할 때 사용할 키로 사용된다. 기본값은 DTYPE이며 바꿔서 사용가능 (해당 기능을 사용하지 않아도 무관하다.)
 
 
@Entity
@DiscriminatorValue("M")
public class Movie {
 
  private String artiest;
  private String genre;
 
}
cs


기본적으로 사용하는 자식클래스 형식이다. 기본적으로 부모 테이블의 ID 컬럼명을 승계받아 사용 하지만 만약 변경하고 싶은 경우 클래스 위에 @PrimaryKeyJoinColumn(name = "MOVIE_ID")처럼 정의해서 사용할 있다.


조인전략의 장점은 저장공간을 효율적으로 관리하거나 테이블의 정규성이 지켜진다는 점이 있지만 조회할 조인이 많아지고 조회 쿼리시 귀찮아지며 수정이 발생하면 부모와 자식 테이블 두번을 해주어야한다.

 


 통합 테이블로 변환 (단일 테이블 전략)

부모의 속성과 자식의 속성을 하나의 테이블로 사용하는 것이다. 그리고 구분 컬럼 DTYPE 추가하여 해당 테이블이 어떤 자식 엔티티를 기반으로 만들어진 테이블인지 구분한다. (DTYPE 어떤자식인지 구분하기 위해 사용한다. 만약 속성이 같은 자식인 경우 구분이 안되기 때문이다.) 해당 전략의 단점으로는 자식 엔티티에 추가된 필드는 필수가 아니기 때문에 null 입력 되어도 된다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "DTYPE")
public abstract class Culture {
 
  @Id @GeneratedValue
  @Column(name = "CULTURE_ID")
  private Long id;
 
  private String name;
  private int price;
 
}
 
cs

-> Inheritance(strategy = InheritanceType.SINGLE_TABLE) 조인 타입을 단일 테이블 전략으로 지정한다. 


1
2
3
4
5
6
7
@Entity
@DiscriminatorValue("M")
public class Movie extends Culture {
 
  private String genre;
 
}
cs


 하나의 테이블로 지정되기 때문에 조인전략과 다르게 자식클래스를 구분할 있는 DTYPE 무조건 추가되어야 한다. 방식은 하나로 묶여있어 쿼리가 단순하다는 장점이 있지만 자식 엔티티의 필드의 null 무조건 허용해줘야 한다는 것과 자식마다 성격이 모두 달라도 테이블에 필요없는 필드까지 추가되어 있어야 하는 문제점이 있다.



- 서브타입 테이블로 변환 (구현 클래스마다 테이블 전략)

 자식 엔티티 마다 별도의 모든 테이블을 만들어 주는 방식이다. 부모와 자식을 키로 묶어서 사용하는 조인 방식과 다르게 자식 마다 부모의 속성과 자식의 속성을 모두 포함한 엔티티를 개별적으로 모두 만드는 것이다.


1
2
3
4
5
6
7
8
9
10
11
12
13
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
@DiscriminatorColumn(name = "DTYPE")
public abstract class Culture {
 
  @Id @GeneratedValue
  @Column(name = "CULTURE_ID")
  private Long id;
 
  private String name;
  private int price;
 
}
cs


- 단일테이블 전략과 다르게 모든 자식 테이블이 별도로 만들어지기 때문에 필요없는 필드가 추가되지 않아서 not null조건을 넣어줄 있다. 하지만 모두 만들어야하고 자식테이블끼리 합칠 성능의 문제가 발생할 있다. 가장 비효율적인 방법으로 일반적으로 조인이나 단일 테이블 전략중에서 사용한다.


- 참고 JAVA ORM 표준 JPA 프로그래밍


댓글()

클래스와 인터페이스 - 규칙 21 전략을 표현하고 싶을 때는 함수 객체를 사용하라.

JAVA/Effective Java|2018. 5. 29. 22:41

호출 대상에 대해 어떠한 작업을 수행하는 것을

전략 패턴이라고 한다.



1
2
3
4
5
class StringCompare {
 public int compare (String s1, String s2) {
   return s1.length() - s2.length();
 }
}
cs



 두 개의 문자열을 받아서 비교하는 클래스를 사용할 수 있는 전략 패턴이다.
비교가 필요할 경우 매번 StringCompare 클래스를 생성하지 말고, 싱글톤 패턴을 정의하여 가져다가 사용하면 더욱 편리하다.

하지만 이 StringCompare 클래스의 경우 객체를 메서드에 전달하기 위해서는 인자의 자료형이 String이어야 한다. 

따라서 Compareator 인터페이스를 정의 하여 이를 StringComapre 클래스가 구현하도록 해야 한다.



1
2
3
4
5
6
7
8
9
10
11
// 인터페이스
public interface Comparator<T> {
  public int compare(T t1, T t2);
}
 
class StringCompare implements Comparator<String> {
  @Override 
  public int compare (String s1, String s2) {
   return s1.length() - s2.length();
  }
}
cs




정리하면,
 함수 객체의 주된 용도는 전략 패턴이다. 전략 패턴을 사용하기 위해서는 인터페이스 (위에서는 Comparator<String>)를 선언하고, 실행가능 전략 클래스 생성 시 저 위에 인터페이스를 구현하도록 해야 한다.

단, 전략 패턴이 한번만 사용할 경우 익명 클래스로 사용할 수 있다.



1
2
3
4
5
6
Arrays.sort(StringArray, new Comparator<String>() {
  @Overide
  public int compare (String s1, String s2) {
    return s1.length() - s2.length();
  }
});
cs



만약 자주 사용 되는 경우에는,
public static final 지시자를 사용하여 외부에 공개 할 수 있다.



1
2
3
4
5
6
7
8
9
10
11
class Host {
  private static Class StringCompare implements Comparator<String>, Serializable {
   public int comapare(String s1, String s2) {
    return s1.length() - s2.length();
   }
  }
 
  // Compare<String>를 구현한 클래스로, 다형성을 이용하여 StringCompare를 받을 수 있다.
  // 외부에서는 Host.STRING_LENGTH_COMPARATOR.compare("dd", "ff"); 방식으로 사용할 수 있다.
  public static final Comparator<String> STRING_LENGTH_COMPARATOR = new StringCompare();
}
cs



출처 : 조슈아 블로크, 『 Effective Java 2/E』, 이병준 옮김, 인사이트(2014.9.1), 규칙21 인용.



댓글()

객체의 생성과 삭제 - 규칙 7 종료자 사용을 피하라

JAVA/Effective Java|2018. 5. 29. 07:45

일반적으로 자주 사용하는 finalizer 예측 불가능하며대체로 위험하고불필요하다.
 
그렇기에  필요로 하는 작업을 명시할 때에는사용해서는 안 된다.
 
문제 사항 1.
 
final 문장에서 Exception 발생하였을 경우에 하단에 기재한 
 
문장이 실행되지 않아 결국 문제를 유발할 수도 있다.



1
2
3
4
5
6
7
8
9
try {
  file = new FileWriter(new File("/test/tt"));
catch (IOException ex) {
  System.out.println("catch block");
finally {
  System.out.println(12/0);  // 강제로 Exception을 발생시킨다. 하단에 file.close() 문장은 실행되지 않는다.
  file.close();
  System.out.println("final block");
}
cs




 문제 사항 2.
 
 
Finalizer 사용하면 프로그램 성능이 심각하게 떨어진다.
 
 finalizer 성능 저하를 발생시키는 이유
1. finalizer 구현하면, GC 자신이 관리하는 finalizer 큐에 저장한다.
2. GC에서 사용하는 특정 쓰레드가 finalizer 큐를 읽고해제해도 되는지 검사한다.
3. 검사  해제 되도 된다면그때서야 해제한다.
 
 3번이 될 때까지 아직도 해제하지 않고 있는 것과검사하는 것에 에너지를 사용하기 때문에 성능에 나쁘다.

출처: <http://ikpil.com/1190>





그럼 반환하거나 삭제해야 하는 자원을 포함하는 객체의 클래스 작성은 어떻게 해야 할까?
 
이는 명시적인 종료 메서드를 하나 정의하여 사용하는 방법이 있다.
 
해당 객체가 주기적으로 해제가 되었는지 여부를 확인하기 위해 해당 객체 안에 그를 체크할  있는 
 
Private 변수를 정의해 두고주기적인 체크를 통해 해제가 되지 않았을 시에 이를 해제해 주어야 한다.
 
 
 
그럼 finalizer try-with-resources 문장이 나온 이후로 진짜로 필요가 없어졌는가?
 
자바가 다른 언어로 만들어진 어플리케이션과 상호 작용할 수 있는 인터페이스 JNI 같이
 
다른언어에서 작성된 메소드등을 수행하는 객체를 네이티브 객체라고 한다
 
Ex) 
CallAgent ca = new Ca(); // 네이티브 객체
Ca.execte();
 
이런 객체는 일반적인 객체가 아니므로 가비지 콜렉터가 이에 대한 리소스 반환을  수가 없다
 
그래서 이런 경우에는 finalizer 사용하여 네이티브 객체를 반환한다.
 
 
finalizer 구현  주의사항
 
 
부모 클래스에 finalizer 함수를 구현해 놓고 하위 클래스에서 finalizer  정의  경우에는 
 
다음과 같이 진행 해야 한다.
 


1
2
3
4
5
6
7
8
@Override
protected void finalize() throws Throwable {
try {
  // 하위 클래스 리소스 제거 부분 기입
finally {
  Super.finalize(); // 부모 클래스 리소스 해제 부분 메소드 호출
}
}
cs



렇게 기입을 해주어야 하위클래스 finalize 메소드가 실행되는 과정에 예외가 발생해도 상위클래스 종료자를 해제할  있기 때문이다.
 
 
만약 자식 클래스에서 상위클래스 finalize() 메서드를 해제하지 않을 경우큰문제가 되기 때문에 
 
이를 예방하기 위해서 모든 객체마다 익명클래스를 생성하여 finalize 정의하는 것이다.
 
Finalizer Guardian : 종료 보호자 패턴
 
 익명 클래스는  클래스를 객체로 가지고 있는 클래스의 객체를 종료시키는 역할을 한다.


1
2
3
4
5
6
7
class Foo {
    private final Object finalizerGuardian = new Object() {
        protected void finalize() throws Throwable {
            /* finalize outer Foo object */
        }
    }
}
cs





출처 : 조슈아 블로크, 『 Effective Java 2/E』, 이병준 옮김, 인사이트(2014.9.1), 규칙7 인용.



댓글()

객체의 생성과 삭제 - 규칙 6 유효기간이 지난 객체 참조는 페기하라.

JAVA/Effective Java|2018. 5. 29. 07:44

Java GC 있어 C C++처럼 순수 메모리 관리를 해주지 않아도 돼서 메모리 관리를 대부분 하지 않는다.
 
그러나  이상 참조하지 않는 객체에 대해 reference 제거하지 않는 경우에는 어떠한 경우에도 메모리 누수가 발생되게 된다.
 
이런 reference  단순하게 null 처리를 해줌으로써 해제된 참조를 해제   있다.
 
Null 바꾸게 되면, GC 해당 객체를 해제할  있는 객체라 생각하고 
 
반환해버린다.
 
하지만 이런 객체참조를 null 처리하는 것은 규범이라기보다는 예외적인 조치가 되어야 한다.
 
그렇다면 이런 만기참조를 해결하는데 좋은 방법이 어떤 것인가?
 
가장 좋은 방법은 해당 참조가 보관된 변수가 유효범위(scope) 벗어나게 하는 것이다.
 
 
또한 
 
캐시(cache) 메모리 누수가 흔히 발생할  있는 장소이다.
 
그래서 그런 캐시관리는 WeakHashMap으로 하면 키가 참조가 없어지는 순간 가비지콜렉터를 통해 
 
키와 값이 쌍으로 사라진다.
 
 참조 Weak reference
Weak reference에 의해 참조된 객체는 garbage collection이 발생하기 전까지는 객체에 대한 참조를 유지하지만 일단 garbage collection이 발생하면 무조건 수거된다. Weak reference가 사라지는 조건은 garbage collection의 실행 주기와 일치하는 것이다. 이를 이용하면 짧은 시간 동안 자주 쓰일 수 있는 객체를 캐쉬할 때 유용하게 이용할 수 있다.
 
 
그리고 메모리 누수가 흔히 발견되는   곳은 리스너 등의 역호출 되는 콜백 함수이다.
 
콜백함수를 사용한 후에는  그것에 대한 reference 해제해야 한다.

출처 : 조슈아 블로크, 『 Effective Java 2/E』, 이병준 옮김, 인사이트(2014.9.1), 규칙6 인용.



댓글()

객체의 생성과 삭제 - 규칙 5 불필요한 객체는 만들지 말라

JAVA/Effective Java|2018. 5. 29. 07:42

기능적으로 동일한 객체는 필요할 때 마다 만들지 않고 재 사용하는 것이 
더욱 효율적이다.

사례 1.
new String("str") 보다 "str"을 사용하라.

 반복문 속에서 String 문장을 사용해야 할 때 new String("test")를 통해서 객체를 새로 생성하는 것이 아니라, "test"와 같이 그 자체가 String 객체인 문장을 사용하는 것이 좋다.

그 이유는 다음과 같다.

(출처 : http://blog.vjvj.net/2017/04/effective-java-5.html)

두 가지 경우 모두 생성 시 heap 메모리에 객체가 생성된다. 
하지만 "test"와 같이 String 객체를 만드는 경우에는 heap에 String Constant Pool영역에 생성된다. 

그래서 사용자가 "test"라는 객체를 생성하여 작업하려 할때 String Constant Pool영역에서 존재여부를 확인하고 

해당 레퍼런스를 가지고 사용하게 된다.  

하지만 new String("test")에 경우에는 매번 새로운 heap 영역에 새로운 객체를 생성하기 때문에 좋지않다.

사례 2.
immutable class를 사용해야 하는 경우에는 생성자 대신 정적 팩토리 메서드를 이용하면 불필요한 객체 생성을 피할 수 있다.

immutable class인 boolean을 사용해야 하여 참, 거짓을 판단하고 싶을 때 두가지 방식으로 boolean 객체를 만든다고 생각하여 보자.



1
2
new Boolean("st".equals("st"));
Boolean.valueOf("st".equals("st"));
cs



위 두가지 경우에서 첫 번째 경우에는 Boolean 객체를 계속 생성하고, 두 번째 방법은 정적 팩토리 메서드 패턴을 이용하여 기존에 생성되어 있는 Boolean 객체를 반환 받기 때문에 더욱 효율적이다.

사례 3
변경 가능한 객체에 경우에 다음과 같은 경우를 피해야 한다.



1
2
3
4
5
6
7
8
public class Babo {
    private final Date birthDay = new Date(201711);
    
    public boolean isTodayBirth() {
        Date today = Calendar.getInstance(TimeZone.getTimeZone("GMT")).getTime();
        return birthDay.compareTo(today) == 0;
    }
}
cs




위에 경우 isTodayBirth() 메소드가 실행 될때 마다 Calendar와 TimeZone 객체가 생성되기 때문에 좋지 않다.

만약 이 메서드가 자주 사용되는 것일 경우에 static 블록에 Calendar와 TimeZone 객체를 생성되게 하고 가져다가 쓰면 계속 만들어지는 문제는 해결될 것이다.



1
2
3
4
5
6
7
8
9
10
11
private final Date birthDay = new Date(201711);
    
private static final Date today;
    
static {
    today = Calendar.getInstance(TimeZone.getTimeZone("GMT")).getTime();
}
    
public boolean isTodayBirth() {
    return birthDay.compareTo(today) == 0;
}
cs



하지만 만약, 한번도 isTodayBirth() 호출되지 않을경우에는 쓰지 않는데 구태여 만들어 버리니 오히려 더 낭비가 될 수 있다.

이럴때는 Lazy 방식으로 정말 필요할 때만 실행되도록 지정해 주어야 한다.

사례 4
java 1.5 부터 나온 자동 객체화 (autoboxing)은 Wapper Class와 primitive type을 섞어서 사용할 수 있게 해주는 기능이다. 

두 개 사이는 자동 변환이 되기때문에 괜찮을 것 같지만,
이 자동변환 자체가 성능을 많이 좌지우지 하기 때문에 조심해야 한다.

Long은 하단의 설명과 같이 long를 클래스로 감싼 클래스이기에 생성하기 위해서는 객체가 계속 생성된다.

그러므로 항상 특별한 경우가 아닌경우에는 기본 자료형(primitive type)을 사용할 것.

(기본 자료형은 객체가 아니기에 null이 안되기에 기본형을 감싼 클래스인 wrapper class를 사용하면 된다.)
기본형   대응 래퍼 클래스 
byte     Byte 
short    Short 
int      Integer 
long     Long 
float    Float 
double   Double 
char     Char 
boolean  Boolean


1
2
3
4
Long sum = 0L;
for (long l = 0; i < Integer.MAX_VALUE; i++) {
 sum += i;
}
cs




그렇다고 무분별한 객체 생성을 막는다고, 객체 풀을 만들어서 사용하는 것을 권하지 않는다.

데이터베이스 연결과 같은 경우에는 객체를 새로 만드는 것이 더 비용이 크기 때문에 객체 풀로 관리 하는 것이 좋으나 대부분의 경우가 객체 풀 관리보다는 새로 만드는 것이 더 유용하다.





출처 : 조슈아 블로크, 『 Effective Java 2/E』, 이병준 옮김, 인사이트(2014.9.1), 규칙5 인용.



댓글()

객체의 생성과 삭제 - 규칙 4 객체 생성을 막을 때는 private 생성자를 사용하라.

JAVA/Effective Java|2018. 5. 29. 07:39

객체 생성을 막기위해서는 
Abstract Class 이용하거나생성자를 private 생성하면 막을  있다.
 
하지만 이렇게 객체 생성을 막는다는 것은 객체지향 개념에서 벗어나는 행위일  있다.
 
하지만 다음과 같은 경우에 사용을 위해서 사용되기도 한다.
 
1.  자바의 기본 자료형 (primitive value) 또는 배열에 적용되는 메서드를  군대에 모아둘  유용하다.
-> Util성 성질을 가지는 클래스에서 유용
Ex) java.lang.Math, java.lang.Arrays
Math.abs(), Math.cos()
Arrays.asList()

2. 규칙 1번에서 사용하는 정적 메서드를 모아놓을 때도 사용할 수 있다.
Ex) java.util.Collections
Collections.emptyList(); 
Collections.emptyMap();


3. final 클래스에 적용할 메서드들을 모아놓을 때도 활용할 수 있다.
-> 클래스에 final을 붙히면 더 이상 상속 구현이 불가능한 클래스이다.
-> 명확한 정의가 되지 않은 클래스가 상속이 가능한경우, 그 클래스를 상속한 자식클래스는 그 클래스가 변경될 때마다 정상적으로 동작하지 않을 위험이 있기 때문에 이런 클래스는 final로 상속이 불가능한 클래스로 구현한 후, 이 클래스를 사용하기 위해서는 팩토리 정적 메소드 패턴을 이용하게 하는 것이 좋다.


그럼 유틸성 클래스를 사용하기 위해서 객체 생성이 불가능하게
만들려고 할때 좋은 방법이 있을까?

생성자를 생략한다
- 생성자를 생략할 경우 컴파일러가 public 기본 생성자를 만들어 버린다. 그러므로 좋은 방법이 아니다.

abstract 클래스로 선언한다
- 해당 추상클래스를 상속받아서 객체로 만들 수 있기 때문에 좋지 않다. 

private 생성자를 클래스에 넣어서 객체 생성을 방지한다.
- 기본 생성자가 없을 경우에 기본 생성자를 컴파일러가 생성하니 private 생성자를 추가해 놓으면 객체 생성을 방지할 수 있다.
- private 생성자가 있는 클래스는 자식클래스를 가질 수 없기 때문에 하위클래스를 이용하여 객체를 생성하는 경우를 막을 수 있다.



1
2
3
4
5
6
7
Ex)
public class CommonUtil {
 private CommonUtil() {
  // 행여나 객체가 생성될 경우 에러를 던져서 그 행위를 방지한다.
  throw new AssertionError();
 }
}
cs





출처 : 조슈아 블로크, 『 Effective Java 2/E』, 이병준 옮김, 인사이트(2014.9.1), 규칙4 인용.



댓글()