반응형

이슈

rewriteBatchedStatements=true

고객사에서 위와 같은 mysql 옵션을 추가하자 다음과 같은 오류가 발생하였다.

java.lang.NullPointerException
at com.mysql.cj.ClientPreparedQuery.computeMaxParameterSetSizeAndBatchSize(ClientPreparedQuery.java:66)

 

오류가 발생한 코드는 Batch JDBC 코드를 작성할 때 자주 사용하는 보일러 플레이트 코드로 executeBatch()가 실행될 때 오류가 발생하였다.

Blob blob = ...
try (Connection connection = DBUtil.getInstance().getConnection();
         PreparedStatement preparedStatement = connection.prepareStatement(query)) {
        connection.setAutoCommit(false);
        for (TransactionBatch batch : batches) {
            try {                   
                preparedStatement.setString(1, batch.getDeviceID());
                preparedStatement.setBlob(2, blob);
                preparedStatement.addBatch();
            } catch (Exception e) {
               e.printStackTrace();
            }
        }

        preparedStatement.executeBatch();
        blob.free();

 

해결

연구소에서 재현을 위해 rewriteBatchedStatements=true 옵션을 추가했는데 동일한 현상이 발생하지 않았고 추후 확인해보니 고객사와 mysql-connector-java 버전에 차이가 있었다.

연구소는 버전이 5.1.38, 고객사는 8.0.19였다. 버전을 고객사와 동일하게 맞춘 후 동일한 현상이 재현되었다.

구글링을 해보니 setBlob() 또는 setBinaryStream()을 사용할 때 문제가 발생하며 해당 메서드 대신 setBytes()를 사용해야 했다. setBytes()를 사용하면 오류가 발생하지 않는다.

이유

이유를 좀 더 찾아보자.

 

2015년 6월에 스택오버플로우에서 해당 관련 글이 올라왔다. (computeMaxParameterSetSizeAndBatchSize가 PreparedStatement에 있는 것으로 보아 버전이 달라보인다.). 해당 답변에서는 rewrite를 하기 위해 사이즈를 재 계산해야 하지만 input stream에서는 할 수 없다라는 의미처럼 보이지만, 정확히 이해가 되지 않는다.

 

 

2017년 3월에 mysql 커뮤니티에 버그 리포트(#85317)로 해당 관련 글이 올라왔다. mysql 개발자가 해당 글을 당일 확인하고 버그로 판단하고 해결되기까지...........무려 5년이 걸렸다.

 

5년 뒤인 8.0.29 버전에 이슈가 패치되었다고 한다. => 링크

  • When the connection property rewriteBatchedStatements was set to true, inserting a BLOB using a prepared statement and executeBatch() resulted in a NullPointerException. (Bug #85317, Bug #25672958)

setBlob()을 사용한 기존 코드를 유지한 채 8.0.29 버전으로 수행하니 더 이상 오류가 발생하지 않았다.

 

8.0.19와 8.0.29로 코드를 비교해서 어떠한 점이 패치되었는지 판단하려 하였으나, 많은 부분이 변경 되어 확인하기가 어려웠다.

 

8.0.19 버전에서 NPE가 발생한 소스는 다음과 같다.

   @Override
    protected long[] computeMaxParameterSetSizeAndBatchSize(int numBatchedArgs) {
        long sizeOfEntireBatch = 0;
        long maxSizeOfParameterSet = 0;

        for (int i = 0; i < numBatchedArgs; i++) {
            ClientPreparedQueryBindings qBindings = (ClientPreparedQueryBindings) this.batchedArgs.get(i);

            BindValue[] bindValues = qBindings.getBindValues();

            long sizeOfParameterSet = 0;

            for (int j = 0; j < bindValues.length; j++) {
                if (!bindValues[j].isNull()) {

                    if (bindValues[j].isStream()) {
                        long streamLength = bindValues[j].getStreamLength();

                        if (streamLength != -1) {
                            sizeOfParameterSet += streamLength * 2; 
                        } else {
                            int paramLength = qBindings.getBindValues()[j].getByteValue().length; // NPE 발생
                            sizeOfParameterSet += paramLength;
                        }
                        
                        ....

 

getByteValue()를 하는 중 배열에 들어 있는 값이 null이라 발생하지 않았을 까 싶다..

 

 

 

반응형
반응형

swagger-ui는 아래 예제처럼 url 이라는 쿼리 파라미터로 외부 리소스의 API 스펙을 임포트하여 사용할 수 있다.

https://petstore.swagger.io/?url=https://darosh.github.io/openapi-directory-lite/specs/github.com/v3.json

 

3.3.8 이후 버전부터는 XSS 보안 이슈로 인하여 해당 옵션을 일반적으로 사용할 수 없도록 변경하였다.

해당 보안 이슈는 다음을 참고한다.

 

사용해야 한다면 재량적으로 쿼리 파라미터에 queryConfigEnabled=true을 주어 사용할 수 있다.

해당 패치 노트는 다음을 참고한다.

 

필자는 swagger-ui를 v3.36.1을 사용하고 있었으며 이후 springdoc 1.6.9 버전으로 마이그레이션을 진행했다.

springdoc 1.6.9 버전은 swagger-ui 4.11.1 버전을 포함하고 있었기 때문에 위와 같은 문제를 직면했다.

 

필자는 특이하게도 springdoc을 이용하여 기본 url인 localhost:8080/swagger-ui/index.html을 사용하지 않고, 특별한 방식으로 swagger-ui를 import해서 사용했다.

관련하여 다음을 참고한다.

@Configuration
@SpringBootApplication
@EnableTransactionManagement
public class App implements WebMvcConfigurer {

  ...

  @Override
  public void addResourceHandlers(ResourceHandlerRegistry registry) {
    registry.addResourceHandler("/tester/**")
        .addResourceLocations("classpath:/META-INF/resources/static/",
            "classpath:/META-INF/resources/webjars/swagger-ui/4.11.1/");
  }

 

따라서 swagger를 사용할 때는 localhost:8080/tester/index.html를 사용했고 url 쿼리 파라미터를 위해 queryConfigEnabled를 사용하였으나 동작하지 않았다.

 

혹시나 하여 기본적으로 제공하는 springdoc의 localhost:8080/swagger-ui/index.html에서도 동작하지 않았다.

 

이후, springdoc에서 제공하는 설정값을 아래와 같이 세팅하여 기동하니 queryConfigEnabled를 주지 않아도 url 옵션이 동작하였다.

springdoc:
  swagger-ui:
    query-config-enabled: true

 

 

반응형
반응형

디자인 패턴 중 일반적으로 가장 처음에 배우는 전략 패턴을 자바로 어떻게 작성해야 하는지에 대한 내용이다.

전략 패턴에 대한 설명은 생략한다.

 

문자열 비교를 해주는 Util이 있다고 가정해보자.

public class StringCompareUtil {

	/**
	 * 일반 문자열 비교
	 */
	public static int compare(String s1, String s2) {
		return s1.compareTo(s2);
	}

	/**
	 * 문자열 길이를 이용한 비교
	 */
	public static int compareLength(String s1, String s2) {
		return s1.length() - s2.length();
	}

}

 

만약 새로운 문자열 비교 기능이 필요하다면 위 클래스를 수정해야 한다.  OCP를 위반하기 때문에 인터페이스를 이용하여 클래스를 변경해보자.

public interface Comparator {
	public int compare(String s1, String s2);

}

public class NormalStringCompare implements Comparator {

	public int compare(String s1, String s2) {
		return s1.compareTo(s2);
	}

}

public class LengthStringCompare implements Comparator {

	public int compare(String s1, String s2) {
		return s1.length() - s2.length();
	}

}

 

인터페이스를 이용하였고 각 기능을 제공하는 클래스를 각각 구현하였다. 이런 식으로 작성한다면 해당 기능을 사용하는 클라이언트 입장에서는 어떠한 구현체여도 인터페이스를 사용하기만 하면 된다.

 

문자열만 비교하는 인터페이스로 사용하기에 아쉽기 때문에 제네릭을 추가하자.

public interface Comparator<T> {
	public int compare(T s1, T s2);

}


public class NormalStringCompare implements Comparator<String> {

	public int compare(String s1, String s2) {
		return s1.compareTo(s2);
	}

}

public class LengthStringCompare implements Comparator<String> {

	public int compare(String s1, String s2) {
		return s1.length() - s2.length();
	}

}

 

새로운 비교 기능을 위해 클래스를 새롭게 구현해도 되지만, 기능이 추가될 때마다 클래스의 개수가 계속 늘어나게 된다. 만약 해당 비교 기능을 특정한 클라이언트 구현체에서만 사용한다면 익명 클래스를 이용해서 해당 위치에서 작성해주자.

		new Comparator<String>() {
			public int compare(String s1, String s2) {
				// TODO
			}
		};

 

위와 같이 익명 클래스를 생성하면 해당 위치에서 새로운 객체가 계속 만들어지므로 해당 익명 클래스를 객체로 갖는 private static final 필드에 저장하고 재사용하는 것을 고려하자.

 

여기까지 책에 대한 설명이다. 참고로 블로그 저자는 2판을 보고 작성하고 있는데, 3판에서는 람다에 대한 챕터로 해당 내용을 새롭게 구성하여 설명하고 있다.

 

이제 익명 클래스 대신에 람다를 사용하는 것을 추천한다. 람다에 대한 설명은 다른 블로그 글을 참조하기 바란다.

 

반응형
반응형

이 챕터는 자바를 처음 배울 때 계층형(추상) 클래스를 사용하는 이유를 설명하는 내용이라고 생각해도 될 것 같다. 그만큼 내용이 어렵지 않으므로 간단하게 설명한다.

 

1. 태그 클래스란?

태그 클래스란 특정한 변수를 기준으로 분기하여 두 가지 이상의 기능을 제공하는 클래스를 의미한다.

public class Figure {
	enum Shape {
		RECTANGLE, CIRCLE
	};

	final Shape shape;

	double length;
	double width;

	double redius;

	Figure(double length, double width) {
		this.shape = Shape.RECTANGLE;
		this.length = length;
		this.width = width;
	}

	Figure(double redius) {
		this.shape = Shape.CIRCLE;
		this.redius = redius;
	}

	double area() {
		switch (shape) {
		case RECTANGLE:
			return length * width;
		case CIRCLE:
			return Math.PI * (redius * redius);
		default:
			throw new AssertionError();
		}
	}
}

 

A 기능을 위한 생성자와 인스턴스 필드들이 존재하며, B 기능을 위한 생성자와 인스턴스 필드들이 각각 존재한다.

 

이러한 경우 단점은 다음과 같다.

 

1. 가독성이 떨어진다.

2. 필요 없는 기능을 위한 필드도 함께 생성되므로 메모리 요구량이 늘어난다.

3. 또 다른 기능이 생긴다면 해당 클래스를 수정해야 하며, swtich문도 모두 수정해야 한다.

 

2. 해결 방안

공통 기능은 추상(abstract) 클래스로 옮기고 서로 다른 기능은 하위 클래스로 구현한다.

 

반응형
반응형

이전에 작성한 글로 대신한다.

 

[JAVA] Constant Interface : 상수를 정의한 인터페이스

 

[JAVA] Constant Interface : 상수를 정의한 인터페이스

이전 블로그에서 2017년 7월 20일에 작성한 글 프로젝트 소스를 보다가 상수(Constant)를 인터페이스(Interface)에 정의한 것을 발견했다. 학교 수업과 책으로 자바를 배웠을 때, 항상 상수는 클래스에

camel-context.tistory.com

 

반응형
반응형

토비의 스프링 3장. 예외 챕터를 보면 DB 벤더마다 SQL 에러코드가 다르고, 관리하기 어려워 스프링은 자바에서 다루는 SQLException을 변환하여 DataAccessException을 사용하도록 한다.

 

DB 벤더마다 에러코드를 매핑 값은 sql-error-codes.xml 이다.

 

업무 중 SQLException을 직접 Spring 예외로 변환해야 하는 기능이 필요하여 찾아보니 아래와 같이 작성하면 된다.

 

SQLErrorCodeSQLExceptionTranslator sqlTranslator = new SQLErrorCodeSQLExceptionTranslator();
DataAccessException ex = sqlTranslator.translate(null, null, sqlException);

 

SQLErrorCodeSQLExceptionTranslator.translate() 메서드에서 마지막 인자로 전달하는 SQLException 객체에는 SQLState 값이 채워져 있어야지 변환할 수 있다.

 

 

 

반응형

'Spring' 카테고리의 다른 글

[Spring] @ResponseBody는 @RestController으로 대체 가능  (0) 2022.08.16
반응형

이 챕터에서는 우리가 자주 사용하는 extends(상속을 통한 확장) 보다 해당 클래스를 참조하는 private 필드를 선언해서 사용하는 것이 더 좋은 이유에 대해 설명한다.

 

(이 챕터 내용과 비슷하게 스프링 바이블인 '토비의 스프링' 책에서도 관심사의 분리, 제어의 역전, 콜백 패턴 등을 설명할 때 계승보다는 구성을 사용하는 것이 좋다고 설명한다.)

 

계승의 문제점

계승은 상위 클래스와 하위 클래스가 밀접한 관계를 가진다. 하위 클래스는 상위 클래스의 구현에 의존할 수밖에 없기 때문에 캡슐화 원칙을 위반한다. 즉 상위 클래스의 구현이 변경되면 하위 클래스는 기존에 구현한 코드가 원하는 방식으로 동작하지 않을 수 있다.

 

계승을 하기 위해서는 상위 클래스 구현에 대해 잘 알아야 한다. 그렇지 않을 경우 하위 클래스가 원하는 방식으로 동작하지 않을 수 있는데 예를 들어보자.

 

java.util에서 제공하는 HashSet 클래스를 상속하여 원소가 추가될 때마다 카운팅을 하는 클래스를 아래와 같이 구현했다.

public class CountHashSet<E> extends HashSet<E> {
	private int addCount = 0;

	public CountHashSet() {

	}

	public CountHashSet(int initCap, float loadFactor) {
		super(initCap, loadFactor);
	}

	@Override
	public boolean add(E e) {
		addCount++;
		return super.add(e);
	}

	@Override
	public boolean addAll(Collection<? extends E> c) {
		addCount += c.size();
		return super.addAll(c);
	}
}

 

심플한 클래스로 잘 동작할 것 같지만 addAll()을 호출할 경우 문제가 발생한다. addAll()에서 super.addAll()은 java.util.AbstractCollection 클래스의 addAll()을 호출하는데 구현 코드는 아래와 같다.

    public boolean addAll(Collection<? extends E> c) {
        boolean modified = false;
        for (E e : c)
            if (add(e))
                modified = true;
        return modified;
    }

 

코드를 보면 알겠지만 addAll()에서 다시 add()를 호출한다. 따라서 위에서 구현한 코드는 중복 계산되므로 addAll()을 호출하게 되면 항상 원소의 두 배만큼 증가한다.

 

해당 버그를 fix 하기 위해 addAll() 재정의 메서드를 제거하더라도 나중에 addAll()에서 add()를 호출하지 않으면 문제가 다시 발생한다. 다른 방법으로 super.addAll()을 호출하지 않고 직접 구현하면 된다. 하지만 상위 클래스의 동작을 위해 직접 구현하기 어려울 뿐 아니라, 접근 불가능한 필드가 있을 경우 구현은 아예 불가능하다.

 

이외에도, 상위 클래스에 또 다른 원소를 추가하는 메서드가 생겨나게 된다면 하위 클래스에서는 매번 릴리즈 노트를 확인해야 할 것이다.

 

하위 클래스가 상위 클래스를 망가뜨릴 수 있다. 만약 HashSet에 특정 조건을 만족해야지만 add 할 수 있는 메서드가 있다면 하위 클래스도 add를 재정의할 때 해당 조건을 추가해서 구현해야지 상위 클래스를 만족한다. 하지만 잘못된 사용자는 하위 클래스를 이용하여 해당 조건을 넣지 않아 상위 클래스를 망가뜨릴 수 있다.

 

계승보다 구성

위와 같은 계승의 문제점은 구성(필드 선언)을 통해 해결할 수 있다. 소스는 아래와 같다.

public class CountHashSetByComposit<E> {
	private int addCount = 0;
	private Set<E> s;

	public CountHashSetByComposit(Set<E> s) {
		this.s = s;
	}

	public boolean add(E e) {
		addCount++;
		return s.add(e);
	}

	public boolean addAll(Collection<? extends E> c) {
		addCount += c.size();
		return s.addAll(c);
	}
}

 

(책에서는 Set 인터페이스를 구현하는 전달 클래스를 하나 만들어 Wrapper 클래스로 예를 들었다.)

 

위 클래스는 어떤 Set 객체를 인자로 받아 필요한 부분만 사용하고, 추가적인 기능은 직접 구현하였다. 계승 대신 구성을 하게 되면 사용하고자 하는 메서드에 대해서만 이해하면 되고, 해당 클래스의 API의 일부가 변경되어도 구성을 사용한 클래스를 알 필요 없다.

 

계승은 상위 클래스와 하위 클래스 사이에 IS-A 관계가 확실할 때만 사용하는 것이 좋다. IS-A 관계가 확실하다고 해도 내가 구현한 상위 클래스가 아니라면 계승보다는 구성이 낫다. 즉 계승은 상위 클래스와 하위 클래스 구현을 같은 개발자가 구현한다면 고려해도 된다. 하지만 구성을 하게 되면 하위 클래스보다 견고할 뿐 아니라, 더 강력하다.

반응형
반응형

Immutable(이하 불변) 클래스는 객체를 한 번 만들게 되면 상태(필드의 값)를 변경할 수 없는 클래스이다.

이 글에서는 불변 클래스의 구변 방법을 설명하고 특징을 설명한다.

 

불변 클래스 구현 방법

자바에서 객체 필드의 값을 변경하지 못하게 하는 final 키워드, 외부에서 접근 불가능하게 하는 접근 제어자 등을 사용하여 불변 클래스를 쉽게 구현할 수 있다.

 

먼저 자바에서 제공하는 클래스 중 불변 클래스인 Integer 클래스의 소스는 아래와 같다.

package java.lang;

...

// 2. final을 사용하여 다른 클래스에서 상속하지 못하도록 한다.
public final class Integer extends Number implements Comparable<Integer> {
	...
    

    // 1. 모든 필드를 final로 선언한다.
    // 4. 모든 필드를 private로 선언한다.
    private final int value;
    
    public int intValue() {
        return value;
    }
    
 	...
    
    // 1. 값을 변경하는 메서드를 제공하지 않는다.
    // 5. 변경 가능한 객체에 대한 참조가 있다면, 외부에서는 해당 참조를 얻을 수 없도록 해야한다.
}

먼저 Integer에서 실제 값을 저장하는 value 필드는 한 번 값이 초기화되면 더 이상 수정할 수 없도록 1. final 키워드를 사용하였고, 외부에서 해당 필드를 접근하지 못하도록 4. private로 선언하였다. 또한 만약 value를 1. 수정할 수 있는 메서드가 제공된 다면 외부에서 해당 메서드를 이용하여 수정 가능하므로 제공하지 않았다.

 

또한 규칙 13에서 설명한 내용과 비슷하게 5. 변경 가능한 객체에 대한 참조가 있다면 외부에서는 해당 참조를 얻을 수 없도록 해야 한다. 만약 아래와 같이 Array 필드인 data를 외부에서 접근 가능하도록 getter 메서드를 제공한다면 참조 값은 변경할 수 없지만, 참조에 대한 객체는 수정이 가능하므로 불변 클래스가 깨지게 된다.

private static final Thing[] data = { ... };

 

또한 2. 클래스에 final을 선언하여 외부에서 Integer를 상속할 수 없도록 하였다. 상속을 허용한다면 하위 클래스에서는 메서드를 재정의할 수 있게 된다. 이 것이 문제가 될 수 있는데 예를 들어 intValue() 메서드는 변경 불가능한 값인 value를 리턴하는 메서드인데, 하위 클래스가 악의적으로 다른 값을 반환하게 재정의 한다면 마치 변경된 것처럼 보인다. 이처럼 잘못 작성되는 하위 클래스를 방지하기 위해 상속할 수 없도록 한다.

 

사실 상속할 수 없도록 하는 방법에는 클래스에 final로 선언하는 방법 말고 한 가지 더 유연한 방법이 있다. 바로 private나 package-private로 된 생성자만을 제공하고, 외부에는 규칙 1에서 설명한 정적 팩토리 메서드를 제공하는 것이다. public이나 protected로 선언된 생성자가 없기 때문에 외부에서는 상속을 할 수 없다. 또한 그렇기 때문에 외부에서는 객체를 생성자로 생성할 수 없으므로 정적 팩토리 메서드를 제공한다.

 

 

불변 클래스 특징

불변 클래스의 장점을 설명하기 위해 이번엔 자바에서 제공하는 또 다른 불변 클래스인 BigInteger에 대해 먼저 설명한다. 소스는 다음과 같다.

package java.math;

...

public class BigInteger extends Number implements Comparable<BigInteger> {

    final int signum;
    final int[] mag;
    
    ...

    // 1. 불변 클래스의 객체는 자유롭게 공유될 수 있다.
    public static final BigInteger ZERO = new BigInteger(new int[0], 0);
    public static final BigInteger ONE = valueOf(1);

    ...
    
    public BigInteger negate() {
        // 2. 불변 클래스안에 필드들은 서로 공유 가능하다.
        return new BigInteger(this.mag, -this.signum);
    }
    
    public BigInteger flipBit(int n) {
        if (n < 0)
            throw new ArithmeticException("Negative bit address");

        int intNum = n >>> 5;
        int[] result = new int[Math.max(intLength(), intNum+2)];

        for (int i=0; i < result.length; i++)
            result[result.length-i-1] = getInt(i);

        result[result.length-intNum-1] ^= (1 << (n & 31));

        return valueOf(result);
    }
    
    private static BigInteger valueOf(int val[]) {
        // 3. 불변 클래스는 값을 변경할 수 없기때문에 새로운 객체를 만든다. 
        return (val[0] > 0 ? new BigInteger(val, 1) : new BigInteger(val));
    }

   ...
}

BigInteger 클래스는 불변 클래스이지만 Integer 클래스에서 설명했던 내용과 다른 부분이 존재한다. 먼저 필드를 private로 선언하지 않고 package-private로 선언하였다. 사실 위에서 설명한 원칙들은 다소 과하기 때문에 필요하다면 일부 완화할 수 있다. package-private도 높은 수준의 접근 제어자이기 때문에 어느 정도 수용 가능하다.

 

하지만 private로 선언하지 않은 것과는 다르게 BigInteger를 개발할 때 큰 실수를 하였는데, 바로 상속을 못하도록 방어하지 않았다. 불행히도 하위 호환성 때문에 이는 고쳐지지 않고 사용되고 있다. 그러나 나머지 구현 부분은 불변 클래스의 규칙을 따른다.

 

 

1. 불변 클래스의 객체는 자유롭게 공유할 수 있다.

불변 클래스의 객체는 절대 변하지 않는다. 따라서 외부에 공유해도 해당 객체를 수정할 수 있는 방법이 없기 때문에 문제가 없다. BigInteger는 자주 사용되는 값(ZERO, ONE 등)을 미리 선언하여 재사용하는데, 이 필드들은 public으로 선언되어 있다. public으로 선언되면 외부에서 수정하는 문제가 발생할 수 있겠지만 BigInteger는 불변 클래스이므로 수정이 불가능하므로 자유롭게 공유할 수 있다.

 

2. 불변 클래스 안에 필드들은 서로 공유 가능하다.

불변 클래스 안에 필드들은 당연하게도 절대 변하지 않는다. 따라서 객체들끼리 필드를 공유하여도 변경되지 않으므로 서로 공유가 가능하다. 예를 들어 BigInteger에서 negate() 메서드는 양수이면 음수를, 음수이면 양수로 변환하여 BigInteger 객체를 생성하는 메서드이다. 이때 msg 필드는 배열이기 때문에 서로 공유하면 문제가 될 수 있다. 하지만 BigiInteger는 불변 클래스이기 때문에 msg 배열은 절대 변경되지 않으므로 새로운 객체와 원래 객체가 같은 배열을 참조해도 어떠한 문제도 발생하지 않는다.

 

3. 불변 클래스는 값을 변경할 수 없기 때문에 새로운 객체를 만든다. 

불변 클래스는 값을 변경할 수 없기 때문에 대부분 새로운 객체를 만들어 반환되도록 작성한다. 이는 불변 클래스의 유일한 단점인데, 만약 객체를 생성하는 비용이 크다면 문제가 발생한다. BigInteger의 flipBit() 메서드는 특정 위치의 bit 값을 반대로 변경하는 메서드이다. 만약 BigInteger의 값이 매우 큰 값이어서 bit 배열이 엄청 크다면, 새로운 BigInteger의 값을 생성하기 위해서 동일한 크기의 bit 배열을 하나 새롭게 만들고, 특정 위치의 bit 값을 변경해주고 객체를 생성해야 한다. 이렇게 되면 bit 배열만큼의 시간과 공간이 소요되기 때문에 문제가 발생한다.

 

이외에 불변 클래스는 단순하며 스레드에 안전하다. 불변 클래스는 값을 변경할 수 없다. 따라서 값이 혹시 null인지, 비정상 범위의 값인지에 대해서 객체를 생성할 때 미리 검사한다면, 클라이언트는 두려워하지 않고 사용하기만 하면 된다. 또한 값이 변경되지 않는다는 것은 스레드에 안전하다.

 

또한 불변 클래스는 다른 불변 클래스의 필드로 사용될 수 있다. 따라서 새로운 불변 클래스를 작성할 때 기조 불변 클래스에 변경 가능성에 대해 걱정할 필요 없다.    

 

 

요약

- 변경 가능한 클래스로 만들 이유가 없다면 불변 클래스로 먼저 만들어 보자.

- 변경 가능한 클래스를 만들어야 한다고 해도, 최대한 변경 가능성을 제한하자.

 

  

 

 

 

 

 

 

 

 

반응형

+ Recent posts