본문 바로가기
  • 개발공부 및 일상적인 내용을 작성하는 블로그 입니다.
부트캠프/후기 챌린지

[멋쟁이사자처럼부트캠프] 백엔드 자바 21기 (2026.01.26) #2

by 방구석 취준생 2026. 3. 29.

* 해당 글은 백엔드 자바 강의이후 회고 글입니다.

https://bootcamp.likelion.net/school/kdt-backendj-21th

 

백엔드 부트캠프 21기: Java : 멋사 부트캠프

실전 스킬 기반 백엔드 개발자 취업 완벽 대비 교육

bootcamp.likelion.net

 

 

Set 인터페이스

순서와 상관없이 중복을 허용하지 않는 경우에는 Set 인터페이스를 구현한 클래스를 사용한다.

Set 인터페이스를 구현한 대표 클래스에는 HashSet, TreeSet 이 있다.

 

 

HashSet 클래스

HashSet 클래스는 집합 자료구조를 구현하며 중복을 허용하지 않는다. 

아래의 코드와 같이 똑같은 데이터를 여러개 넣어도 HashSet 객체 내부에서는 같은 데이터를 여러개 저장하지 않고 하나의 데이터당 한 개만 저장하는 것을 알 수 있다.

public class HashSetTest {
    public static void main(String[] args){
        HashSet<String> hashSet = new HashSet<String>();
        hashSet.add(new String("임정순"));
        hashSet.add(new String("박현정"));
        hashSet.add(new String("홍연의"));
        hashSet.add(new String("강감찬"));
        hashSet.add(new String("강감찬"));
        
        System.out.println(hashSet);
    }
}

 

HashSet 을 활용해 회원관리 프로그램 만들어보기

HashSet 을 이용해 회원을 관리하는 프로그램을 만들어보자.

구현할 메서드는 MemberArrayList 때와 동일하다.

public class MemberHashSet {
    private HashSet<Member> hashSet;
    
    public MemberHashSet(){
        hashSet = new HashSet<Member>();
    }
    
    public void addMember(Member member){
        hashSet.add(member);
    }
    
    public boolean removeMember(int memberId) {
        // Set 자료구조는 데이터의 순서가 없기 때문에 Iterator 클래스를 이용해 순회한다.
        Iterator<Member> ir = hashSet.iterator(); 
        
        while(ir.hasNext()){
            Member member = ir.next(); // 회원 정보를 하나씩 가져온다.
            int tempId = member.getMemberId(); // 아이디 비교를 위해 데이터 추출
            if(tempId == memberId) { // 같은 아이디인 경우
                hashSet.remove(member); // 회원 삭제
                return true;
            }
        }
        
        System.out.println(memberId + " 가 존재하지 않습니다.");
        return false;
    }
    
    public void showAllMember(){
        for(Member member : hashSet){
            System.out.println(member);
        }
        System.out.println();
    }
}

 

 

 

  • HashSet 객체에는 저장되어 있는 데이터에 순서가 없기 때문에, 회원을 삭제할 때 ArrayList 에서 get(i) 메서드를 사용하여 처음부터 순회를 돌았던것과 달리  Iterator 클래스를 사용하여 순회를 돌았다.

아래와 같이 테스트 프로그램을 작성하여 MemberHashSet 클래스가 잘 구현되었는지 확인해볼 수 있다.

 

public class MemberHashSet {
    public static void main(String[] args){
        MemberHashSet memberHashSet = new MemberHashSet();
        
        Member memberLee = new Member(1001, "이지원");
        Member memberSon = new Member(1002, "손민국");
        Member memberPark = new Member(1003, "박서훤");
        
        memberHashSet.addMember(memberLee);
        memberHashSet.addMember(memberSon);
        memberHashSet.addMember(memberPark);
        memberHashSet.showAllMember();
        
        // 아이디 중복회원 추가
        Member memberHong = new Member(1003, "홍길동");
        memberHahsSet.addMember(memberHong);
        memberHashSet.showAllMember();
    }
}

 

  • 위의 코드를 실행시켜보면 같은 아이디 1003 을 가진 "박서훤" 과 "홍길동" 회원이 같이 그대로 출력되는 것을 확인해볼 수 있다.
  • 여기서 같은 회원이라는 것은 회원 아이디가 같다는 뜻인데 원래 HashSet 의 정의대로라면 홍길동 회원은 추가되지 않아야 한다.
  • 처음 작성했던 코드에서는 "강감찬" 이라는 데이터가 중복으로 저장되지 않았었는데 그 이유는 String 클래스에는 객체가 동일한 경우에 대한 처리 방법이 이미 구현되어 있기 때문이다. 
  • 그럼 Member 클래스에도 같은 객체를 처리하는 방법을 구현해보자.

 

