Wrapper class

자바의 기본 자료형(Primitive Data Type)을 객체로 취급하기 위해 사용하는 클래스

기본 타입의 데이터를 객체로 포장하기 때문에 Wrapper class 라고 부른다.

java.lang 패키지에 정의되어 있다.

 

 

 

Use Case

 

1. 제네릭 클래스는 객체만 처리한다. 

제네릭 클래스를 이용하기 위해서는 기본 타입 데이터를 래핑해서 사용해야 한다.

 

2. java.util 패키지의 클래스는 객체만 처리한다.

java.util 패키지에 포함 된 클래스

AbstractCollection, 
AbstractList, AbstractSequentialList, LinkedList, ArrayList, Vector, Stack, 
AbstractSet, HashSet, LinkedHashSet, TreeSet, 
AbstractMap, HashMap, LinkedHashMap, TreeMap, 
Arrays, BitSet, Calendar, GregorianCalendar, 
Collection, Date, Dictionary, Hashtable, Properties, 
EventObject, Locale, Observable, Random, Scanner, StringTokenizer

 

3. 자바5 이전에는 오토박싱이 없어서 Java Collection Framework 를 사용하려면 기본 타입 데이터를 수동으로 래핑해서 사용해야했다.

지금은 autoboxing 을 사용해서 ArrayList.add(100) 을 쉽게 수행할 수 있지만,

Java 내부적으로 valueOf() 메서드를 사용하여 기본값을 Integer 로 변환하여 동작한다.

 

 

 

참고로 봐주세요.

 

다음과 같이 선언했다고 했을 때, a 와 b 가 차지하는 메모리는 어떻게 될까? 

int a = 100;
Integer b = 100;

 

int 형인 a 는 32 비트를 차지한다.

Interger 형인 b는 값인 100과 그에 관련 된 정보를 저장한다.

객체 메타 데이터는 JVM 버전에 따라 다르지만 일반적으로 아래와 같이 구성된다.

 

 

https://developer.ibm.com/articles/j-codetoheap/

- 클래스 포인터: 객체 유형을 설명하는 포인터, 여기서는 Interger 클래스의 포인터 (32비트)

- 플래그: 객체 상태 저장 (32비트)

- 락: 객체에 대한 동기화 정보, 객체가 현재 동기화되었는지 여부 (32 비트)

- 데이터: int 타입 100 (32비트)

 

즉, int 형으로 값을 저장한 a 보다 b 가 4배의 메모리를 차지하게 된다.

 

 

 

 

같은 값의 객체를 비교하면, 어떤 결과가 나올까?

Integer aObject = 127;
Integer bObject = 127;

System.out.println(aObject == bObject);

 

정답은 true 이다.

참조 자료형을 == 으로 비교했는데(주소값으로 비교했는데) 왜 같은 값이 나올까?

 

Integer class 내부에는 디폴트로 -127~128 까지의 수를 캐싱하게 되어있다.

이렇게 캐싱 된 인스턴스는 autoboxing 을 통해 새로운 인스턴스를 만들지 않고 사용된다.

 

 

 

 

 

First Class Collection

소트웍스 앤솔로지의 객체지향 생활체조에 언급 된 일급 컬렉션에 대해 알아보자.

객체지향적인, 리팩토링하기 쉬운 코드를 작성하기 위해 참고하는 지침 정도로 생각하면 된다.

 

규칙 8: 일급 콜렉션 사용
이 규칙의 적용은 간단하다. 
콜렉션을 포함한 클래스는 반드시 다른 멤버 변수가 없어야 한다. 
각 콜렉션은 그 자체로 포장돼 있으므로 이제 콜렉션과 관련된 동작은 근거지가 마련된셈이다.
필터가 이 새 클래스의 일부가 됨을 알 수 있다. 
필터는 또한 스스로 함수 객체가 될 수 있다. 
또한 새 클래스는 두 그룹을 같이 묶는다든가 그룹의 각 원소에 규칙을 적용하는 등의 동작을 처리할 수 있다. 
이는 인스턴스 변수에 대한 규칙의 확실한 확장이지만 그 자체를 위해서도 중요하다. 
콜렉션은 실로 매우 유용한 원시 타입이다. 
많은 동작이 있지만 후임 프로그래머나 유지보수 담당자에 의미적 의도나 단초는 거의 없다. - 소트웍스 앤솔로지 객체지향 생활체조편

 

