본문 바로가기
  • Where there is a will there is a way.
개발/java

객체의 생성

by 소확행개발자 2020. 7. 9.

지난번 빌더 패턴을 보면서 객체의 생성과 관리에 대해서 다시한번 생각해보는 계기가 되었다. 

 

2020/07/06 - [개발/java] - 빌더로 생성자 대체하기

 

빌더로 생성자 대체하기

내가 자바로 웹 애플리케이션을 개발할때 인스턴스 생성에 대한 고민이 있었다. Goods goods = new Goods(); goods.setGoodsId(); goods.setGoodsTitle(); goods.setGoodsCategory(); goods.set... 이와 같이 상..

derekpark.tistory.com

그렇다면 어떨때 객체를 생성하고 어떨때 재사용하게끔 하는것이 좋을까 ?

 

effective java 3 에는 이렇게 설명한다.

 

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

String s = new String("derek");

String 은 대표적인 immutable 클래스 이다. 다음이 의미하는 것을 뭘까?

이 문장은 실행될 때마다 String 인스턴스를 새로 만든다. 완전히 쓸데없는 행위이다. 

String s = "derek";

이 코드는 새로운 인스턴스를 매번 만드는 대신 하나의 String 인스턴스를 사용한다. 또한 이와 똑같은 문자열 리터럴을 사용하는 모든 코드가 같은 객체를 사용함이 보장된다. 

 

또한 

Boolean(String) // 생성자 방식
Boolean.valueOf(String) // 팩토리 메소드 방식

생성자 대신 팩터리 메서드를 사용하는 것이 좋다. ( 실제로 java 9 에서 사용 자제로 지정되었다 )

 

다음을 생각해보자

static boolean isRomanNumeral(String s) {
	return s.matches(~);
}

public boolean matches(String regex) {
	return Pattern.matches(regex, this);
}


public boolean matches(String regex) {
	return Pattern.matches(regex, this);
}

public static boolean matches(String regex, CharSequence input) {
  Pattern p = Pattern.compile(regex);
  Matcher m = p.matcher(input);
  return m.matches();
}


public static Pattern compile(String regex) {
	return new Pattern(regex, 0);
}

문제점이 무엇일까?

s.matches 를 사용하게 되면 새로운 인스턴스를 반환한다. 이는 생성비용이 높다고 표현할 수 있다. ( 왜냐면 해당 메소드를 사용할 때마다 같은 패턴객체를 생성하므로 ) 

 

성능을 개선하려면 필요한 정규표현식을 표현하는 immutable 인스턴스 클래스를 초기화 과정에서 직접 생성해 캐싱해두는 방법이 있다. 

 

pulbic class RomanNomals {

	private static final Pattern ROMAN = Pattern.compile(~);
    
    
    static boolean isRomanNomal(String s) {
    	
        return ROMAN.matcher(s).matches();
    
    }


}

위의 이야기는 기존 객체를 재사용해야 한다면 새로운 객체를 만들지 마라 였다면 

 

새로운 객체를 만들어야 한다면 기존 객체를 재사용하지 말아야 하는 경우도 있을 것이다. 이럴때 방어적 복사가 필요한 상황일 수도 있다. 방어적 복사에 실패하면 언제 터져 나올지 모르는 버그와 보안 구멍으로 이어진다. 

 

자바를 사용할때도 클라이언트가 불변식을 깨뜨리려 혈안이 되어 있다고 가정하고 방어적으로 프로그래밍해야 한다.

 

어떤 객체든 그 객체의 허락 없이는 외부에서 내부를 수정하는 일은 불가능 하다. 하지만 주의를 기울이지 않으면 자기도 모르게 내부를 수정하도록 허락하는 경우가 생긴다. 

 

다음 코드를 보자 

 

public final class Period {

	private final Date start;
    private final Date end;

}

public Period(Date start, Date end) {

	if (start.compareTo(end) > 0){
    	throw new IllegalArgumentException();
    }
    this.start = start;
    this.end = end;

}

이 클래스는 불변일까?

 

여기서 확인해봐야할 건 Date 클래스이다. 

Date start = new Date();
Date end = new Date();
Period p = new Period(start, end);
end.setYear(78);

이렇게 되면 쉽게 불변식이 깨져버린다. ( Date 가 불변객체가 아니기 때문 )

그래서 자바 8 부터는 Date 대신에 java utill class 인 localDateTime 을 사용한다. 

 

public Period(Date start, Date end) {
	this.start = new Date(start.getTime());
    this.end = new Date(end.getTime());
    
    if(this.start.comareTo(this.end)> 0)
    	throw new IllegalArgumentException()
        
}

무엇이 다를까? 멀티스레드 환경이라면 원본 객체의 유효성을 검사한 후 ( 기존 ) 복사본을 만드는 그 찰나의 취약한 순간에 다른 스레드가 원본 객체를 수정할 위험성이 있기 때문이다. 

 

접근자 또한 다음과 같이 변경해야 한다.

 

Date start = new Date();
Date end = new Date();
Period p = new Period(start, end);
p.end().setYear(78); // p 내부 공격

 

public Date start() {

	return new Date(start.getTime());

}

// end 도 마찬가지 

 

우리가 내부 객체를 클라이언트에게 전에 반드시 심사숙고 해야한다. 안심할 수 없다면 방어적 복사본을 반환해야 한다. 

'개발 > java' 카테고리의 다른 글

클래스와 멤버에 대해서  (0) 2020.07.27
자바 직렬화에 대한 생각정리  (0) 2020.07.26
빌더로 생성자 대체하기  (0) 2020.07.06
Iterator design pattern  (0) 2020.05.28
JPA 2차 캐시란  (0) 2020.04.26

댓글