일급 + 컬렉션의 의미로 나누어 생각했다. 일급의 의미는 일급 객체의 의미 (위키백과)를 참조했는데, 다음과 같다.
컴퓨터 프로그래밍 언어 디자인에서, 일급 객체란 다른 객체들에 일반적으로 적용 가능한 연산을 모두 지원하는 객체를 가리킨다. 보통 함수에 인자로 넘기기, 수정하기, 변수에 대입하기와 같은 연산을 지원할 때 일급 객체라고 한다.
말이 어렵지만 정의는 간단하다.
- 변수에 할당(assignment)할 수 있다.
- 다른 함수를 인자(argument)로 전달 받는다.
- 다른 함수의 결과로서 리턴될 수 있다.
컬렉션은 자바에서 목록성 데이터를 처리하는 자료구조를 통칭한다. 일급 컬렉션이란 이러한 컬렉션을 자료구조로만 사용하는게 아닌 목적성을 가지고 의미있게 만드는게 목적이라 볼 수 있다.
그렇다면 왜 목적성을 가지고 의미있게 만들까. 결론부터 말하면, 원시 타입으론 객체 지향에서 의미하는 객체를 만들 수 없기 때문이다. 즉, 상태와 행위를 관리할 수 없다.
예를 들어, 아래와 같은 원시타입을 보고 무엇을 알 수 있는지 살펴보자.
String tutorName; //튜터 이름
List<String> tutors; //튜터 목록
- 튜터 이름에 String 형태로 할당할 수 있다.
- 튜터 목록에 String 객체들을 List 컬렉션으로 관리할 수 있다.
그런데, 다음과 같은 요구사항이 들어왔다.
- 튜터의 이름엔 한글만 들어갈 수 있고, 최소 한 글자 이상이여야 한다.
- 튜터의 목록은 최대 10개까지 저장 가능하다.
단순한 방법은 위의 요구사항을 만족하는 함수를 선언하는걸 생각할 것이다.
void validateTutorName(String name){
if(tutorName.size() != 0 && Pattern.matches("/[ㄱ-ㅎㅏ-ㅣ가-힣]/g", name)){
tutorName = name;
}
else{
throw new IllegalArgumentException();
}
}
void addTutor(String tutorName){
if(tutors.size() < 10){
tutors.add(tutorName);
}
}
그런데 이 코드엔 몇 가지 문제가 있다.
- 제약 조건이 한 두 개가 아니라면, 어느 클래스 파일에서 관리할 것인가?
- 값을 바꿀 수 없는 상태가 추가되는 요구사항이 들어오면 어떻게 수정할 것인가?
여기서 잠시 객체지향의 개념을 빌리자면, 객체는 자기 스스로 상태와 행위를 관리할 수 있어야하며 이를 캡슐화라 한다.
내부 복잡한 로직은 신경쓰지 않고 추상화하여 유지보수를 편하게 하기 위함이다.
위의 코드에서는 캡슐화가 지켜지지 않은 코드라 볼 수 있다. 따라서 이를 OOP스럽게 만든 방법이 일급 컬렉션이다.
간단하다. 컬렉션을 wrapping하면 된다. 이렇게 하여 얻는 이점은 다음과 같다.
- 불변성 보장
- 캡슐화
- 의미있는 객체
불변성 보장
자바에서 final 키워드의 의미는 재할당이 불가능하다는 의미이다. 즉, 이미 할당한 변수에 다른 값을 할당하는게 불가능하다. 그러나 이미 할당된 값을 변조하는건 가능하다. 아래의 코드처럼 말이다.
final List<Integer> nums = new ArrayList<>();
nums.add(1); //가능
일급 컬렉션이 주는 불변을 코드로 살펴보면 다음과 같다.
public class Accumulator {
private final List<Integer> nums;
public Accumulator(List<Integer> nums){
this.nums = nums;
}
public int getSum(){
return nums.stream()
.reduce(0, Integer::sum);
}
}
getSum 외에 다른 메서드가 없기 때문에 내부의 nums에 원소를 추가할 수 없다. 이후 다른 사람이 코드를 수정해도 해당 객체의 값은 절대 바뀌지 않기 때문에 사이드 이펙트가 적어진다.
참고: 위의 코드는 엄밀히 말하면 불변이 아니다. List의 내부 객체를 참조하는 코드를 작성한다면, 레퍼런스 참조로 인해 값이 변조될 수 있다. 따라서 완전한 불변으로 만들고 싶다면, list대신 unmodifiable list로 사용하면 된다.
캡슐화
위의 Accumulator 클래스에 값이 0개 라면, 합을 반환할 수 없다는 요구사항이 들어왔다 가정하자. 0개라면 예외를 반환하는 분기문을 작성하면 된다.
public class Accumulator {
...
public int getSum(){
if(nums.size() == 0){
throw new IllegalStateException();
}
return nums.stream()
.reduce(0, Integer::sum);
}
}
Accumulator 내부에서 nums의 상태가 관리된다. 캡슐화가 지켜진 것이다. 외부에서 Accumulator의 상태를 인지할 필요가 없어 편해졌다.
의미있는 객체
위의 튜터 예제에서 문제점은 String Type이라는 정보만으로는 변경에 유연하게 대처할 수 없는 문제가 있었다.
이외에도 문제가 더 있는데, 변수 이름만으로 어떤 역할인지 알아야 한다. 이런 문제는 나중에 유지보수할 때 변수 이름으로 검색해야 하므로 어려워진다. 개발자마다 변수명을 다르게 지을 수 있기 때문이다.
따라서, 의미있는 이름으로 만들면 바뀌지도 않고, 어떤 책임을 갖는지 명확하게 알 수 있다.
일급 컬렉션 단점
- 보일러 코드가 늘어날 수 있다. 컬렉션 하나를 의미있게 만들기 위해 클래스로 wrapping해야 하므로 클래스 생성 코드에, 내부 메서드 코드가 매번 추가돼야 한다. 앞서 언급했던 장점이 필요가 없다면 일급 컬렉션 대신 일반 컬렉션을 사용해도 무방하다 생각한다.
'Java' 카테고리의 다른 글
Enum에 대하여 (0) | 2022.02.26 |
---|---|
제네릭에 대하여 (0) | 2022.02.26 |
Optional에 대하여 (0) | 2022.02.10 |
Stream에 대하여 (0) | 2022.02.10 |
Java의 static method는 왜 오버라이딩 되지 않는가 (0) | 2022.01.23 |