간단하게 말하면, Collection 을 Wrapping 하면서 그 외에 다른 멤버 변수가 없는 상태를 일급 컬렉션이라고 한다.

 

Map<String, String> map = new HashMap<>();
map.put("yuna1", "A");
map.put("yuna2", "B");
map.put("yuna3", "C");

 

이렇게 만들어진 Map 을 아래와 같이 Wrapping 하는 것이다.

 

public class Student {

    private Map<String, String> grade;

    public Student(Map<String, String> grade) {
        this.grade = grade;
    }
}

 

 

이점

 

- 비즈니스에 종속적인 자료구조

- Collection 의 불변성을 보장

- 상태와 행위를 한 곳에서 관리

 

 

 

비즈니스에 종속적인 자료 구조

 

리그 오브 레전드 라는 게임에 칼바람 나락이라는 모드가 있다.

5명의 인원이 한 팀이 되어 중복 없이 랜덤으로 챔피언(이하 챔프)을 할당받는다.

 

팀을 생성하는 조건은 다음과 같다.

 

- 5개의 챔프가 필요

- 5개의 챔프는 중복되지 않아야함

 

 

조건에 맞게 팀을 생성해보도록 하겠다. 자바로 하는 일은 없겠지만

 

public void createTeam() {
    List<String> teamMembers = createMembers();
    validateSize(teamMembers);
    validateDuplicate(teamMembers);

    ....
}

private void validateSize(List<String> teamMembers) {
    if(teamMembers.size() != MEMBER_SIZE)
        throw new IllegalArgumentException("멤버는 5명만 가능합니다.");
}

private void validateDuplicate(List<String> teamMembers) {
    Set<String> nonDuplicateMembers = new HashSet<>(teamMembers);
    if(nonDuplicateMembers.size() != MEMBER_SIZE)
        throw new IllegalArgumentException("챔프는 중복될 수 없습니다.");
}

 

위와 같이 구현을 한다면 조건에는 부합하지만, 팀을 생성한 뒤에 매번 검증 로직을 구현해야한다.

 

createMembers 메서드를 만든 나는 번거롭더라도 매번 검증을 하면 되지만

어디선가 createMembers 메서드로 팀을 만들고싶은 개발자는 검증 로직을 수행해야하는지 알 수 없어 문제가 발생할 가능성이 높다.

 

이러한 경우 일급 컬렉션을 사용할 수 있다.

비즈니스에 종속적인 자료구조, 즉 주어진 조건으로만 생성할 수 있는 자료구조를 만드는 것이다.

 

 

class Team {

    private static final int MEMBER_SIZE = 5;
    
    private final List<String> teamMembers;

    public Team(List<String> teamMembers) {
        validateSize(teamMembers);
        validateDuplicate(teamMembers);
        
        this.teamMembers = teamMembers;
    }

    private void validateSize(List<String> teamMembers) {
        if(teamMembers.size() != MEMBER_SIZE)
            throw new IllegalArgumentException("멤버는 5명만 가능합니다.");
    }

    private void validateDuplicate(List<String> teamMembers) {
        Set<String> nonDuplicateMembers = new HashSet<>(teamMembers);
        if(nonDuplicateMembers.size() != MEMBER_SIZE)
            throw new IllegalArgumentException("챔프는 중복될 수 없습니다.");
    }
}
public void createTeam() {
    List<String> teamMembers = new Team(createMembers());

    ....
}

 

