Lee's Grow up

[JAVA/자바] COLLECTION API, 컬렉션 API 다루기 본문

PROGRAMMING/JAVA

[JAVA/자바] COLLECTION API, 컬렉션 API 다루기

효기로그 2019. 9. 20. 15:24
반응형

해당 내용은 자바 9 이상을 기준으로 설명합니다. 또한 관련 내용은 모던 자바 인 액션을 참고하였습니다.

컬렉션 생성 팩토리 메서드

1. Arrays.asList 팩토리 메서드
public static <T> List<T> asList(T... a) { return new ArrayList<>(a); }

Arrays라는 유틸성 클래스에 위와 같이 정적 메서드로 제공이 되어있다. 위처럼 ArrayList의 생성자를 통해 생성을 하고, 여기서 ArrayListjava.java.util.ArrayList 가 아닌 Arrays 안에 선언된 클래스입니다.

private static class ArrayList<E> extends AbstractList<E>
        implements RandomAccess, java.io.Serializable{

        ...

      }

또한 AbstractList를 상속 받으며, set을 오버라이딩했기 때문에, 변경(삭제, 추가)은 불가능하지만, 수정은 가능한 객체를 리턴해줍니다.

List<Member> members = Arrays.asList(
                    new Member(LEE, 23, "A"),
                    new Member(PARK, 15, "C"),
                    new Member(KIM, 41, "D"),
                    new Member(KIM, 61, "B"),
                    new Member(KIM, 7, "E")
                );

members.set(0, new Member());
members.add(new Member()); // 불가능 런타임 익셉션인 UnsupportedOperationException 발생

그런데 배열 API인데 컬렉션을 만드는 다소 이상한 부분이 있습니다. 또한 스트림을 통해 불변 컬렉션을 만들 수 있습니다.

List<Member> members = Stream.of(new Member(), new Member())
                .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));

그런데 뭔가, 스트림을 통해 리스트를 만들고, 만든 리스트를 불변 리스트로 바꾸고 사용이 편리해 보이지 않습니다.
그래서 자바 9에서는 컬렉션 API를 개선해서 쉽게 불변 컬렉션을 만들 수 있는 정적 팩토리 메서드를 제공해줍니다.

2. 컬렉션 제공 팩토리 메서드

여기서 말하는 컬렉션은 list, set, map 인터페이스를 지칭합니다.

예로 List로 들면 List.of() 라는 메소드가 인자가 1개인거부터 10개까지, 또는 가변인자로 여러개가 선언되어 있는 것을 볼 수 있습니다. 그 이유는 가변인수를 사용하면 배열을 할당, 초기화하는 비용이 발생하기 때문에 최대 10개까지는 이러한 비용을 제외하도록 List.of(), Set.of, Map.of가 동일한 형태로 선언되어 있습니다.

그럼 사용방법을 알아보겠습니다.

List<String> list = List.of("one", "two", "three");

간단합니다. 위에처럼 사용하면 우리는 간편하게 불변 컬렉션을 만들 수 있습니다. 집합(Set)의 경우도 동일하게 of 팩토리 메서드를 사용해서 객체를 할당 받을 수 있습니다. 단, 중복이 되는 경우 중복이 되는 요소와 함께 IllegalArgumentException이 발생합니다.

무언가 데이터를 변경, 수정할 필요가 없다면 제공해주는 팩토리 메서드를 이용하고, 변경 수정이 필요한 경우, Collectors.toList()로 만들거나, 직접 만드는것을 추천합니다.

마지막으로 map의 경우 키와 값 두쌍이 필요로 하기 때문에 아래와 같이 선언가능합니다.

Map<String,Integer> ageMap = Map.of(
                            "Lee", 21,
                            "Park", 17);

10개를 넘어서는 맵의 경우 아래의 팩토리 메서드를 이용하는 것이 좋습니다.

Map<String,Integer> ageMap = Map.ofEntries(
                        Map.entry("Lee", 21),
                        Map.entry("Park", 17));

