모든지 기록하자!

[Java] Set 인터페이스 본문

Java

[Java] Set 인터페이스

홍크 2021. 5. 26. 22:34
728x90

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

중복을 허용하지 않는 데이터의 예로는 회원 아이디, 주민등록번호, 사번, 쇼핑몰 주문번호 등은 중복 불가함

HashSet 클래스

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

중복을 허용하지 않는 것을 간단한 예제로 확인해 보자

import java.util.HashSet;

public class HashSetTest {

	public static void main(String[] args) {
		
		HashSet<String> set = new HashSet<String>();
		
		// HashSet은 중복을 허용하지않고 get메서드가 없어서(i번째 꺼내오지못함) 
		//순회하는 Iterator 사용, Hash방식은 순서가 상관없음
		boolean b1 = set.add("aaa");
		
		System.out.println(b1); 
		set.add("aaa");
		set.add("bbb");
		set.add("ccc");
		
		System.out.println(set); //HashSet에는 toString 이미 구현됨
		
		boolean b = set.add("aaa");
		System.out.println(b);
		System.out.println(set);
	}
}

출력 결과

HashSet은 자료가 추가된 순서와 상관없이 출력된다.

HashSet 활용

import java.util.HashSet;
import java.util.Iterator;

import collection.Member;

public class MemberHashSet {

private HashSet<Member> hashSet; // HashSet 선언
	
	public MemberHashSet() {
		hashSet = new HashSet<Member>(); // HashSet 생성
	}
	
	public void addMember(Member member) {
		hashSet.add(member); // HashSet에 회원추가
	}
	
	public boolean removeMember(int memberId) {
		
		Iterator<Member> iterator = hashSet.iterator();
		while(iterator.hasNext()) {
			Member member = iterator.next(); // 회원을 하나씩 가져와서
			int tempId = member.getMemberId(); // 아이디 비교
			if(memberId == tempId) { // 같은 아이디인 경우
				hashSet.remove(member); // 회원 삭제
				return true;
			}
		}
		
		System.out.println(memberId + "가 존재하지 않습니다.");
		return false;
	}
	
	public void showAll() { // 모든 회원 출력
		for(Member member : hashSet) {
			System.out.println(member);
		}
		System.out.println();
	}
}

 

테스트 프로그램

import collection.Member;

public class MemberHashSetTest {

	public static void main(String[] args) {

		MemberHashSet memberHashSet = new MemberHashSet();
		
		Member memberLee = new Member(101, "이병헌");
		Member memberChoi = new Member(102, "최민식");
		Member memberHa = new Member(103, "하정우");
		
		memberHashSet.addMember(memberLee);
		memberHashSet.addMember(memberChoi);
		memberHashSet.addMember(memberHa);
		
		memberHashSet.showAll();
		//같은 id값이 들어가면 안됨 (ex 101)
		//Member객체에 Equals랑 HashCode를 구현해야 두 객체가 같은지 체크할수있음
		Member memberLee2 = new Member(101, "조진웅"); // 아이디 중복 회원 추가
		memberHashSet.addMember(memberLee2);
		memberHashSet.showAll();
	}
}

출력 결과

출력 결과를 보면 같은 아이디 101을 가진 조진웅과 이병헌이 그대로 출력됐다.

HashSet의 정의대로면 추가되지 않아야 한다. 앞에서 본 HashSetTest 예제에는 같은 문자열(aaa)은

두 번 추가되지 않았다. String(aaa)이 두 번 추가되지 않은 이유는 String 클래스에 객체가 동일한 경우에 대한 처리 방법이 이미 구현되어 있기 때문이다. 

객체가 동일함을 구현하기

인스턴스 주소가 같으면 같은 객체이다. 여기에서는 회원 아이디가 같아도 같은 회원이다.

Object 클래스에서 논리적으로 같은 객체를 구현하기 위해 equals() 메서드와 hashCode() 메서드를 재정의했다. 그러므로 Member 클래스에도 equals() 메서드와 hashCode() 메서드를 재정의 하여 회원 아이디가

같으면 같은 회원임을 구현해줘야 한다.

@Override
	public int hashCode() { // hashCod()메서드가 회원 아이디를 반환하도록 재정의
		 return memberId; 
		
	}

	@Override
	public boolean equals(Object obj) {
		if(obj instanceof Member) {  // obj가 Member인지 체크
			Member member = (Member)obj; //obj를 Member로 다운캐스팅
			
			if(this.memberId == member.memberId) { // 매개변수로 받은 회원 아이디가
				return true;                      // 자신의 회원 아이디와 같다면 true 반환
				
			}else return false;
			
		}return false;
		
	}

출력 결과

Member 클래스에 equals()와 hashCode() 메서드를 재정의하고 실행해보면 아이디가 같은 회원은

추가되지 않은 것을 알 수 있다.

TreeSet 클래스

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

import java.util.TreeSet;

public class TreeSetTest {

