자바 9
에서는 작은 컬렉션 객체
를 쉽게 만들 수 있는 몇 가지 방법을 제공한다.
자바에서는 적은 요소를 포함하는 리스트
를 어떻게 만들까?
List<String> friends = Arrays.asList("Raphael", "Olivia", "Thibaut");
friends.set(0, "Richard"); // 문제 없음
friends.add("Tom"); //UnsupportedOperationException 발생
public static <T> List<T> asList(T... a) {
return new ArrayList<>(a);
}
private static class ArrayList<E> extends AbstractList<E>
implements RandomAccess, java.io.Serializable
{
private final E[] a;
ArrayList(E[] array) {
a = Objects.requireNonNull(array);
}
...
}
그렇다면 set은??
Set<String> elems1 = new HashSet<>(Arrays.asList("e1","e2","e3"));
Set<String> elems2 = Stream.of("e1","e2","e3").collect(toSet());
List.of
불변 리스트
를 만든다.Set.of
불변 집합
을 만든다.IllegalArgumentException
이 발생한다.Map.of
Map.ofEntries
List.of
팩토리 메소드를 이용해서 간단하게 리스트를 만들 수 있다.
List<String> friends = List.of("Raphael", "Olivia", "Thibaut");
Arrays.asList
방법과 다르게 List.of
는 추가, 삭제뿐만 아니라 변경(set)도 할 수 없고 null
추가가 불가능한스트림 API
vs 리스트 팩토리
데이터 처리 형식을 설정하거나 데이터를 변환할 필요가 없다면 사용하기 간편한 팩토리 메서드를 사용하면 된다 !
// OK
Set<String> friends = Set.of("Raphael", "Olivia", "Thibaut");
// 요소가 중복되어 있다는 IllegalArgumentException 발생
Set<String> friends = Set.of("Raphael", "Olivia", "Olivia");
List.of
와 비슷한 방법으로 바꿀 수 없는 집합을 만들 수 있다.자바 9
에서는 두 가지 방법으로 바꿀 수 없는 맵을 만들 수 있다.
Map.of
팩토리 메서드에 키와 값을 번갈아 제공하는 방법Map<String, Integer> ageOfFriends =
Map.of("Raphael", 30, "Olivia", 25, "Thibaut", 26);
오버로딩
으로 10개까지 지원해둔 것)Map.ofEntries
이용import static java.util.Map.entry;
Map<String, Integer> ageOfFriends = Map.ofEntries(
entry("Raphael", 30),
entry("Olivia", 25),
entry("Thibaut", 26));
자바 8
에서는 List
, Set
인터페이스에 다음와 같은 메서드를 추가했다.
removeIf
replaceAll
UnaryOperator
: Function(T, T), T → Tsort
그런데 이들 메서드는 호출한 컬렉션 자체를 바꾼다.
새로운 결과를 만드는 스트림
동작과 달리 이들 메서드는 기존 컬렉션을 바꾼다.컬렉션을 바꾸는 동작은 에러를 유발하며 복잡함을 더하기 때문이다!!
Iterator
와 Collection
의 상태를 동기화 시켜주어야 하기 때문이다. // ConcurrentModificationException 발생
for (Transaction transaction : transactions){
if(Charater.isDigit(transaction.getReferenceCode().charAt(0))){
transactions.remove(transaction);
}
}
// for-each 내부적으로 Iterator 객체를 사용하므로 아래와 동일
for(Iterator<Transaction> iterator = transactions.iterator();
iterator.hasNext(); ){
Transaction transaction = iterator.next();
if(Charater.isDigit(transaction.getReferenceCode().charAt(0))){
// 반복하면서 별도의 두 객체를 통해 컬렉션을 바꾸고 있음
transactions.remove(transaction);
}
}
iterator.remove()
사용java.util.ArrayList의 remove()
protected transient int modCount = 0;
public boolean remove(Object o) {
final Object[] es = elementData;
final int size = this.size;
int i = 0;
found: {
if (o == null) {
for (; i < size; i++)
if (es[i] == null)
break found;
} else {
for (; i < size; i++)
if (o.equals(es[i]))
break found;
}
return false;
}
fastRemove(es, i);
return true;
}
private void fastRemove(Object[] es, int i) {
modCount++;
final int newSize;
if ((newSize = size - 1) > i)
System.arraycopy(es, i + 1, es, i, newSize - i);
es[size = newSize] = null;
}
java.util.ArrayList의 이너 클래스 Itr
public Iterator<E> iterator() {
return new Itr();
}
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
// prevent creating a synthetic constructor
Itr() {}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
...
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
removeIf
로 해결 가능하다.transactions.removeIf(
transaction -> Charater.isDigit(transaction.getReferenceCode().charAt(0))
);
때로는 요소를 제거하는 것이 아닌 변경해야 할 상황이 있다.
스트림 API
를 사용하면 되지만 새 컬렉션을 만들기에 기존 컬렉션을를 바꾸고 싶은 경우 부적합
이때는 replaceAll
을 사용하여 데이터를 변경 가능 !
// 첫 단어만 대문자로 바꾸는 코드
referenceCodes.replaceAll(
code -> Charater.toUpperCase(code.charAt(0)) + code.subString(1)
);
맵을 조회하기 위한 기존의 반복 코드
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")
);
다음 두 개의 새로운 메서드를 이용하면 맵을 키 또는 값을 기준으로 정렬 가능
Entry.comparingByValue
Entry.comparingByKey
Map<String, String> favoriteMovies = Map.ofEntries(
Map.entry("ljo", "Star Wars"),
Map.entry("hsy", "Matrix"),
Map.entry("yhh", "James Bond")
);
favoriteMovies.entrySet().stream()
.sorted(Entry.comparingByKey())
.forEachOrdered(System.out::println); // 키 값 순서대로
hsy=Matrix
ljo=Star Wars
yhh=James Bond
기존에 찾으려는 키가 존재하지 않을 경우 NPE을 방지하기 위해 널 체크를 해야 했지만getOrDefault
를 이용하면 이를 해결 할 수 있다.
키
가 맵에 없으면기본값
을 반환한다.맵에 키가 존재하는지 여부에 따라 어떤 동작을 실행하고 결과를 저장해야 하는 상황이 필요한 때가 있다.
computeIfAbsent
computeIfPresent
compute
Ex) 허승연님에게 줄 영화 목록을 만든다고 가정
String friend = "hsy";
List<String> movies = friendsToMovies.get(friend);
if (movies == null){ // 초기화 확인
movies = new ArrayList<>();
friendsToMovies.put(friend, movies);
}
movies.add("Iron man"); // 영화 추가
friendsToMovies.computeIfAbsent("Raphael", name -> new ArrayList<>)).add("Star Wars");
</br>
remove
메서드는 이미 알고 있다자바 8
에서는 키가 특정한 값과 연관되어 있을 때만 항목을 제거하는 오버로드 버전
메서드를 제공한다.map.remove(key, value);
맵의 항목을 바꾸는데 사용할 수 있는 메서드들
replaceAll
Bifunction
을 적용한 결과로 각 항목의 값을 교체한다.List
의 replaceAll
과 비슷한 동작을 수행Replace
오버로드 버전
도 있다.두 개의 맵에서 값을 합칠 때 조건
을 걸고 합치려면 merge
메서드 이용
Map<String, String> family = Map.ofEntries(
entry("Teo", "Star Wars"), entry("Cristina", "James Bond")
);
Map<String, String> friends = Map.ofEntries(
entry("Raphael", "Star Wars"), entry("Cristina", "Matrix")
);
// merge 메서드 사용 - 조건에 따라 맵을 합치는 코드
Map<String, String> everyone = new HashMap<>(family);
friends.forEach((k, v) ->
everyone.merge(k, v, (movie1, movie2) -> movie1 + " & " + movie2)
);
{Raphael=Star Wars, Cristina=James Bond & Matrix, Teo=Star Wars}
어떻게 합칠지 결정하는 BiFunction
을 인수로 받는다.ConcurrentHashMap
는 내부 자료구조의 특정 부분만 잠궈 동시
추가
, 갱신
작업을 허용
ConcurrentHashMap
은 스트림에서 봤던 것과 비슷한 종류의 세 가지 새로운 연산을 지원한다.
forEach
reduce
search
null
이 아닌 값을 반환할 때까지 각 (키, 값) 쌍에 함수를 적용또한, 다음 처럼 4가지 연산 형태를 지원
forEach
, reduce
, search
)forEachKey
, reduceKey
, searchKey
)forEachValue
, reduceValue
, searchValue
)Map.Entry
객체로 연산 ( forEachEntry
, reduceEntry
, searchEntry
)위의 연산들은 ConcurrentHashMap의 상태를 잠그지 않고 연산을 수행한다.
따라서, 이들 연산에 제공한 함수는 계산이 진행되는 동안 바뀔 수 있는 객체, 값, 순서 등에 의존하지 않아야한다.
그리고 이들 연산에 병렬성 기준값(threshold
) 를 지정해야한다.
기준값
보다 작으면 순차적
으로 연산을 실행한다.ConcurrentHashMap
클래스는 맵의 매핑 개수를 반환하는 mappingCount
메서드 제공ConcurrentHashMap
클래스는 집합 뷰로 반환하는 keySet
이라는 새 메서드 제공newKeySet
이라는 새 메서드를 이용해 ConcurrentHashMap
으로 유지되는 집합을 만들 수도 있다.