[JAVA] 컬렉션 API 개선

Jae-Baek Song·2023년 2월 4일

모던자바인액션

목록 보기
7/11
post-thumbnail

저자: 라울-게이브리얼 우르마 , 마리오 푸스코 , 앨런 마이크로프트
도서명: 모던 자바 인 액션
출판사: 한빛미디어


오버로딩 vs 가변 인수

List 인터페이스를 조금 더 살펴보면 List.of의 다양한 오버로드 버전이 있다는 사실을 알 수 있다.

static <E> List<E> of(E e1, E e2)
static <E> List<E> of(E e1, E e2, E e3)
  
static <E> List<E> of(E... elements)

내부적으로 가변 인수 버전은 추가 배열을 할당해서 리스트로 감싼다.
따라서 배열을 할당하고 초기화하며 나중에 가비지 컬렉션을 하는 비용을 지불해야한다.

List.of로 열 개 이상의 요소를 가진 리스트를 만들때는 가변 인수를 이용하는 메소드가 사용된다.

리스트 팩토리

리스트 팩토리

List<String> friends = List.of("Raphael", "Olivia", "Thibaut");
System.out.println(friends);

[Raphael,Olivia,Thibaut]

Arrays.asList() 와 차이점

Array.asListList.of
삽입 (add)불가능불가능
삭제 (remove)불가능불가능
변경 (set, replace)가능불가능
Null 허용여부허용X허용X
각 원소에 대한 Null 허용여부허용허용X

https://kim-jong-hyun.tistory.com/31

집합 팩토리

Set.of 팩토리 메서드를 이용해서 바꿀 수 없는 집합을 만들 수 있다.

Set<String> friends = Set.of("Raphael", "Olivia", "Thibaut");
System.out.prinln(friends);

맵 팩토리

Map.of 를 사용해서 맵을 만들 수 있다. 맵을 만들려면 key 와 value 가 필요한데, 두 가지 방법으로 이를 지정할 수 있다.

키와 값을 번갈아 제공한다.

Map<String, Integer> ageOfFriends = Map.of("Sunmin", 28, "Hyerin", 30, "SeJeong", 29, "KwangWoon", 31);
System.out.println(ageOfFriends);

Map.Entry<K, V> 객체 생성

import static java.util.Map.entry;

Map<String, Integer> ageOfFriends = Map.ofEntries(Map.entry("Sunmin", 28),
                                                  Map.entry("Hyerin", 30),
                                                  Map.entry("Kwangwoon", 31),
                                                  Map.entry("SeJeong", 29));

removeIf

for(Transaction transaction : transactions) {
  if(Character.isDigit(transaction.getReferenceCode().charAt(0))) {
    transactions.remove(transaction);
  }
}

for-each 구문은 내부적으로 Iterator 객체를 사용하기 때문에 Iterator 객체, Collection 객체 총 2개의 객체가 컬렉션을 관리한다. 따라서 반복자의 상태가 컬렉션의 상태와 서로 동기화되지 않게된다.

transactions.removeIf(transaction -> Character.isDigit(transaction.getReferenceCode().charAt(0)));

replaceAll

기존 코드

for (ListIterator<String> iterator = referenceCodes.listIterator();
             iterator.hasNext(); ) {
           String code = iterator.next();
           iterator.set(Character.toUpperCase(code.charAt(0)) + code.substring(1));
}

replaceAll 사용

referenceCodes.replaceAll(code -> Character.toUpperCase(code.charAt(0)) +
             code.substring(1));

Map 처리

forEach 메서드

기존 코드

for(Map.Entry<String, Integer> entry: ageOfFriends.entrySet()) {
           String friend = entry.getKey();
           Integer age = entry.getValue();
           System.out.println(friend + " is " + age + " years old");
}

forEach 사용

ageOfFriends.forEach((friend, age) -> System.out.println(friend + " is " +
             age + " years old"));

getOrDefault

Map<String, String> favouriteMovies
               = Map.ofEntries(entry("Raphael", "Star Wars"),
              entry("Olivia", "James Bond"));

System.out.println(favouriteMovies.getOrDefault("Olivia", "Matrix"));
System.out.println(favouriteMovies.getOrDefault("Thibaut", "Matrix"));