	public static void main(String[] args) {

		TreeSet<String> tree = new  TreeSet<String>();
		//String은 이미 Comparable이 구현됨
		tree.add("aaa");
		tree.add("ccc");
		tree.add("bbb");
		
		System.out.println(tree);
		//컬렉션프레임웍 클래스들은 toString이 default로 구현됨
	}
}

출력 결과

[aaa, bbb, ccc] 

 

결과 값은 정렬되어 출력되었다. 자바는 정렬을 구현하기 위해 '이진트리(binary tree)'를 사용한다.

이진 검색 트리

트리 자료 구조에서 자료가 들어가는 공간을 노드(node)라고 한다. 위아래로 연결된 노드의 관계를

부모-자식 노드라고 한다. 이진 검색 트리는 노드에 저장되는 자료의 중복을 허용하지 않고, 부모가 가지는 자식 노드의 수가 2개 이하이다. 왼쪽에 위치하는 자식 노드는 부모 노드보다 항상 작은 값을 가진다.

반대로 오른쪽에 놓인 자식 노드는 부모 노드보다 항상 큰 값을 가진다. 따라서 어떤 특정 값을 찾으려 할 때 비교 범위가 평균 1/2 만큼씩 줄어들어 효과적으로 자료를 검색할 수 있다.

 [23, 10, 48, 15, 7, 22, 56]을 넣어 간단한 이진 검색 트리를 만들어 보자

TreeSet 활용

import java.util.Iterator;
import java.util.TreeSet;

import collection.Member;

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

테스트 프로그램

import collection.Member;

public class MemberTreeSetTest {

	public static void main(String[] args) {

		MemberTreehSet memberTreeSet = new MemberTreehSet();
		
		Member memberLee = new Member(102, "이병헌");
		Member memberChoi = new Member(101, "최민식");
		Member memberHa = new Member(103, "하정우");
		
		memberTreeSet.addMember(memberLee);
		memberTreeSet.addMember(memberChoi);
		memberTreeSet.addMember(memberHa);
		memberTreeSet.showAll();
		
		Member memberJo = new Member(103, "조진웅"); // 아이디 중복 회원 추가
		memberTreeSet.addMember(memberJo);
		memberTreeSet.showAll();
	}
}

아이디 중복 없이 제거되고 회원 아이디로 정렬되어 출력될 줄 알았는데 오류가 발생한다.

오류 내용은 Member 클래스가 Comparable 인터페이스를 구현하지 않았다는 의미다.

Comparable 인터페이스를 구현하지 않았다는 의미는  Member 클래스를 TreeSet의 요소로

추가할 때 어떤 기준으로 노드를 비교하여 트리를 형성해야 하는지 구현하지 않았다는 뜻이다.

이때 사용하는 인터페이스가 Comparable 또는 Comparator이다.

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

정렬 기준 값이 있는 Member 클래스에 Comparable과 Comparator를 구현하면 된다.

public class Member implements Comparable<Member>{
	...(생략)
}

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

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

Member 클래스에서 compareTo() 메서드를 구현해야 한다. 

@Override
	public int compareTo(Member member) { 
	// String에 compareTo가 이미 구현됨 이름순으로 정렬 할때 사용	
		return (this.memberId - member.memberId); // *(-1) <- 내림차순
	}   //(this가더크면)양수가 나오면 오름차순 음수가 나오면 내림차순

비교 대상은 this의 회원 아이디, 즉 새로 추가한 회원의 아이디와 CompareTo() 메서드의 매개변수로 전달된

회원 아이디이다. 두 값을 비교해서 새로 추가한 회원 아이디가 더 크면 양수, 그렇지 않을 때 음수 같으면 0

을 반환하도록 만들었다. 이렇게 구현하면 출력 결과 값은 오름차순이다. 음수가 나올 땐 내림차순으로 정렬

 

출력 결과

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

Comparator 역시 정렬을 구현하는 데 사용하는 인터페이스다. Comparator 인터페이스는 compare() 메서드를 구현해야 한다. 

public class Member implements Comparator<Member>{
	...(생략)
}
@Override
	public int compare(Member member1, Member member2) { // 전달받은 두 매개변수를 비교한다.
		return (member1.memberId - member2.memberId);
	}

compare() 메서드에는 매개변수가 2개 전달된다. compareTo() 메서드는 this와 전달된 매개변수를 비교했지만 compare() 메서드는 전달되는 두 매개변수를 비교한다. 첫 번째 매개변수가 더 크면 양수를 반환하고 오름차순으로 정렬된다.

 

Comparator를 사용할 때는 TreeSet 생성자에 Comparator를 구현한 객체를 매개변수로 전달해야 한다.

MemberTreehSet memberTreeSet = new MemberTreehSet(new Member());

일반적으로 Comparator 보다는 Comparable를 많이 사용한다.

하지만 어떤 클래스가 이미 Comparable 인터페이스를 구현한 경우에 이 클래스의 정렬 방식을 정의할 때 

Comparator 인터페이스를 사용할 수 있다.

 

 

728x90
Comments