컬렉션의 처리

1. 리스트와 집합 처리

스트림을 통한 동작은 새로운 결과를 만드는 동작이다. 기존의 list,나 set을 수정하기 위해선 반복문을 돌리고, 어떠한 조건이 일치하면 삭제를 진행 등의 방식으로 로직을 구현했었는데, 이는 많은 에러를 유발하며, 코드의 가독성을 떨어트리는 문제가 발생한다. 대표적인 예로 ConcurrentModificationException이다.

이를 해결하기 위해 ListSet는 메서드를 제공해준다.

  • removeIf : list, set 둘다 포함, 프리디케이트를 만족하는 요소를 제거
  • replaceAll : list만 포함, UnaryOperator 함수를 이용해 요소 변경
  • sort : list만 포함, List 인터페이스에서 제공하는 기능으로 정렬

아래는 기존의 삭제 방식이다.

for(Iterator<String> iterator = names.iterator(); iterator.hasNext();) {
            if(iterator.next().equals("Lee")) {
                iterator.remove();
            }
        }

해당 방식을 제공해주는 메서드를 이용하면 프리디케이트를 통해서 간결하게 구현 가능하고, 직관적인 코드로 변한다.

names.removeIf(name -> name.equals("Lee"));

replaceAll의 경우 이름처럼 각 요소를 새로운 요소로 변환시켜 주는 메서드이고, 사용법은 아래와 같다.

names.replaceAll(name -> Character.toUpperCase(name.charAt(0)) + name.substring(1));

sort의 경우 Comparator을 인자로 넘겨 정렬을 해주는 메서드이다.

2. 맵 처리

반복 맵에서 키와 값을 반복해서 처리하는 작업은 불필요한 코드가 많아지고, 귀찮은 작업 중 하나입니다.
그래서 forEach 메서드를 제공해주고 인자로 BiConsumer 의 구현을 넘겨주면 됩니다.

memberMap.forEach((name, age) -> System.out.println("name : " + name + ", age : " + age));

정렬의 경우도 키 또는 값으로 정렬을 제공해주는 새로운 메서드를 Entry.comparingByValue, Entry.comparingByKey를 추가했다.

memberMap.entrySet().stream()
            .sorted(java.util.Map.Entry.comparingByKey())
            .forEach(System.out::println);

null 체크 맵에서 키가 존재하지 않을 경우 null을 리턴하기 때문에 null을 체크하는 로직이 필요했었다.
하지만 이제는 getOrDefault 메서드를 제공해 키가 없을 경우 선언된 값으로 리턴해주는 메서드를 제공해준다.

System.out.println(memberMap.getOrDefault("Lee", -1));

위 예제는 Lee라는 값이 없으면 -1을 리턴해준다.

계산을 위한 메서드로 총 아래와 같이 3가지를 제공해준다.

  • computeIfAbsent : 키에 해당하는 값이 없으면, 키를 이용해 새 값을 맵에 추가
  • computeIfPresent : 키가 존재하면 새 값을 계산하고 맵에 추가
  • computo : 제공된 키로 새 값을 계산하고 맵에 저장

아래는 computeIfAbsend 예제입니다.

memberMap.computeIfAbsent("Han", name -> 22);

해당 값이 없으면 새로운 키와 값을 저장합니다.

아래는 computeIfPresent의 예제입니다.

memberMap.computeIfPresent("Lee",(name, age) -> age + 1);

Lee라는 키가 존재하면 새로운 값을 계산해서 맵에 추가해줍니다.

교체를 위해서 map는 replaceAll, replace를 제공해준다.

합침을 위해서 map는 putAllmerge를 제공해준다. putAll의 경우 중복이 될 경우, 내가 원하는 처리를 하지 못하기 때문에, merge를 제공 어떤 처리를 BiFunction의 람다식으로 넘겨준다.

memberMap.merge("Lee", 10, (value1, value2) -> value1 + value2);
반응형
Comments