코딩마을방범대

[Java] 컬렉션 팩토리(Set, Map, List) 본문

💡 백엔드/Java

[Java] 컬렉션 팩토리(Set, Map, List)

신짱구 5세 2023. 7. 31. 16:44
728x90

 

 

List

 

List<String> list = new ArrayList<>();
list.add("1");
list.add("2");

 

초기화 시에 선언이 불가능한 경우라면 위의 방법밖에 없겠지만,

초기화 시 미리 데이터를 넣어놓는 간단한 방법도 있다.

 

 


 

 

1. Arrays.asList

고정된 크기이며, 변환 가능한 배열로 저장된다.

따라서 set을 통해 데이터를 갱신할 순 있지만 요소를 추가, 삭제 하는 것은 오류가 발생한다.

( 추가, 갱신, 삭제 모두 불가능 )

List<String> animals = Arrays.asList("강아지", "고양이", "카피바라");

animals.set(0, "비둘기");
animals.add("푸바오"); // UnsupportedOperationException 발생!
animals.remove(0); // UnsupportedOperationException 발생!

 

💡 Arrays.asList를 통해 Set 객체를 생성할 수도 있다.

Set<String> animals = new HashSet<>(Arrays.asList("강아지", "고양이", "카피바라"));

Set<String> animalStreams = Stream.of("강아지", "고양이", "카피바라").collect(Collectors.toSet());

 

 


 

 

2. List.of

Java9 이상부터 사용 가능하며, List.of를 이용해 리스트를 생성하게 되면 변환이 불가능하다.

( 추가, 갱신, 삭제 모두 불가능 )

List<String> animals = List.of("강아지", "고양이", "카피바라");

animals.set(0, "비둘기"); // UnsupportedOperationException 발생!
animals.add("푸바오"); // UnsupportedOperationException 발생!
animals.remove(0); // UnsupportedOperationException 발생!

 


 

3. Double Brace Initialization({{ ... }})을 이용

( 추가, 갱신, 삭제 모두 가능 )

List<String> animals = new ArrayList<String>() {{
	add("강아지");
	add("고양이");
	add("카피바라");
}};

 


 

 

4. Stream을 이용

배열을 통해 리스트를 생성하는 방식

( 추가, 갱신, 삭제 모두 가능 )

String[] animalsArr = new String[] { "강아지", "고양이", "카피바라"};
List<String> animals = new ArrayList<>(Stream.of(animalsArr).collect(Collectors.toList()));

 

 

 


 

 

 

 

 

Set

 

Set<String> animals = new HashSet<>();
animals.add("강아지");
animals.add("고양이");

 

리스트와 같이 초기화 하는 방법은 아래와 같다.

 

 


 

 

1. Set.of

Java9 이상부터 사용 가능하며, 변환이 불가능하고 Set의 특성 상 중복값을 이용해 생성하려고 할 경우 오류가 발생한다.

( 추가, 갱신, 삭제 모두 불가능 )

Set<String> animals = Set.of("강아지", "고양이", "카피바라");

animals.add("비둘기"); // UnsupportedOperationException 발생!
animals.remove("강아지"); // UnsupportedOperationException 발생!
Set<String> overlapAnimals = Set.of("강아지", "강아지", "고양이", "카피바라"); // IllegalArgumentException 발생!

 


 

 

2. Double Brace Initialization({{ ... }})을 이용

( 추가, 갱신, 삭제 모두 가능 )

Set<String> animals = new HashSet<String>(){{
	add("강아지");
	add("고양이");
	add("카피바라");
}};

 

 

 

 

 


 

 

 

 

 

Map

 

Map<String, String> animals = new HashMap<>();
animals.put("강아지", "귀여움");
animals.put("고양이", "엉뚱함");
animals.put("카피바라", "착함");

 

초기화 하는 방법은 아래와 같다.

 

 


 

 

1. Map.of

Java9 이상부터 사용 가능하며, 변환이 불가능하다.

( 추가, 갱신, 삭제 모두 불가능 )

Map<String, String> animalsInfo = Map.of("강아지", "귀여움", "고양이", "엉뚱함", "카피바라", "착함");
carInfo.put("푸바오", "공주"); // UnsupportedOperationException 발생!
carInfo.remove("강아지"); // UnsupportedOperationException 발생!

 

위 방법은 요소의 개수가 적을 때만 사용하는 것이 좋으며,

그 이상의 Map을 생성할 땐 Map.ofEnties를 사용하는 것이 좋다고 한다.

 