맵에 키가 존재하지 않으면 두번째 인수로 받은 기본값을 반환한다.
키가 존재하더라도 값이 널인 상황에서는 getOrDefault가 널을 반환하는경우 두번째 인수가 반환된다.

계산 패턴

computeIfAbsent

기존 코드

String friend = "Raphael";
List<String> movies = friendsToMovies.get(friend);
if(movies == null) {
    movies = new ArrayList<>();
   friendsToMovies.put(friend, movies);
}
movies.add("Star Wars");

computeIfAbsent 사용

friendsToMovies.computeIfAbsent("Raphael", name -> new ArrayList<>())
              .add("Star Wars");

삭제패턴

remove

기존 코드

String key = "Raphael";
String value = "Jack Reacher 2";
if (favouriteMovies.containsKey(key) &&
     Objects.equals(favouriteMovies.get(key), value)) {
   favouriteMovies.remove(key);
   return true;
} else {
   return false;
}

remove 사용

favouriteMovies.remove(key, value);

replaceAll

Map<String, String> favouriteMovies = new HashMap<>();
favouriteMovies.put("Raphael", "Star Wars"); 
favouriteMovies.put("Olivia", "james bond");
favouriteMovies.replaceAll((friend, movie) -> movie.toUpperCase()); 

merge

기존 코드

String movieName = "JamesBond";
long count = moviesToCount.get(movieName);
if(count == null) {
   moviesToCount.put(movieName, 1);
}
else {
   moviesToCount.put(moviename, count + 1);
}

merge 사용

moviesToCount.merge(movieName, 1L, (key, count) -> count + 1L);

ConcurrentHashMap

ConcurrentHashMap은 HashTable 클래스의 단점을 보완하면서 Multi-Thread 환경에서 사용할 수 있도록 나온 클래스이다.
ConcurrentHashMap은 내부 자료구조의 특정 부분만 잠궈 동시 추가, 갱신 작업을 허용한 다. 따라서 동기화된 Hashtable 버전에 비해 읽기 쓰기 연산 성능이 월등하다(참고로, 표준 HashMap은 비동기로 동작함).

HashMap과 ConcurrentHashMap의 차이점

  • Thread Safe
    주요 차이점은 ConcurrentHashMap는 내부적 동기화 때문에 스레드가 Safe합니다.
    HashMap는 내부적으로 동기화되지 않고 스레드로부터 안전하지 않습니다. HashMap 메서드를 사용하여 외부에서 동기화 할 수 있습니다.

  • Internal Structure(내부구조)
    ConcurrentHashMap의 모든 작업이 동기화되는 것은 아닙니다. 추가 및 삭제와 같은 수정 작업만 동기화됩니다. 읽기 작업은 동기화되지 않습니다. 이렇게 하면 ConcurrentHashMap이 외부에서 동기화된 HashMap보다 동시 다중 스레드 응용 프로그램에 대한 첫 번째 선택 맵이 됩니다.

  • Null Keys And Null Values
    HashMap은 최대 하나의 null 키와 임의의 수의 null 값을 허용합니다.
    ConcurrentHashMap은 null 키와 null 값도 허용하지 않습니다.

  • Fail-Fast Vs Fail-Safe
    HashMap에 의해 반환된 반복자는 본질적으로 빠른 속도입니다. 반복자 생성 후 맵이 수정되면 ConcurrentModificationException이 발생하기 때문입니다.
    ConcurrentHashMap에 의해 반환된 반복자는 본질적으로 안전합니다. iterator 생성 후 맵이 수정되면 예외가 발생하지 않습니다.

  • Performance(성능)
    ConcurrentHashMap에 대한 수정 작업만 동기화됩니다. 따라서 ConcurrentHashMap에 대한 추가 또는 제거 작업은 HashMap보다 느립니다. ConcurrentHashMap 및 HashMap 모두에 대한 읽기 작업은 두 맵의 읽기 작업이 동일한 성능을 제공합니다.

결론적으로 ConcurrentHashMap는 내부적으로 동기화 함으로, 동시 멀티 쓰레드 어플리케이션에 적합합니다. HashMap은 내부적으로 동기화 되지않습니다. 따라서 단일 쓰레드 프로그램에 적합합니다.

https://devlog-wjdrbs96.tistory.com/269
https://mongsil1025.github.io/book/modern-java-in-action/ch8/
https://applepick.tistory.com/124

0개의 댓글