본문 바로가기

프로그래밍 언어/JAVA

JAVA 입문 - Set 인터페이스

지난 글: [프로그래밍 언어/JAVA] - JAVA 입문 - List 인터페이스

 

순서와 상관 없이 중복을 허용하지 않는 경우 Set 인터페이스를 구현한 클래스를 사용한다. 우리가 사용하는 데이터 중 중복을 허용하지 않는 데이터는 뭐가 있을까? 회원 아이디나 주민등록번호, 사번, 전화 번호 등은 중복되면 안될 것이다.Set 인터페이스를 구현한 대표 클래스에는 HashSet와 TreeSet가 있다. 우선 HashSet부터 살펴보자.

 

HashSet 클래스

HashSet 클래스는 집합 자료 구조를 구현하며 중복을 허용하지 않는다. 중복을 허용하지 않는다는 의미를 알기 위해 아래와 같이 간단한 HashSet를 테스트하는 프로그램을 작성해보자. HashSet 클래스를 생성하고 문자열 자료를 추가한다.

출력 결과

11~12행 hashSet에 동일한 자료 "이순신"을 추가했다. 같은 문자열을 추가한 것이다. 결과 화면을 보면 같은 자료는 중복되어 출력되지 않았다. 그리고 추가한 순서와 상관 없이 출력되었다. 이 두가지로 HashSet의 두 가지 특성을 알 수 있다.

 

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

이제 HashSet를 활용해 회원을 관리하는 프로그램을 구현해보자. 구현할 메서드는 MemberArrayList 클래스와 동일하다.

HashSet 클래스를 생성하고 addMember( ), removeMember( ), showAllMember( ) 메서드를 구현한다.

회원을 삭제할 때 사용하는 remove( ) 메서드가 ArrayList와는 조금 다르다. ArrayList에서는 get(i) 메서드를 사용해 i 번째에 해당하는 항목을 삭제했지만, HashSet에서는 해당하는 아이디를 가진 회원을 찾기 위해 Iterator를 사용하며, 만약 아이디가 같으면 HashSet의 remove( ) 메서드를 사용해 해당하는 회원을 삭제한다. 

이제 테스트 프로그램을 실행하여 MemberHashSet 클래스가 잘 구현되었는지 확인해보자. 먼저 MemberHashSet를 생성하여 회원 집합을 추가한다. 그리고 기존에 추가된 회원과 아이디가 같은 회원을 추가해보자.

출력 결과

출력 결과를 보면 같은 아이디 1003을 가진 케인, 콘테 회원이 그대로 출력되었다. 회원 아이디가 같다는 것은 같은 회원이라는 뜻인데 원래 HashSet의 정의대로라면 콘테 회원이 추가되지 않아야 한다. 위에서 본 같은 문자열(이순신)은 두 번 추가되지 않았다. String("이순신")이 두 번 추가 되지 않은 이유는 String 클래스에 객체가 동일한 경우에 대한 처리 방법이 이미 구현되어 있기 때문이다. 그럼 Member 클래스에도 같은 객체를 처리하는 방법을 구현해보자.

 

객체가 동일함을 구현하기

물리적으로 인스턴스 주소가 같으면 같은 객체이다. 하지만 여기서는 회원 아이디가 같아도 같은 회원이다. Object 클래스에서 논리적으로 같은 객체를 구현하기 위해 equals( ) 메서드와 hashCode( ) 메서드를 재정의했다. 그러므로 Member 클래스에도 equals( ), hashCode( ) 메서드를 재정의하여 회원 아이디가 같으면 같은 회원임을 구현해 주어야 한다.

Member 클래스에 재정의한 메서드

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

출력 결과

 

TreeSet 클래스

자바의 Collection 인터페이스나 Map 인터페이스를 구현한 클래스 중 Tree로 시작하는 클래스는 데이터를 추가한 후 결과를 출력하면 결과 값이 정렬된다. TreeSet는 자료의 중복을 허용하지 않으면서 출력 결과 값을 정렬하는 클래스다. TreeSet를 활용한 간단한 코드를 살펴보자.

출력 결과

TreeSet에 척준경, 권율, 강감찬 순으로 요소를 추가했다. 그런데 결과 값은 정렬되어 출력 되었다. 그럼 정렬은 어떤 기준으로 이루어질까? 자바는 정렬을 구현하기 위해 '이진 트리(binary tree)'를 사용한다.

 

이진 검색 트리

트리는 자료 사이의 계층 구조를 나타내는 자료 구조다. TreeSet를 이해하기 위해 필요한 '이진 검색 트리(Binary Search Tree; BST)'에 대해 간단히 설명하자면, 트리 자료 구조에서 각 자료가 들어가는 공간을 노드라고 한다. 그리고 위 아래로 연결된 노드의 관계를 '부모-자식 노드(parent-child node)'라고 한다. 이진 검색 트리는 노드에 저장되는 자료의 중복을 허용하지 않고, 부모가 가지는 자식 노드의 수는 2개 이하다. 또한 왼쪽에 위치하는 자식 노드는 부모 노드보다 항상 작은 값을 가지며, 오른쪽에 위치하는 노드는 부모 노드보다 항상 큰 값을 가진다. 따라서 어떤 특정 값을 찾으려 할 때 한 노드와 값의 크기를 비교해 왼쪽 혹은 오른쪽으로 이동한다. 하여 비교 범위가 평균 1/2만큼씩 줄어들어 효과적으로 자료를 검색할 수 있다. 자세한 내용은 이 블로그의 다른 카테고리 글  [알고리즘] - 이진 탐색 트리에서 볼 수 있다.

 

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