Map<String, String> animalsInfo = Map.ofEntries(
	Map.entry("강아지", "귀여움"),
	Map.entry("고양이", "엉뚱함"),
	Map.entry("카피바라", "착함")
);

 

 


 

2. Double Brace Initialization({{ ... }})을 이용

( 추가, 삭제 모두 가능 )

Map<String, String> animals = new HashMap<String, String>(){{
	put("강아지", "귀여움");
	put("고양이", "엉뚱함");
	put("카피바라", "착함");
}};

 

 

 


 

 

 

 

 

 

리스트(List)와 집합(Set) 처리 

 

java8에서는 List, Set 인터페이스에 다음과 같은 메서드를 추가했다. 

메소드 설명
removeIf 조건에 만족하는 요소를 제거한다.
(List, Set 구현 및 구현을 상속받은 곳에서 사용가능)
replaceAll 리스트에서 이용할 수 있는 기능으로 UnaryOperator 함수를 이용해 요소를 바꾼다.
sort List 인터페이스에서 제공하는 기능으로 리스트를 정렬한다.

 


 

 

removeIf

 

컬렉션을 아래 코드와 같이 for문을 반복해 제거하면 오류가 발생한다.

List<String> animals = new ArrayList<String>(){{
	add("강아지"); add("고양이"); add("카피바라");
}};

for (String animal : animals) { 
	if (animal.equals("강아지")) {
		animals.remove(animal); // ConcurrentModificationException 발생!
	}
}
ConcurrentModificationException 의 발생 원인

리스트를 순회하는 중 요소를 삭제하거나 변경하는 로직을 짤 경우,
Index가 변경되어 일부 요소를 순회하지 않을 수 있기 때문에 런타임 시 오류를 발생시킴.

해결 방법으론, 역순으로 반복문을 돌거나 removeAll(), removeIf()를 활용하는 방법이 있다.

 

 

위의 예제에서 오류가 발생하는 이유는 for문은 내부적으로 Iterator 객체를 사용하므로, for문은 아래와 같이 해석된다고 한다.

반복문에 사용되는 iterator와 컬렉션(animals)의 상태가 서로 동기화되지 않기 때문에 Index 문제가 발생하는 것이다.

 

for ( Interator<String> iterator = animals.iterator(); iterator.hasNext(); ) {
	String animal = iterator.next();
	if (animal.equals("강아지")) {
		animals.remove(animal);
	}
}

 

 

위와 같은 문제의 해결 방법으로 간단하게 removeIf를 사용할 수 있다.

animals.removeIf(animal -> animal.equals("강아지"));

// 여러개를 삭제할 경우
animals.removeIf(animal -> animal.equals("강아지") || animal.equals("고양이"));
removeAll 을 이용할 경우
animals.removeAll(new ArrayList<String>(){{
	add("강아지");
}});​

 

 


 

 

replaceAll

 

리스트들의 요소들에 대해 전체적으로 데이터 변경이 필요한 경우 for문을 통해 변경해줄 수 있다.

for (String animal : animals) {
	int idx = animals.indexOf(animal);
	animals.set(idx, "귀여운 " + animal);
}

 

 

위처럼 for문을 사용하는 것 외에 stream의 map을 활용해 매핑해주는 방법도 있다.

animals = animals.stream().map(animal -> "귀여운 " + animal).collect(Collectors.toList());

stream 을 이용할 경우 아래처럼 iterator를 이용해 새로운 컬렉션을 만드는 것을 확인할 수 있다.

효율성으로도 좋지 않고, 다시 list로 감싸줘야하는 번거로움이 있다.

for (ListIterator<String> iterator = animals.listIterator(); iterator.hasNext(); ) {
	String animal = iterator.next();
	iterator.set("귀여운 " + animal);
}

 

 

아래처럼 간단하게 replaceAll 메소드를 통해 요소들의 데이터를 변경할 수 있다.

animals.replaceAll(animal -> "귀여운 " + animal);
// [귀여운 강아지, 귀여운 고양이, 귀여운 카피바라]

 

 

 

 

 


 

 

 

 

 

 

맵(Map) 처리

 

Java8에서는 Map 인터페이스에 몇 가지 default 메소드를 추가했다.

 


 

 

forEach

 

Map 에서 복잡하게 key와 value를 찾아낼 필요 없이 forEach를 이용해 key와 value를 한 번에 가져올 수 있다.