* 객체가 동일함을 구현하기

기본적으로 인스턴스 주소가 같아야 같은 객체이다. 하지만 여기서는 회원 아이디가 같아도 같은 회원이어야 한다.

Object 클래스에서는 논리적으로 같은 객체를 구현하기 위해 equals() 메서드와 hashCode() 메서드를 재정의하였다.

그러므로 Member 클래스에도 equals() 메서드와 hashCode() 메서드를 재정의하여 회원 아이디가 같으면 같은 회원임을 아래와 같이 구현해주어야 한다.

 

아래의 코드를 보자.

public class Member {
    private int memberId;
    private String memberName;
    
    // ...
    
    @Override
    public int hashCode(){
        return memberId; // hashCode() 메서드가 회원 아이디를 반환하도록 재정의
    }
    
    @Override
    public boolean equals(Object obj){
        if(obj instanceof Member){
            Member member = (Member) obj;
            // 매개변수로 받은 회원 아이디가 자신의 회원 아이디와 같다면 true 반환
            if(this.memberId == member.memberId)
                return true;
            else
                return false;
        }
        
        return false;
    }
}

 

  • Member 클래스에 위와같이 equals() 와 hashCode() 메서드를 재정의하고 MemberHashSetTest 를 수행해서 출력 결과를 확인해보면 이번엔 아이디가 같은 회원은 추가되지 않는것을 알 수 있다.

* Set 인터페이스의 중복 데이터 처리 주요 특징 및 방법

  • 동작 원리 : 
    • 데이터를 추가할 때 (add()), 기존 요소들과 hashCode() 로 해시 코드를 비교하고, 같으면 equals() 로 데이터를 상세 비교하여 중복을 판단한다.
  • 사용자 정의 객체처리 :
    • HashSet 등에서 객체 필드값이 같을 때 중복으로 처리하려면 equals() 와 hashCode() 메서드를 반드시 재정의 해주어야 한다.
    • 이를 구현하지 않으면 주소값을 기준으로 비교하여 다른 객체로 인식한다.
  • 구현 클래스별 특징
    • HashSet : 가장 대표적인 Set 클래스, 순서를 보장하지 않으며 빠른 속도로 중복 제거
    • LinkedHashSet : 입력된 순서(insertion order) 를 유지하면서 중복 제거
    • TreeSet : 데이터의 오름차순(기본) 또는 설정한 정렬 순서를 유지하며 중복 제거

 

TreeSet 클래스

자바의 Collection 인터페이스나 Map 인터페이스를 구현한 클래스 중 Tree 로 시작하는 클래스는 데이터를 추가한 후 결과를 출력하면 결과 값이 정렬된다.

TreeSet 은 자료의 중복을 허용하지 않으면서 출력 결과 값을 정렬하는 클래스이다.

아래는 TreeSet 을 활용한 간단한 코드이다.

public class TreeSetTest{
    public static void main(String[] args){
        TreeSet<String> treeSet = new TreeSet<String>();
        treeSet.add("홍길동");
        treeSet.add("강감찬");
        treeSet.add("이순신");
        
        for(String str : treeSet) {
            System.out.println(str);
        }
    }
}

 

 

  • 위의 클래스를 실행시켜 보면 TreeSet 자료구조에 추가된 문자열 데이터들이 사전순으로 정렬되어 출력되는 것을 확인해볼 수 있다.