이제 팀을 생성하고싶은 개발자들은 해당 일급 컬렉션을 사용하면 된다.

 

 

 

불변성을 보장해야하는 경우

 

이제 캐릭터를 할당하고 게임이 시작되었다.

게임이 끝날때까지 무슨 짓을 하든지 할당 받은 캐릭터들이 변경되어서는 안된다.

 

이러한 경우에도 일급 컬렉션을 사용하면 된다.

 

public Team(List<String> teamMembers) { // 생성자
    validateSize(teamMembers);
    validateDuplicate(teamMembers);

    this.teamMembers = teamMembers;
}

public String getFirstMember() {
    return teamMembers.get(0);
}

 

컬렉션을 생성할 때는 생성자로 값을 할당하고, 컬렉션 내부의 값을 가져올때는 get 메서드를 통해 가져온다.

(getter 에서 리스트 자체를 리턴하게 되면 레퍼런스 관계가 되어 mutable 해지는 것에 주의)

 

 

 

상태와 행위를 한 곳에서 관리

 

일급 컬렉션의 또 다른 이점은 값과 로직이 함께 존재하는 것이다.

 

대식가 A양이 있다.

매끼를 근처 식당에 가서 모든 메뉴를 시켜먹는다.

 

public class Dish {

    String name;
    
    int price;

    public Dish(String name, int price) {
        this.name = name;
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public int getPrice() {
        return price;
    }
}

 

메뉴에는 음식마다 이름과 가격이 써있을 것이다.

 

public class 아침을_먹는_식당 {

    private List<Dish> menu = new ArrayList<>();
    menu.add(new Dish("pasta", 2000));
    menu.add(new Dish("pizza", 5000));
    
    public int getTotal() {
        return menu.stream().mapToInt(Dish::getPrice).sum();
    }
}

public class 점심을_먹는_식당 {

    private List<Dish> menu = new ArrayList<>();   
    menu.add(new Dish("salad1", 10000));
    menu.add(new Dish("salad2", 5000));

    public int getSum() {
        return 이렇게 저렇게 해서 합계를 구하는 새로운 방법;
    }
}

 

식당 클래스마다 합계를 구하는 중복 메서드들이 생성되고, 그 안에서 어떤 방식으로 합계를 구하든지 제약이 없다.

일급 컬렉션을 사용하면 이러한 문제를 해결할 수 있다.

 

public class MenuGroups {

    private List<Dish> menu;

    public MenuGroups(List<Dish> menu) {
        this.menu = menu;
    }

    public int getTotal() {
        return menu.stream().mapToInt(Dish::getPrice).sum();
    }
}
public class 아침을_먹는_식당 {

    List<Dish> menu = new ArrayList<>();
    menu.add(new Dish("pasta", 2000));
    menu.add(new Dish("pizza", 5000));

    MenuGroups menuGroups = new MenuGroups(menu); // 상태
    int total = menuGroups.getTotal(); // 행위
}

 

일급 컬렉션 내부에 비즈니스 로직을 구현하면

합계를 구하는 중복 코드를 작성하지 않아도 되고 상태와 행위를 한 곳에서 관리할 수 있다.

 

 

 

 

 

 

 

(참조)

 

Wrapper class

https://developer.ibm.com/articles/j-codetoheap/

https://pjh3749.tistory.com/254

 

First class Collection 

https://jojoldu.tistory.com/412

https://tecoble.techcourse.co.kr/post/2020-05-08-First-Class-Collection/

 

'Java' 카테고리의 다른 글

[Java] JVM 알아보기 - (1) JDK, JRE, JVM  (0) 2022.11.16
[Java] Generic 과 WildCard  (0) 2022.08.31
[Java] Reflection 이란?  (0) 2022.08.15
[Java] static 키워드 알아보기  (0) 2022.07.18
[Java8] 비동기 처리 CompletableFuture  (0) 2022.06.17
복사했습니다!