animals.forEach((key, value) -> System.out.println(key + ":" + value));

 

위의 코드는 아래와 같이 해석할 수 있다.

for (Map.Entry<String, String> entry: animals.entrySet()) {
	String key = entry.getKey();
	String value = entry.getValue();
	System.out.println(key + ":" + value);
}

 

 


 

 

정렬 메서드

 

아래 두 개의 메소드를 이용해 Map의 key 또는 value를 기준으로 정렬을 할 수 있다.

  • Entry.comparingByValue
  • Entry.comparingByKey
animals.entrySet()
	.stream()
	.sorted(Map.Entry.comparingByKey())
	.forEachOrdered(System.out::println);

 

 


 

 

getOrDefault

 

찾으려는 key 가 존재하지 않으면 null로 인해 오류가 발생할 수 있다.

이 오류를 방지하기 위해 getOrDefault 메소드를 이용해 null 값일 경우 default 값을 사용할 수 있다.

※ key가 존재하는데 value가 null일 경우엔 default를 반환하지않고 null을 반환한다.

 

첫 번째 인수로 key값을 주고, 두 번째 인수로 default 값을 주면 된다.

String result = animals.getOrDefault("푸바오", "어쨋든 귀여움");

 

 

 


 

 

계산 패턴

 

Map에 key가 존재하는지 여부에 따라 인수의 동작을 실행하는 메소드이다.

메소드 내용
computeIfAbsent 제공된 키에 해당하는 값이 없으면(값이 없거나 null), 
키를 이용해 연산 후 맵에 추가한다.
computeIfPresent 제공된 키가 존재하면, 새 값을 계산하고 맵에 추가한다.
제공된 키가 존재하지 않으면, 작업을 수행하지 않는다.
compute 제공된 키로 새 값을 계산하고 맵에 저장한다.

 

Map<String, String> animals = new HashMap<>();
animals.put("카피바라", "착함");
animals.put("쿼카", " 만지면 ");

animals.computeIfAbsent("푸바오", (key) -> "귀여운 " + key); // key가 없기 때문에 추가
animals.computeIfAbsent("푸바오", (key) -> key + "🎀"); // key가 존재하기 때문에 실행하지 않음

animals.computeIfPresent("기니피그", (key, value) -> value + key); // key가 존재하지 않기 때문에 실행하지 않음

animals.compute("쿼카", (key, value) -> key + value + "벌금"); // key가 있든 없든 실행

// {카피바라=착함, 쿼카=쿼카 만지면 벌금, 푸바오=귀여운 푸바오}

 

 

 

 


 

 

삭제 패턴

 

remove 메소드를 이용해 요소를 삭제하는 방법은 많이 사용해보았지만 value를 인수로 주어 해당 value와 일치하지 않으면 지우지 않는 방법도 있다.

 

아래와 같은 경우는 value가 "착함" 이므로 삭제되지 않는다.

Map<String, String> animals = new HashMap<>();
animals.put("카피바라", "착함");

animals.remove("카피바라", "🐷");

 

 

 

 


 

 

Map 합치기

 

아래의 예제에제는 Integer 타입의 value를 합쳐서 numbers2 Map에 저장하는 예제이다.

Map<String, Integer> numbers1 = new HashMap<>() {{
	put("number1", 10);
	put("number2", 20);
	put("number3", 30);
	put("number4", 40);
}};

Map<String, Integer> numbers2 = new HashMap<>() {{
	put("number1", 1);
	put("number2", 2);
	put("number3", 3);
}};

numbers1.forEach((k, v) -> numbers2.merge(k, v, Integer::sum));

// numbers2 : {number1=11, number2=22, number3=33, number4=40}

 

 

Integer 뿐만 아니라 함수를 활용할 수 있다면 String 도 가능하다.

Map<String, String> animals1 = new HashMap<String, String>() {{
	put("고양이", "엉뚱함");
	put("강아지", "귀여움");
	put("비둘기", "구구");
}};

Map<String, String> animals2 = new HashMap<String, String>() {{
	put("고양이", "🐱");
	put("강아지", "🐶");
	put("카피바라", "🐷");
}};

animals1.forEach((k, v) -> animals2.merge(k, v, String::concat));

// animals2 : {카피바라=🐷, 고양이=🐱엉뚱함, 비둘기=구구, 강아지=🐶귀여움}

 

 

 

 

 

 


참고사이트

[모던 자바] 컬렉션 팩토리(Collection Factory) 소개 및 사용법

 

 

728x90