이번에 살펴볼 인터페이스는 Map이다.
Map은 아래의 그림에서 보는 것과 같이 데이터 저장을 위한 Key와 Value의 쌍으로 구성되어 있는 형태의 데이터 묶음이다. 물론 Key는 유일한 값이어야 하며, Value는 Key를 통해 찾고자 하는 데이터이다.
따라서 Map에 정의되어 있는 메소드는 이전에 살펴본 List와는 조금 다른 부분이 있다. Map에 정의된 추상 메소드들을 살펴보자
1) Map에 정의된 추상 메소드
표에서 보는 것과 같이 전달인자로 메소드의 대부분이 Key와 Value를 받는 Bi- 형태이며, 이외에도 여러 메소드가 있는데, 아래 소개한 메소드들은 default로 선언된 메소드들이다.
메소드 | 설명 |
V compute(K, BiFunction<K, V, V> f) | K에 해당하는 데이터를 전달받아 BiFunction의 apply() 메소드를 실행 |
V computeIfAbsent(K, Function<K, V> f) | Key를 전달받아 Function 인터페이스의 apply() 메소드를 실행 |
V computeIfPresent(K, BiFunction<K, V, V> f) | computeIfAbsent() 메소드와 유사한데, 전달된Key값이 존재하면 데이터가 존재 |
void forEach(BiConsumer<K, V> action) | Map의 전체 데이터에 반복해서 순환하여 BiConsumer인터페이스의 apply() 메소드 실행한다. BiConsumer의 apply() 메소드는 두 개의 데이터가 전달되고 반환되는 값이 없는 형태이다. |
V getOrDefault(K, V) | Key와 Value를 전달받아 Key가 존재하면 Key에 해당하는 Value를 반환하고 없을 경우 Value로 지정한 default값을 반환한다. |
V merge(K, V, BiFunction<K, V, V> f) | Map에 데이터를 병합하는 메소드 첫 번째 전달인자는 Key, 두 번째 전달인자는 Value, 세 번째 전달인자는 Lambda 함수 원본에 동일한 key가 존재하는지 여부와는 상관없이 값을 삽입된다. 반환되는 데이터는 삽입된 Value이다. |
V putIfAbsent(K, V) | 전달된 Key, Value가 존재하지 않으면 삽입 |
Boolean remove(K, V) | 전달된 Key, Value와 같은 데이터가 존재하면삭제한 후 삭제 결과를 Boolean 형태로 반환 |
V replace(K, V) | 전달된 Key, Value를 교체 한 후 교체되기 전 의 값을 반환 |
Boolean replace(K, V, V) | 전달인자를 3개 받는 replace() 메소드는 두 번째로 전달된 데이터를 세 번째로 전달된 데이터로 교체하게 되는데, 교체 여부를 Boolean 으로 반환한다. |
void replaceAll(BiFunction<K, V, V> f) | 이 메소드는 전달인자로 값을 받는 것이 아니라 Lambda 함수를 전달받게 되는데 BiFunction에 선언된 Lambda식은 2개의 전달값을 받아 전체를 교체한다. |
메소드의 사용 방법을 코드를 통해 하나씩 살펴보자.
MapTest1.java
import java.util.HashMap;
import java.util.Map;
public class MapTest1 {
public static void main(String[] args) {
Map<Integer, String> city = new HashMap<>();
city.put(1, "서울");
city.put(2, "인천");
city.put(3, "원주");
city.put(4, "광주");
city.put(5, "제주");
// 1) V compute(K, BiFunction<K, V, V> f)
city.compute(3, (k, v) -> v + "시" ); // "원주" ->"원주시"로 변경
// 2) Key로 전달된 1의 값이 있으면 그 데이터인 Value에 "특별시"라는 문자열을 붙인 후
// 그 결과를 반환하므로 temp를 출력하면 "서울특별시"가 출력되고, Map 데이터에서 1번 데이터의 값이
// "서울" -> "서울특별시"로 변경
// 존재하지 않는 Key를 전달하면 null이 반환된다.
String temp = city.computeIfPresent(1, (k, v) -> v +"특별시"); // temp에는 "서울특별시"
System.out.println(city);
// 3) 위의 메소드와는 다르게 Key로 전달된 6이 Map안에 존재하지 않을 경우 값이 삽입된다.
city.computeIfAbsent(6, (x) -> "부산"); // 6이 존재하지 않으면 city map에 key는 6, value는 "부산"을 삽입
System.out.println(city);
// 4) forEach(): 요소의 수만큼 반복 수행
city.forEach((k, v) -> System.out.println(k+"번 도시: " + v));
}
}
MapTest2.java
import java.util.HashMap;
import java.util.Map;
public class MapTest2 {
public static void main(String[] args) {
Map<Integer, String> city = new HashMap<>();
city.put(1, "서울");
city.put(2, "인천");
city.put(3, "원주");
city.put(4, "광주");
city.put(5, "제주");
// 5) V getOrDefault(K, V)
String temp = city.getOrDefault(2, "뉴욕");
System.out.println(temp);
temp = city.getOrDefault(12, "뉴욕");
System.out.println(temp);
// 6) V merge(K, V, BiFunction<K, V, V> f)
// Key는 5, Value는 "강릉"으로 세번째 전달인자인 Lambda에서 반환하는 v가 city에 merge된다.
// 반환되는 값은 삽입된 v이다.
// 동일한 키가 존재하면 replace, 존재하지 않으면 insert 된다.
temp = city.merge(6, "강릉", (k, v) -> v);
System.out.println("반환값: " +temp);
System.out.println("원본: " + city);
Map<Integer, String> zone = new HashMap<>();
zone.put(100, "동작");
zone.put(200, "서초");
zone.put(300, "강남");
zone.put(400, "종로");
// 반복적으로 Merge --> city에 zone 데이터를 병합
zone.forEach((k, v) -> city.merge(k, v, (key, value) -> value));
System.out.println(city);
// 7) V putIfAbsent(K, V)
// 전달된 Key가 Map에 존재하지 않으면 삽입된다.
// 반환되는 값은 Key에 해당하는 Value로, 동일한 Key가 존재하여 삽입이 안된 경우 해당 Key의 Value가, 삽입된 경우 null이 반환된다.
temp = zone.putIfAbsent(500, "양재");
System.out.println(zone);
System.out.println(temp);
// 8) Boolean remove(K, V)
// Key와 Value의 값이 정확할 경우 삭제.
// 삭제되면 true, 아니면 false 반환
System.out.println("삭제? " + zone.remove(300, "강남"));
// 9) V replace(K, V)
zone.replace(100, "관악");
System.out.println("replace() 결과: " + zone);
// 10) Boolean replace(K, V, V)
// 위의 전달인자 2개짜리 replace()와 동일한데, 메소드는 K, V가 동일해야 교체된다.
zone.replace(400, "종로", "강북");
System.out.println("replace() 결과: " + zone);
// 11) void replaceAll(BiFunction<K, V, V> f)
// 전체 데이터를 대상으로 처리
// 전체 데이터가 들어있는 city Map데이터 중 k의 값이 100 이상인 경우 값 뒤에 "구"를 붙인다.
city.replaceAll((k, v) -> k >= 100 ? v.concat("구"): v);
System.out.println(city);
}
}
각 메소드의 사용방법이나 결과는 코드 내에 주석으로 달아놓았기 때문에 주석을 참고해서 결과를 확인해보면 된다.
위에서 공부한 Map 인터페이스에 정의된 여러 메소들 이용해 몇 가지 문제를 풀어보자
Score.java
class Score {
String id; // 학번
String name; // 이름
int database; // DB 점수
int java; // Java 점수
int python; // Python 점수
public Score() { }
public Score(String id, String name, int database, int java, int python) {
super();
this.id = id;
this.name = name;
this.database = database;
this.java = java;
this.python = python;
}
@Override
public String toString() {
return String.format("%s번: %s %3d %3d %3d", id, name, database, java, python);
}
}
학생들의 정보를 Score.java 클래스가 위와 같이 선언되어 있다. (setter, getter는 따로 작성하지 않았음)
Score클래스를 값으로 갖는 Map을 생성하여 주어진 몇가지의 문제가 아래에 있다. 해결방법도 제시되어 있지만, 코드를 읽기 전에 먼저 풀어보는 것도 좋은 공부가 될 것이라고 생각된다.
MapTest3.java
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
public class MapTest3 {
public static void main(String[] args) {
Map<String, Score> aClass = new HashMap<>();
aClass.put("101", new Score("101", "손오공", 89, 99, 86));
aClass.put("102", new Score("102", "저팔계", 100, 90, 89));
aClass.put("103", new Score("103", "사오정", 90, 88, 78));
aClass.put("104", new Score("104", "홍길동", 80, 89, 75));
aClass.put("105", new Score("105", "전우치", 75, 100, 90));
// [문제-1] forEach를 이용해 학생들의 모든 정보를 출력하시오
System.out.println("== 학생 기본 정보 출력 ==");
aClass.forEach((k, v) -> System.out.println(v + " " + (v.java + v.database + v.python)));
// [문제-2] 학번을 입력받아 해당 학생의 평균을 계산하여 출력하는 코드를 작성하시오
String id = null;
Scanner keyin = new Scanner(System.in);
System.out.print("\n* 학번 입력: ");
id = keyin.nextLine();
Score find = aClass.get(id);
if(find != null) {
double avg = (find.database + find.database + find.python) / 3.0;
System.out.println(find.name + "의 평균: " + avg);
} else {
System.out.println("** 조회된 정보 없음");
}
// [문제-3] 자바점수를 1점씩 올려주기로 함. 단 자바점수가 100점이 아닌 학생만
// forEach() 이용
System.out.println("\n** 자바점수 1점씩 올려주기");
aClass.forEach((k, v) -> v.java = (v.java<100? v.java++: v.java));
aClass.forEach((k, v) -> System.out.println(v));
// [문제-4] 새로 전학 온 학생 등록하기
// putIfAbsent() 이용
System.out.println("\n** 전학생 등록");
Score temp = new Score();
temp.id = "600";
temp.name = "이몽룡";
aClass.putIfAbsent(temp.id, temp);
aClass.forEach((k, v) -> System.out.println(v));
// [문제-5] bClass학생과 aClass학생을 allClass 객체로 병합하시오
// forEach(), merge() 이용
Map<String, Score> allClass = new HashMap<>();
Map<String, Score> bClass = new HashMap<>();
bClass.put("201", new Score("201", "김콩쥐", 78, 80, 67));
bClass.put("202", new Score("202", "박팥쥐", 90, 66, 89));
bClass.put("203", new Score("203", "이방자", 80, 85, 90));
bClass.put("204", new Score("204", "정향단", 89, 80, 100));
bClass.put("205", new Score("205", "성춘향", 96, 90, 75));
System.out.println("\n** 전교생 **");
aClass.forEach((k, v) -> allClass.merge(k, v, (key, value) -> value));
bClass.forEach((k, v) -> allClass.merge(k, v, (key, value) -> value));
allClass.forEach((k, v) -> System.out.println(v));
}
}
총 5개의 문제가 주어졌는데, 위 코드에서 Value는 예제에서 본 것과 같은 문자열이 아니라 사용자가 정의한 Score 클래스이다.
Score 클래스를 Value로 갖는 Map을 생성한 후 문제를 하나씩 해결해 보고, Value값이 Integer나 String이 아닌 사용자 정의 클래스 인 경우 어떻게 해결하는지 보는 것도 큰 도움이 될 것이다.
다음에는 Lambda를 여러 형태로 결합해서 사용하는 방법을 살펴보도록 하자
다음글: 7. 메소드 참조 (::)
[Java] 7. 메소드 참조 (::) (1) | 2023.08.31 |
---|---|
[Java] 5. 컬렉션에 정의된 함수형 인터페이스 (0) | 2023.08.17 |
[Java] 4. java.util.function에 정의된 함수형 인터페이스 사용 (0) | 2023.08.08 |
댓글 영역