Java 컬렉션과 제네릭

List·Set·Map을 조회와 중복 규칙에 맞게 선택하고 검색·정렬 구현하기

이번 문서에서 만드는 것

4주에는 고정 길이 배열 대신 회원 추가·ID 조회·검색·정렬에 맞는 컬렉션을 선택합니다. 2주의 Member 클래스를 사용합니다. 컬렉션 메서드를 전부 외우기보다 데이터의 순서와 중복 규칙부터 정합니다.

구조주로 쓰는 상황주의할 점
List순서 있는 명단, 같은 값도 허용ID 검색을 직접 반복할 수 있음
Set중복 없는 트랙·태그객체 중복 판단은 equals·hashCode 계약에 영향받음
Map회원 ID로 회원 찾기같은 키에 put하면 기존 값이 교체됨

제네릭 List<Member>는 이 목록에 Member를 담겠다는 컴파일 시 계약입니다. raw type이나 강제 캐스팅으로 경고를 없애지 않습니다. null을 포함할 수 있는 컬렉션인지도 생성 방식에 따라 다릅니다.

실행 가능한 명단 예제

새 폴더에 앞 주 Member.java를 복사하고 RosterDemo.java를 작성합니다.

Java
import java.util.ArrayList;import java.util.Comparator;import java.util.LinkedHashMap;import java.util.LinkedHashSet;import java.util.List;import java.util.Map;import java.util.Set;
public class RosterDemo {    public static void main(String[] args) {        Map<Long, Member> byId = new LinkedHashMap<>();        add(byId, new Member(1, "나래"));        add(byId, new Member(2, "가람"));        add(byId, new Member(3, "가람"));
        List<Member> sorted = new ArrayList<>(byId.values());        sorted.sort(Comparator.comparing(Member::getName).thenComparingLong(Member::getId));        for (Member member : sorted) {            System.out.println(member.getId() + ": " + member.getName());        }
        Member found = byId.get(999L);        System.out.println(found == null ? "없는 회원" : found.getName());        System.out.println("검색: " + search(sorted, "가").size());
        Set<String> tracks = new LinkedHashSet<>(List.of("frontend", "design", "frontend"));        System.out.println(tracks);        try {            add(byId, new Member(1, "중복"));            throw new AssertionError("중복 ID 허용");        } catch (IllegalArgumentException expected) {            System.out.println("중복 ID 거절");        }    }
    static void add(Map<Long, Member> members, Member member) {        if (members.putIfAbsent(member.getId(), member) != null) {            throw new IllegalArgumentException("이미 등록된 ID입니다.");        }    }
    static List<Member> search(List<Member> members, String query) {        String keyword = query.strip();        List<Member> result = new ArrayList<>();        for (Member member : members) {            if (member.getName().contains(keyword)) result.add(member);        }        return result;    }}
Shell
javac -encoding UTF-8 Member.java RosterDemo.javajava RosterDemo

가람 두 명이 서로 다른 ID로 남아 있어야 합니다. LinkedHashMap과 LinkedHashSet은 입력 순서를 유지합니다. HashMap의 순회 순서를 화면 정렬 계약으로 사용하지 않습니다. 이름순이 필요하면 예제처럼 정렬 기준을 명시합니다.

new ArrayList<>(byId.values())는 목록 구조를 복사하지만 Member 객체까지 깊게 복사하지 않습니다. 복사한 목록에서 Member를 rename하면 같은 객체를 가리키는 Map에서도 이름이 바뀝니다. “복사했다”와 “객체 상태까지 독립적이다”를 구별합니다.

실수와 회복 방법

  • for-each 순회 중 같은 목록에서 직접 remove하면 ConcurrentModificationException이 발생할 수 있습니다. 조건 삭제에는 removeIf, 복잡한 삭제에는 Iterator의 remove를 검토합니다.
  • List.of는 수정 불가능한 목록입니다. 추가가 필요하면 new ArrayList<>(...)로 시작합니다.
  • 새 Member 객체를 Set에 넣었다고 ID 중복이 자동으로 제거되지는 않습니다. 이 예제는 ID를 Map의 키로 사용해 중복 규칙을 명확히 합니다.
  • 이 Map은 단일 콘솔 실습용입니다. 동시 HTTP 요청에서 안전한 저장소라고 가정하지 않습니다.

세션 실습과 완료 기준

권장 90분: 구조 선택 20분, 실행·예측 25분, 기능 추가 25분, 리뷰 20분입니다. 이름 검색 결과를 ID 내림차순으로 정렬하고 없는 ID 삭제가 데이터에 영향을 주지 않는지 확인합니다.

완료 기준은 같은 이름·다른 ID, 같은 ID·다른 이름, 빈 목록, 없는 ID 네 경우를 실제로 검사하는 것입니다. 제출물에 List 대신 Map을 선택한 조회 한 곳과 그 이유를 적습니다. Stream 변환은 선택 연습이며 반복문을 완전히 이해한 뒤 진행합니다.

더 읽어보기

연결된 PBL 미션과 VOD

주차·미션참고 VOD 범위
4주 · 컬렉션Java 첫걸음 8·9장

강좌 안내: 참고 강좌 1. 이 문서는 영상 전체를 옮긴 전사 자료가 아닙니다. 세부 내용은 위 공식 문서에서 확인합니다.