본문 바로가기

IT/자바

Chapter 8 컬렉션 API 개선

반응형

Arrays.asList() 팩토리 메서드를 통해 리스트를 생성할 수 있다.

List<String> friends = Arrays.asList("Raphael", "Olivia", "Thibaut");

 

단, 고정 크기의 리스트를 생성했기에 요소 추가, 삭제가 불가능하다.

만약 friends.add("test")로 추가할 경우 UnsupportedOperationException 예외가 발생한다.

 

이처럼 자바 9에서 작은 리스트, 집합, 맵을 쉽게 만들 수 있도록 팩토리 메서드를 제공한다.

 

1) 리스트 팩토리 - List.of (크기 변경 불가능)

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

 

2) 집합 팩토리 - Set.of (크기 변경 불가능)

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

 

단, Set<String> friends = Set.of("Raphael", "Olivia", "Olivia");

> Set의 경우 고유 요소만 포함하므로 중복 요소로 집합 생성 불가능

 

3) 맵 팩토리 - Map.of

Map<String, Integer> ageOfFriends = Map.of("Raphael", 30, "Olivia", 25, "Thibaut", 26);

 

맵 팩토리의 경우 Map.ofEntries 팩토리 메서드를 통해 가변 인수로 맵을 생성할 수 있다. 

Map<String, Integer> ageOfFriends = Map.ofEntries(entry("Raphael", 30), entry("Olivia", 25), entry("Thibaut", 26));

 

팩토리 메서드로 리스트, 집합, 맵을 생성시 크기를 변경할 수 없지만, 각각의 인터페이스를 오버로딩해서 가변인수를 받아 생성하도록 재정의할 수 있다. 

 

하지만, 그렇게 할 경우 가변인수 버전은 추가 배열을 리스트로 감싸는 방식으로 나중에 가비지 컬렉션에 대한 비용을 지불해야 함으로 권장하지 않는 방법이다.

 

데이터 처리 형식을 설정하거나 데이터를 변환할 필요가 없는 경우에만 팩토리 메서드를 사용하는걸 권장한다.

(팩토리 메서드 (List.of) 대신에 컬렉터로 스트림을 리스트(Collectiors.toList)로 변환할 수 있음)

 

리스트와 집합 처리 (자바 8)

List 인터페이스에 removeIf, replaceAll, sort 메서드가 추가되었다.

Set 인터페이스에 removeIf 메서드가 추가되었다.

> 메서드가 추가된 이유는 컬렉션을 바꾸는 동작이 복잡함 때문

 

1) removeIf : 프레디케이트를 만족하는 요소를 제거한다.

@Test
@Description("성이 Lee인 친구 삭제(List 인터페이스의 removeIf)")
void collectionAPITest8_1() throws Exception{
	// given
	List<String> friends = new ArrayList<String>();
	friends.add("Lee One");
	friends.add("Lee Two");
	friends.add("Yang One");
	friends.add("Yang Two");

	// when
	friends.removeIf(friend -> friend.contains("Lee"));

	// then
	System.out.println("friends "+ friends);
	assertEquals(2, friends.size());
}

 

2) replaceAll : UnaryOperator 함수를 이용해 요소를 바꾼다.

@Test
@Description("친구 모두를 성을 Kim으로 변경(List 인터페이스의 replaceAll)")
void collectionAPITest8_2() throws Exception{
	// given
	List<String> friends = new ArrayList<String>();
	friends.add("Lee One");
	friends.add("Lee Two");

	// when
	friends.replaceAll(friend -> "Kim "+friend.split(" ")[1]);

	// then
	System.out.println("friends "+ friends);

	assertEquals(true, friends.get(0).contains("Kim"));
	assertEquals(true, friends.get(1).contains("Kim"));
}

 

맵 처리 (자바 8)

1) forEach 메서드 (BiConsumer 를 인수로 받아 손쉽게 구현 가능)

@Test
@Description("forEach")
void collectionAPITest8_3() throws Exception{
	// given
	Map<String, Integer> ageOfFriends = Map.ofEntries(entry("Raphael", 30),
			entry("Olivia", 25),
			entry("Thibaut", 26));

	// then
	ageOfFriends.forEach((name, age) -> System.out.println(name + " is "+ age +" years old"));
}

[결과]

Thibaut is 26 years old
Olivia is 25 years old
Raphael is 30 years old

 

2) 정렬 메서드

Entry.comparingByKey, Entry.comparingByValue

@Test
@Description("정렬")
void collectionAPITest8_4() throws Exception{
	// given
	Map<String, Integer> ageOfFriends = Map.ofEntries(entry("Raphael", 30),
			entry("Olivia", 25),
			entry("Thibaut", 26));
	// then
	ageOfFriends.entrySet().stream().sorted(Map.Entry.comparingByKey()).forEach(System.out::println);
	ageOfFriends.entrySet().stream().sorted(Map.Entry.comparingByValue()).forEach(System.out::println);
}

[결과]

Olivia=25
Raphael=30
Thibaut=26

=============

Olivia=25
Thibaut=26
Raphael=30

 

3) getOrDefault 메서드

기존엔 키가 존재하지 않는 경우에 대한 null 체크가 필요했지만, getOrDefault 메서드의 2번째 인자값으로 기본값을 세팅할 수 있다.

