728x90
4. 불변성
4.1 객체지향 프로그래밍의 가변성과 구조
- 일반적으로
setter
메서드를 사용하여 상태를 변화- 기존 자료 구조의 변경된 내용이 해당 상태를 업데이트하며, 참조하는 다른 모든 사용자에게도 영향을 미침.
- 가변 상태는 복잡성과 불확실성을 유발
- 가변 상태를 공유하는 것은 공유된 상태에 액세스하는 컴포넌트의 수명을 포함하여 복잡성을 증가시킴
- 동시성 프로그래밍은 공유된 상태의 복잡성에 영향을 받으며 많은 문제가 가변성에서 발생
@Setter
@ToString
@AllArgsConstructor
public class Study {
private String name;
private LocalDate startDate;
private int studyHours;
public static void main(String[] args) {
Study study = new Study("Java", LocalDate.now(), 10);
study.setName("Functional Programming with Java");
study.setStartDate(LocalDate.now());
System.out.println(study);
}
}
4.2 함수형 프로그래밍의 불변성
- 불변성의 원칙은 자료 구조 생성 후 변경할 수 없어야한다.
- 불변 자료 구조는 데이터에 대한 지속적인 뷰를 제공하지만 데이터를 변경할 수 없다.
@Setter
@ToString
@AllArgsConstructor
public final class ImmutableStudy {
private final String name;
private final LocalDate startDate;
private final int studyHours;
public ImmutableStudy newName(String name) {
return new ImmutableStudy(name, this.startDate, this.studyHours);
}
public ImmutableStudy newStudyHour(int newStudyHours) {
return new ImmutableStudy(this.name, this.startDate, newStudyHours);
}
public static void main(String[] args) {
ImmutableStudy study = new ImmutableStudy("Java", LocalDate.now(), 10);
ImmutableStudy newStudy = study.newName("Functional Programming with Java")
.newStudyHour(20);
System.out.println(study);
System.out.println(newStudy);
}
}
예측 가능성
- 자료 구조를 참조하는 한, 생성된 시점과 동일한 상태임을 알 수 있다.
유효성
- 초기화한 후, 자료 구조는 완전한 상태가 된다.
숨겨진 사이드 이펙트 없음
- 불변 자료 구조는 항상 그대로이기 때문에, 사이드 이펙트가 발생하지 않는다.
스레드 안정성
- 사이드 이펙트가 발생하지 않는 불변 자료 구조는 스레드 경계를 자유롭게 이동할 수 있다.
캐시 가능성 및 최적화
- 자료 구조가 생성 직후부터 변경되지 않았기 때문에, 불변 자료 구조를 신뢰하고 캐싱할 수 있다.
변경 추적
- 모든 변경이 새로운 자료 구조를 생성한다면, 이전 참조를 저장함으로써 이전 상태를 추적할 수 있다.
4.3 자바 불변성 상태
- 자바 14부터 불변 자료 구조인 레코드를 도입
java.lang.String
- 문자열을 연결하면, 새로운 String 객체가 생성된다.
- 그러므로 문자열 연결을 계속 사용하는 것을 지양해야 한다.
- String Builder나 invokedynamic을 사용하는 것이 좋다.
- 기술적 관점에서, String은 성능 고려로 인하여 hashCode를 지연해서 계산한다.
public class StringExample {
public static void main(String[] args) {
String original = "Java";
String modified = original.concat(" is fun");
System.out.println("original: " + original);
System.out.println("modified: " + modified);
}
}
// String Class
public String concat(String str) {
if (str.isEmpty()) {
return this;
}
return StringConcatHelper.simpleConcat(this, str);
}
// StringConcatHelper Class
@ForceInline
static String simpleConcat(Object first, Object second) {
String s1 = stringOf(first);
String s2 = stringOf(second);
if (s1.isEmpty()) {
// newly created string required, see JLS 15.18.1
return new String(s2);
}
if (s2.isEmpty()) {
// newly created string required, see JLS 15.18.1
return new String(s1);
}
// start "mixing" in length and coder or arguments, order is not
// important
long indexCoder = mix(initialCoder(), s1);
indexCoder = mix(indexCoder, s2);
byte[] buf = newArray(indexCoder);
// prepend each argument in reverse order, since we prepending
// from the end of the byte array
indexCoder = prepend(indexCoder, buf, s2);
indexCoder = prepend(indexCoder, buf, s1);
return newString(buf, indexCoder);
}
불변 컬렉션
- Set, List, Map 같은 컬렉션 그룹이 존재
- new 키워드를 사용하여 직접 인스턴스화할 수 있는 public 타입은 아니나, 이러한 유형은 필요한 인스턴스를 생성하기 위한 정적 편의 메서드를 제공
변경 불가능한 컬렉션
import java.util.Collection;
Collection<T> unmodifiableCollection(Collection<? extends T> c);
Set<T> unmodifiableSet(Set<? extends T> set);
List<T> unmodifiableList(List<? extends T> list);
...
변경 불가능한 뷰
는 기존 컬렉션의 추상화에 불과하여, 기본 컬렉션이 여전히 변경될 수 있다는 점이다.- 원본 참조를 통해 변경할 수 있으므로, 변경 불가능한 뷰를 사용할 때 주의해야 한다.
- 그러므로 반환값으로 사용될 컬렉션에 대해 원치 않는 변경을 막기 위해 사용된다.
불변 컬렉션 팩토리 메서드
List<E> of(E e1, ...);
Set<E> of(E e1, ...);
Map<K,V> of(K k1, V v1, ...);
불변 복제
- static 메서드 copyOf를 사용하여 더 깊은 수준의 불변성을 제공
Set<E> copyOf(Collection<? extends E> elements);
List<E> copyOf(Collection<? extends E> elements);
Map<K,V> copyOf(Map<? extends K, ? extends V> elements);
- 단순히 뷰를 제공하는 것이 아닌, 새로운 컨테이너를 생성하여 요소들의 참조를 독립적으로 유지
원시 타입과 원시 래퍼
- 원시타입은 리터럴 또는 표현식을 통해 초기화되는 단순한 값을 나타내며 각자 하나의 값을 표현하며, 사실상 불변의 특성을 갖습니다.
불변 수학
- 정수와 소수점 계산을 더욱 안전하고 정확하게 처리하기 위한 BigInteger, BigDecimal 불변 클래스를 제공
- 주의할 점은 계산의 실제 결과를 사용하는 것을 잊어버릴 수 있다.
자바 시간 API (JSR-310)
- java.util 대신 java.time 패키지를 통해 다양한 정밀도를 가진 여러 날짜 및 시간 관련 타입들을 사용할 수 있다.
- 모두 불변성을 가지며 동시 환경에서도 안전하게 사용할 수 있다.
열거형
- 자바 열거형은 상수로 구성된 특별한 타입이며, 불변성을 가진다.
- 필드를 추가할 수 있으며 일반적으로 final 또는 String이 사용되지만, 가변 객체 타입 및 setter가 사용될 수 있다. 문제를 일으킬 가능성이 있어 권장하지 않는다.
final 키워드
- 특징
- final 클래스는 하위 클래스화 될 수 없다
- final 메서드는 오버라이딩 될 수 없다.
- final 필드는 생성자 내부에나 선언될 때 정확히 한 번 할당되어야 하며 재할당할 수 없다.
- final변수 참조는 필드처럼 동작하며 선언 시에 정확히 한 번만 할당할 수 있다. 참조 자체에만 영향을 미치며 참조된 변수의 내용에 영향을 주지 않는다.
- 참조를 재할당 할 수 없지만 저료 구조를 여전히 변경할 수 있다.
- 예를 들어, final List를 선언하고, List의 요소를 변경할 수 있다. (TODO: 예제 필요)
레코드
- 레코드는
기본 데이터
의 집합체이며, POJO나 Java Bean에 비해 간결하고 목표지향적이다. - 상태 선언으로 구성된 얕은 불변성을 가진 데이터 운반체이며, getter, 동등성 비교, toString 및 hashCode 메서드 등을 제공.
4.4 불변성 만들기
- 타입을 불변하게 만드는 가장 쉬운 방법은, 처음부터 데이터를 수정할 수 없도록 설계하는 것
일반적인 관행
- 주요 목표는 불변 자료 구조와 불변 참조를 기본 접근 방식으로 사용하는 것.
기본적인 불변성
- 데이터 전송 객체, 값 객체 또는 어떠한 종류의 상태와 같은 새로운 자료 구조는 불변하도록 설계되어야 한다.
항상 불변성을 고려하기
- 특별히 다르게 명시되지 않았거나, 직접 생성하지 않은 경우에 모든 자료 구조는 불변이라고 가정한다.
- 변경해야 한다면, 기존의 것을 기반으로 새로운 것을 만드는 것이 더 안전하다.
기존 타입 수정하기
- 기존 타입이 불변하지 않더라도, 가능하다면 새롭게 추가되는 사항들은 불변해야 한다.
필요한 경우 불변성 깨기
- 코드와 불변성이 맞지 않다면, 강제로 바꿀 필요는 없다.
- 불변성의 주요 목표는 안전하고 합리적인 자료 구조를 제공하는 것이며, 이를 위해서는 개발 환경이 적절히 뒷받침되어야 한다.
외부 자료 구조 불변 처리하기
- 스코프가 아닌 모든 자료 구조는 불변하다고 가정하자.
- 직접 조작하는 대신 변경을 위한 가변 래퍼 뷰를 만들고, 수정할 수 없는 컬렉션 타입을 반환하자.
- 메서드를 순수하게 유지하고 호출자가 예상하지 못한 의도치 않은 변경을 방지할 수 있다.
반응형
'스터디 > 함수형 프로그래밍 with 자바' 카테고리의 다른 글
Chapter 06. 스트림 (Stream) (1) | 2024.06.06 |
---|---|
Chapter 05. 레코드 (Record) (0) | 2024.05.30 |
Chapter 03. JDK의 함수형 인터페이스 (1) | 2024.05.16 |
Chapter 02. 자바 람다 (0) | 2024.05.08 |
Chapter 01. 함수형 프로그래밍 소개 (1) | 2024.05.02 |