이와같은 TreeSet 의 정렬은 어떤 기준으로 이루어질까?

자바는 정렬을 구현하기 위해 여기서 이진 트리(Binary Tree) 를 사용한다.

 

이진 검색트리(Binary Search Tree : BST)

트리는 자료 사이의 계층 구조를 나타내는 자료 구조이다. 

TreeSet 을 이해하기 위해 필요한 이진 검색 트리(Binary Search Tree : BST) 에 대해 간단히 알아보자. 

 

  부모 노드(parant node)  
왼쪽 자식 노드(left child node)   오른쪽 자식 노드(right child node)

 

  • 트리 자료 구조에서 각 자료가 들어가는 공간을 노드라고 한다.
  • 그리고 위아래로 연결된 노드의 관계를 부모-자식 노드(parent-child node) 라고 한다.
  • 이진 검색트리는 저장되는 자료의 중복을 허용하지 않고, 부모가 가지는 자식 노드의 수가 2개 이하이다.
  • 또한 왼쪽에 위치하는 자식 노드는 부모 노드보다 항상 작은값을 가진다.
  • 반대로 오른쪽에 놓인 자식 노드는 부모 노드보다 항상 큰 값을 가진다.
  • 따라서 어떤 특정값을 찾으려 할 때 한 노드와 비교해 비교한 노드보다 작은 값이면 왼쪽 자식노드 방향으로, 그렇지 않으면 오른쪽 자식노드 방향으로 이동한다.
  • 따라서 비교 범위가 평균 1/2 만큼씩 줄어들어 효과적으로 자료를 검색할 수 있다. 
  • 이진 검색 트리를 가장 왼쪽노드부터 시작하여 왼쪽 -> 부모 -> 오른쪽 순으로 순회하면 오름차순이 된다. 순회하다가 노드의 끝을 만나면 부모 노드로 올라간다. 
  • 반대로 오른쪽 -> 부모 -> 왼쪽 순으로 순회하면 내림차순이 된다.
  • 자바의 TreeSet 은 이진 검색트리를 활용하여 자료를 정렬한다.
  • 어떤 기준으로 값의 크기를 비교할 것인지는 프로그래머가 직접 구현해야 한다.

TreeSet 를 활용해 회원관리 프로그램 구현하기

TreeSetTest 클래스에서 별도의 코드를 구현하지 않아도 요소들이 정렬되었던 이유는 String 클래스 안에 정렬 방식이 이미 구현되어 있기 때문이다.

이제 TreeSet 를 활용하여 회원 관리 프로그램을 구현해보자.

동일한 Set 인터페이스를 구현한 클래스이므로 HashSet 대신 TreeSet 만 선언하여 생성하면 나머지는 코드는 같다.

 

코드는 아래와 같으며 회원 정렬 기준은 회원 아이디순으로 하였다.

public class MemberTreeSet {
    private TreeSet<Member> treeSet;
    
    public MemberTreeSet(){
        treeSet = new TreeSet<Member>();
        
        // TreeSet에 회원을 추가하는 메서드
        public void addMember(Member member){
            treeSet.add(member); 
        }
        
        // TreeSet 에서 회원을 삭제하는 메서드
        public boolean removeMember(int memberId){
            Iterator<Member> ir = treeSet.iterator();
            
            while(ir.hasNext()) {
                Member member = ir.next();
                
                int tempId = member.getMemberId();
                if(tempId == memberId){
                    treeSet.remove(member);
                    return true;
                }
            }
            System.out.println(memberId + " 가 존재하지 않습니다.");
            return false;
        }
        
        // 전체 회원을 출력하는 메서드
        public void showAllMember(){
            for(Member member : treeSet){
                System.out.println(member);
            }
            System.out.println();
        }
    }
}

 

위의 코드를 기반으로 MemberTreeSet 클래스를 테스트하는 코드를 아래와 같이 만들어서 실행해보자.