@Test
@Description("getOfDefault")
void collectionAPITest8_5() throws Exception{
	// given
	Map<String, Integer> ageOfFriends = Map.ofEntries(entry("Raphael", 30),
			entry("Olivia", 25),
			entry("Thibaut", 26));
            
	// then
	System.out.println(ageOfFriends.getOrDefault("James", 100));
}

[결과]

100

 

4) 계산 패턴

> 맵에 키가 존재하는지 여부에 따라 동작이 달라지는 경우가 있다.

 

ComputeIfAbsent : 제공된 키가 없으면 계산 후 맵에 추가

ComputeIfPresent : 제공된 키가 있으면 계산 후 맵에 추가

Compute : 제공된 키로 계산 후 맵에 추가

@Test
@Description("ComputeIfAbsent")
void collectionAPITest8_6() throws Exception{
	// given
	Map<String, List<String>> movieOfFriends = new HashMap<>();
	List<String> tempMovie = new ArrayList<>();
	tempMovie.add("Jame Bond");
	tempMovie.add("Matrix");

	movieOfFriends.put("Raphael", tempMovie);

	// when
	movieOfFriends.computeIfAbsent("James", movie -> new ArrayList<>()).add("Star wars");

	// then
	assertEquals("Star wars", movieOfFriends.get("James").get(0));
}

 

5) 삭제 패턴

> 키가 특정 값과 연관되었을 때만 항목 제거 - remove(key, value)로 삭제

@Test
@Description("remove")
void collectionAPITest8_7() throws Exception{
	// given
	Map<String, List<String>> movieOfFriends = new HashMap<>();
	List<String> tempMovie = new ArrayList<>();
	tempMovie.add("Jame Bond");
	tempMovie.add("Matrix");

	movieOfFriends.put("Raphael", tempMovie);

	// when
	movieOfFriends.remove("Raphael", tempMovie);

	// then
	assertEquals(0, movieOfFriends.size());
}

 

6) 교체 패턴

> 맵 항목을 변경 (replaceAll, replace)

@Test
@Description("replace, replaceAll")
void collectionAPITest8_8() throws Exception{
	// given
	Map<String, String> movieOfFriends = new HashMap<>();
	movieOfFriends.put("Raphael", "Jame Bond");
	movieOfFriends.put("James", "Matrix");

	// when
	movieOfFriends.replaceAll((friend, movie) -> movie.toUpperCase());
	movieOfFriends.replace("James", movieOfFriends.get("James").toLowerCase());

	// then
	System.out.println(movieOfFriends);
}

[결과]

{Raphael=JAME BOND, James=matrix}

 

7) 합침

> 2개의 맵을 합침 (putAll, merge)

@Test
@Description("putAll, merge")
void collectionAPITest8_9() throws Exception{
	// given
	Map<String, String> movieOfFriends = new HashMap<>();
	movieOfFriends.put("Raphael", "Jame Bond");
	movieOfFriends.put("Cristina", "Matrix");

	Map<String, String> movieOfFamily = new HashMap<>();
	movieOfFamily.put("Cristina", "Jame Bond");
	movieOfFamily.put("Teo", "Star wars");

	// when
	Map<String, String> everyone1 = new HashMap<>(movieOfFamily);
	everyone1.putAll(movieOfFriends);

	Map<String, String> everyone2 = new HashMap<>(movieOfFamily);
	movieOfFriends.forEach((key, value) -> everyone2.merge(key, value, (movie1, movie2) -> movie1+" & "+movie2));

	// then
	System.out.println(everyone1);
	System.out.println(everyone2);
}

[결과]

pullAll -> {Cristina=Matrix, Raphael=Jame Bond, Teo=Star wars}
merge -> {Raphael=Jame Bond, Cristina=Jame Bond&Matrix, Teo=Star wars}

 

개선된 ConcurrentHashMap

ConcurrentHashMap 클래스는 동시성 진화적이며, 최신 기술이 반영된 HashMap이다.

동기화된 HashMap보다 읽기쓰기 성능이 좋다.

 

아래의 연산 형태를 제공한다. (concurrentHashMap 상태를 잠그지 않고 연산 수행)

1) 키, 값 연산 : forEach, reduce, search

2) 키 연산 : forEachKey, reduceKey, searchKey

3) 값 연산 : forEachValue, reduceValue, searchValue

4) Map.Entry 객체 연산 : forEachEntry, reduceEntries, searchEntries

 

또한, reduceValuesToInt, reduceValuesToLong 같은 연산이 제공됨으로 박싱 작업을 피할 수 있다.

 

size() 보단 MappingCount 메서드를 사용하는 걸 권장한다.

> 반환 값이 int 형으로 int 범위가 넘어가는 상황에 대한 대처가 가능함

 

keyset() 대신 newKeySet()을 사용하는 걸 권장한다. 

> 맵이 집합으로 변환되지 않고 concurrentHashMap으로 유지되는 집합을 생성함

 

즉, ConcurrentHashMap은 Map에서 상속받은 디폴트 메서드를 지원하며, 스레드 안정성도 제공한다.

 

 

 

#참고서적

 모던 자바 인 액션 - 저 : 라울-게이브리얼 우르마, 마리오 푸스코, 앨런 마이크로프트

 

싸니까 믿으니까 인터파크도서

자바 1.0이 나온 이후 18년을 통틀어 가장 큰 변화가 자바 8 이후 이어지고 있다. 자바 8 이후 모던 자바를 이용하면 기존의 자바 코드 모두 그대로 쓸 수 있으며, 새로운 기능과 문법, 디자인 패턴�

book.interpark.com

반응형