TreeSetTest.java 예에서 별도의 코드를 구현하지 않아도 요소들이 정렬되었던 이유는 String 클래스 안에 정렬 방식이 이미 구현되어 있기 때문이다. 이제 패키지를 새로 만들고 TreeSet를 활용하여 회원 관리 프로그램을 구현해보자. 동일한 Set 인터페이스를 구현한 클래스이므로 HashSet 대신에 TreeSet만 선언하여 생성하면 나머지 코드는 같다. 코드는 아래와 같으며, 회원 정렬 기준은 회원 아이디순으로 한다.

그럼 TreeSet를 테스트하여 회원 아이디 순서대로 정렬이 되는지 확인해보자.

출력 결과

아이디 중복 없이 제거되고 회원 아이디로 정렬되어 출력될 줄 알았지만 오류가 발생했다. 위 출력 화면에서 오류 내용을 살펴보면 Member 클래스가 Comparable 인터페이스를 구현하지 않았다고 한다. Comparable 인터페이스를 구현하지 않았다는 의미는 우리가 만든 Member 클래스를 TreeSet의 요소로 추가할 때 어떤 기준으로 노드를 비교하여 트리를 형성해야 하는지를 구현하지 않았다는 뜻이다. 따라서 회원을 TreeSet에 추가할 때 어떤 기준으로 비교할 것인지를 구현해 주어야 한다. 이때 사용하는 인터페이스가 Comparable 또는 Comparator이다.

 

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

우리는 Member 클래스가 가진 회원 아이디를 기준으로 하여 오름차순으로 정렬할 것이다. Comparable과 Comparator는 이러한 정렬을 구현할 수 있게 해주는 인터페이스이다. 그렇다면 정렬 방식을 어디에 구현해야 할까? 정렬 기준 값이 있는 Member 클래스에 구현하면 된다. 먼저 Comparable 인터페이스를 활용해 구현해보자.

 

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

Comparable 인터페이스에는 compareTo( ) 추상 메서드가 포함되어 있다. 따라서 인터페이스를 구현하는 Member 클래스에서 compareTo( ) 메서드를 구현해야 한다. compareTo( ) 메서드를 구현한 Member 클래스 코드는 아래와 같다.

이 예에서 재정의한 compareTo( ) 메서드의 의미는 다음과 같다. 비교 대상은 this의 회원 아이디, 즉 새로 추가한 회원의 아이디와 compareTo( ) 메서드의 매개변수로 전달된 회원 아이디이다. 두 값을 비교하여 새로 추가한 회원 아이디가 더 크면 양수, 그렇지 않으면 음수, 같으면 0을 반환하도록 만들었다. 이렇게 구현하면 출력 결과 값은 오름차순으로 정렬된다. compareTo( )의 반환 값은 정수 값인데, 비교하는 두 값 중 this 값이 더 크면 양수를 반환하여 오름차순으로 정렬된다. 반대로 this 값이 더 작으면 음수를 반환하여 내림차순으로 정렬된다. compareTo( )는 프로그래머가 호출하는 메서드가 아닌 객체가 TreeSet에 요소를 추가할 때 호출되는 메서드이다. 그리고 어떤 매개변수가 전달될지는 기존 TreeSet에 어떤 요소가 들어 있는지에 따라 달라진다. 이제 MemberTreeSetTest 클래스를 실행하면 아래와 같은 정렬 결과를 볼 수 있다.

아이디가 오름차순으로 정렬되어 있음을 알 수 있다. 내림 차순으로 정렬하려면 Member 클래스의 compareTo( ) 메서드를 아래와 같이 수정하면 된다.

 

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

Comparator 역시 정렬을 구현하는 데 사용하는 인터페이스이다. Comparator 인터페이스는 compare( ) 메서드를 구현해야 한다. Member2 클래스를 새로 만들어 Comparator를 구현한 코드는 아래와 같다.

Member2 클래스에 구현한 compare( ) 메서드

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를 사용한다. 아래 예제를 보자.

출력 결과

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

 

참고 서적: 자바 프로그래밍 입문 - 박은종

'프로그래밍 언어 > JAVA' 카테고리의 다른 글

JAVA 입문 - 내부 클래스  (0) 2022.06.08
JAVA 입문 - Map 인터페이스  (0) 2022.06.07
JAVA 입문 - List 인터페이스  (0) 2022.06.05
JAVA 입문 - 컬렉션 프레임워크  (0) 2022.06.04
JAVA 입문 - 제네릭  (0) 2022.06.03