public class MemberTreeSetTest {
    public static void main(String[] args){
        MemberTreeSet memberTreeSet = new MemberTreeSet();
        
        Member memberPark = new Member(1003, "박서훤");
        Member memberLee = new Member(1001, "이지원");
        Member memberSon = new Member(1002, "손민국");
        
        memberTreeSet.addMember(memberLee);
        memberTreeSet.addMember(memberSon);
        memberTreeSet.addMember(memberPark);
        memberTreeSet.showAllMember();
        
        // 아이디 중복 회원 추가
        Member memberHong = new Member(1003, "홍길동");
        memberTreeSet.addMember(memberHong);
        memberTreeSet.showAllMember();
    }
}

 

  • 위의 코드를 실행하면 아이디 중복으로 들어온 데이터는 제거되고 회원 아이디로 정렬되어 잘 출력될 줄 알았는데 그렇지 않고 오류가 발생하는 것을 확인할 수 있다.
  • 오류를 살펴보면 collection.Member cannot be cast to java.base/java.lang.Comparable ... 라는 형식의 오류가 출력되고 있는것을 확인해볼 수 있다.
  • 이 오류는 Member 클래스가 Comparable 인터페이스를 구현하지 않았다는 의미이다. 
  • Comparable 인터페이스를 구현하지 않았다는 말의 의미는 앞서만든 Member 클래스를 TreeSet 의 요소로 추가할 때 어떤 기준으로 노드를 비교하여 트리를 형성해야 하는지를 구현하지 않았다는 뜻이다.
  • 따라서 회원을 TreeSet 에 추가할 때 어떤 기준으로 비교할 것인지를 구현해주어야 한다.
  • 이때 사용하는 인터페이스가 바로 Comparable 또는 Comparator 이다.

 

Comparable 인터페이스와 Comparator 인터페이스

Member 클래스가 가진 회원 아이디를 기준으로 하여 오름차순으로 정렬해야 한다.

Comparable 과 Comparator 는 이러한 정렬을 구현할 수 있게 해주는 인터페이스이다.

그렇다면 정렬 방식을 어디에 구현해야 할까? 정렬 기준값이 있는 Member 클래스에 구현하면 된다.

public class Member implements Comparable<member> {
    // ....
}

 

 

* 자기 자신과 전달받은 매개변수를 비교하는 Comparable 인터페이스

Comparable 인터페이스에는 compareTo() 추상 메서드가 포함되어 있다.

따라서 이 인터페이스를 구현하는 Member 클래스에서 compareTo() 메서드를 구현해야 한다.

compareTo() 메서드를 구현한 Member 클래스 코드는 다음과 같다.

 

public class Member implements Comparable<Member> {
    private int memberId;
    private String memberName;
    
    public Member(int memberId, String memberName) {
        this.memberId = memberId;
        this.memberName = memberName;
    }
    
    // ....
    
    // compareTo() 메서드 재정의
    // 추가한 회원 아이디와 매개변수로 받은 회원 아이디를 비교함
    @Override
    public int compareTo(Member member) {
        return(this.memberId - memberId.memberId);
    }
}

 

  • 위의 코드에서 재정의한 compareTo() 메서드의 의미는 다음과 같다.
  • 비교 대상은 this 의 회원 아이디, 즉 새로 추가한 회원의 아이디와 compareTo() 메서드의 매개변수로 전달된 회원 아이디이다.
  • 두 값을 비교하여 새로 추가한 회원 아이디가 더 크면 양수, 그렇지 않으면 음수, 같으면 0을 반환하도록 만들었다.
  • 이렇게 구현하면 출력 결과값은 오름차순으로 정렬된다.

compareTo() 의 반환값은 정수 값인데, 비교하는 두 값중 this 값이 더 크면 양수를 반환하여 오름차순으로 정렬된다.

그렇지 않고 this 값이 더 작으면 음수를 반환하여 내림차순으로 정렬된다.

compareTo() 는 프로그래머가 호출하는 메서드가 아닌 객체가 TreeSet 에 요소를 추가할 때 호출되는 메서드이다.

그리고 어떤 매개변수가 전달될지는 기존 TreeSet 에 어떤 요소가 들어있는지에 따라 달라진다.

이제 MemberTreeSetTest 클래스를 다시 실행하면 이번엔 오류없이 정상적으로 데이터가 정렬되어 출력되는 것을 확인해볼 수 있다.

 

만약 정렬방식을 내림차순으로 바꾸려면 아래의 코드와 같이 compareTo() 메서드를 수정하면 된다.

// 내림차순으로 정렬하기 위해 반환값을 음수로 만듬
@Override
public int compareTo(Member member) {
    return (this.memberId - member.memberId) * (-1);
}

 

 

* 두 매개변수를 비교하는 Comparator 인터페이스

Comparator 인터페이스 역시 정렬을 구현하는데 사용하는 인터페이스이다.

Comparator 인터페이스는 compare() 메서드를 구현해야 한다.

Member2 클래스를 새로 만들어 Comparator 를 구현한 코드는 아래와 같다.

public class Member2 implements Comparator<Member2> {
    private int memberId;
    private String memberName;
    
    public Member2(int memberId, String memberName) {
        this.memberId = memberId;
        this.memberName = memberName;
    }
    
    // ....
    
    // compare() 메서드 재정의
    // 전달받은 두 매개변수를 비교한다.
    @Override
    public int compare(Member2 mem1, Member2, mem2) {
        return mem1.getMemberId() - mem2.getMemberId();
    }
}

 

  • Comparator 인터페이스는 compare() 메서드를 구현해야 하는데, 이 메서드에는 매개변수가 2개 전달된다.
  • compareTo() 메서드는 this 와 전달된 매개변수를 비교하였다면, compare() 메서드는 전달되는 두 매개변수를 비교한다.
  • 첫번째 매개변수가 더 클 때 양수를 반환하여 오름차순으로 정렬된다.

Comparator 를 사용할 때 유의할 점은 TreeSet 생성자에 Comparator 를 구현한 객체를 매개변수로 전달한다는 것이다.

즉, 다음과 같이 코드를 작성해야 한다.

TreeSet<Member> treeSet = new TreeSet<Member>(new Member());

 

  • 일반적으로 Comparator 인터페이스보다는 Comparable 인터페이스를 더 많이 사용한다.
  • 다만 어떤 클래스가 이미 Comparable 인터페이스를 구현한 경우에 이 클래스의 정렬 방식을 정의할 때 Comparator 인터페이스를 사용할 수 있다.
  • 예를 들어 String 클래스는 Comparable 인터페이스를 이미 구현하고 있다, 그리고 Comparable 인터페이스의 compareTo() 메서드는 오름차순 정렬을 구현하고 있다.
  • 만약 정렬 방식을 내림차순으로 바꾸고 싶은 경우에는 어떻게 해야할까?
  • String 클래스의 경우 final 로 선언되어 있어서 상속받아 compareTo() 메서드를 재정의 할 수도없다. 이러한 경우 Comparator 를 사용한다.
class MyCompare implements Comparator<String> {

    // 내림차순으로 정렬
    // String 은 이미 Comparable 인터페이스를 내부적으로 구현하고 있으므로
    // compareTo() 메서드를 가져와서 활용할 수 있다.
    @Override
    public int compare(String s1, String s2) {
        return (s1.compareTo(s2)) * -1;
    }
}

public class ComparatorTest {
    public static void main(String[] args){
        // TreeSet 생성자의 매개변수로 정렬 방식을 지정
        Set<String> set = new TreeSet<String>(new MyCompare());
        set.add("aaa");
        set.add("ccc");
        set.add("bbb");
        
        System.out.println(set);
    }
}

 

  • TreeSet 클래스를 생성할 때 매개변수를 넣지 않으면 원래 String 클래스에 정의된 Comparable 인터페이스의 compareTo() 메서드 구현 내용대로 오름차순으로 정렬된다.
  • 위의 코드에서는 TreeSet 클래스 생성자에 Comparator 인터페이스를 구현한 MyCompare 인스턴스를 매개변수로 넣었기 때문에, 재정의한 compare() 메서드 방식에 따라 내림차순으로 정렬 방식이 바뀐다.