자바 개발자 일기 14일차 (Set)

Shy·2024년 1월 17일

아이티윌

목록 보기
14/22

Set

13일차에 HashSet과 TreeSet이 있는 것을 보았다.

HashSet

  • 내부 구현: HashSet은 내부적으로 HashMap을 사용하여 요소들을 저장한다.
  • 순서: 요소들이 저장되는 순서는 불확실하며, 추가된 순서를 유지하지 않는다.
  • 성능: HashSet은 요소를 추가, 제거, 검색하는 데 걸리는 시간이 대체로 일정하다(상수 시간, O(1)). 하지만, 이는 해시 함수의 효율성에 크게 의존한다.
  • 용도: 요소의 순서가 중요하지 않고 빠른 성능이 필요할 때 적합하다.
  • 중복과 null 값: 중복을 허용하지 않으며, 최대 하나의 null 값을 가질 수 있다.

HashSet은 내부적으로 HashMap을 사용하여 요소들을 저장한다. 여기서 HashMap은 키-값 쌍으로 데이터를 저장하는 구조인데, HashSet에서는 각 요소 자체가 키로 사용되며, 모든 키에는 동일한 가상의 값을 할당한다. 이 과정에서 중요한 부분은 각 요소가 해시 함수를 통해 해시 코드로 변환되는 방식이다.

해시 함수와 버킷

  1. 해시 함수
    • HashSet에 요소를 추가하려고 할 때, 그 요소는 먼저 해시 함수를 통과하여 해시 코드로 변환된다. 이 해시 코드는 해당 요소가 HashMap 내에서 저장될 위치(즉, 버킷)를 결정한다.
  2. 버킷
    • HashMap은 내부적으로 이러한 버킷들의 배열로 구성되어 있으며, 각 버킷은 해시 코드에 따라 할당된다. 서로 다른 요소가 같은 해시 코드를 가질 경우, 이들은 같은 버킷에 저장됩니다.

중복과 순서

  • 중복 방지
    • HashSet에 요소를 추가할 때, 해당 요소의 해시 코드에 해당하는 버킷을 먼저 확인한다. 이미 같은 요소가 존재한다면, 새 요소는 추가되지 않습니다. 이렇게 해시 코드를 사용함으로써 HashSet은 중복을 효과적으로 방지합니다.
  • 순서 없음
    • 요소들은 해시 함수에 의해 결정된 해시 코드에 따라 저장되므로, HashSet에 저장된 요소들 사이에는 일정한 순서가 없다. 이는 요소들이 추가된 순서와는 무관하게 저장됨을 의미한다.

예를 들어, "Java", "Python", "C++" 세 문자열을 HashSet에 추가한다고 해 보자. 각 문자열은 해시 함수를 통해 고유한 해시 코드로 변환되고, 이 코드는 각 문자열이 저장될 HashMap 내의 버킷을 결정한다.

만약 "Java"와 "Python"이 서로 다른 해시 코드를 가지면, 이들은 서로 다른 버킷에 저장된다.

하지만 "Java"를 또 추가하려고 하면, 이미 "Java"에 해당하는 해시 코드와 매치되는 버킷에 "Java"가 존재하기 때문에, 중복 추가는 이루어지지 않는다.

이러한 방식으로 HashSet은 고유한 요소들만을 저장하며, 데이터의 추가, 삭제, 검색을 효율적으로 수행할 수 있다.

해쉬셋에 관한 말....다이제스트

Set은 '중복 원소'를 허용하지 않는다고 했다.
이 말은 우리가 원소를 추가해야 할 때 마다 해당 원소가 중복되는 원소인지 아닌지를 검사해야한다는 것이다. 그럴 때마다 모든 원소를 검사해가면서 한다면 매우 비효율적이다. 그렇기에 고안한 방법이 hash함수를 통해 특정 값에 대한 고유의 다이제스트를 얻고 그 값에 대응하는 index를 찾아서 해당 인덱스에 있는 요소만 검사하면 되는 것이다. 이 것이 HashSet의 기본 개념이다.

예시코드

import java.util.HashSet;
import java.util.Set;

public class HashSetExample {
    public static void main(String[] args) {
        // HashSet 생성
        Set<String> myHashSet = new HashSet<>();

        // 요소 추가
        myHashSet.add("Java");
        myHashSet.add("Python");
        myHashSet.add("C++");

        // 중복 요소 추가 시도
        boolean isAdded = myHashSet.add("Java"); // 이미 "Java"는 추가되어 있으므로, 이 명령은 false를 반환할 것이다.
        System.out.println("Java 추가 성공? " + isAdded); // false 출력

        // HashSet의 내용 출력
        System.out.println("HashSet의 내용: " + myHashSet);

        // 특정 요소가 HashSet에 포함되어 있는지 확인
        if (myHashSet.contains("Python")) {
            System.out.println("HashSet에는 Python이 포함되어 있습니다.");
        }

        // 요소 삭제
        myHashSet.remove("C++");
        System.out.println("C++ 삭제 후 HashSet의 내용: " + myHashSet);
    }
}

예제코드1

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

public class HashSet_Exam {

    public static void main(String[] args) {
        // HashSet: Set 인터페이스를 구현한 클래스로, 중복 데이터를 허용하지 않고 순서가 없음
        // ---------------------------------------------------------------
        // Set에서 동일 데이터 여부 확인 방법: hashCode() 및 equals() 값 확인
        // 1. 해시코드 값 확인: hashCode()
        // 2. equals() 메소드 결과가 모두 일치하면 동일 데이터로 처리
        // ---------------------------------------------------------------
        // 문자열 "홍길동"의 해시코드 값 확인
        System.out.println("\"홍길동\".hashcode() : " + "홍길동".hashCode());
        // 문자열 "홍길동"을 나타내는 두 개의 String 객체 생성
        String str1 = "홍길동";
        String str2 = new String("홍길동");
        // 두 객체의 해시코드 비교 및 equals() 메소드 결과 확인
        System.out.println("str1.hashCode() : " + str1.hashCode());
        System.out.println("str2.hashCode() : " + str2.hashCode());
        // 두 객체가 동일한 데이터로 처리되는지 확인 (해시코드 및 equals 결과가 같은지)
        System.out.println((str1.hashCode() == str2.hashCode() && str1.equals(str2))); // 동일 데이터일 경우 true 출력
        System.out.println("===================================");

        // HashSet 생성 및 데이터 추가
        HashSet<String> set = new HashSet<String>();
        set.add("홍길동");
        set.add("김유신");
        // 중복 데이터 "홍길동" 추가 시도
        set.add("홍길동"); // 중복, 추가되지 않음
        set.add(new String("홍길동")); // 중복, 추가되지 않음
        // HashSet의 내용과 크기 출력
        System.out.println("set : " + set);
        System.out.println("set.size() : " + set.size());
        // 특정 요소의 포함 여부 확인
        System.out.println("set.contain(\"홍길동\"" + set.contains("홍길동"));
        System.out.println("set.contain(\"김길동\"" + set.contains("김길동"));
        
        // 추가 데이터 입력
        set.add("홍경래");
        set.add("을지문덕");
        // HashSet의 내용 출력
        System.out.println("set : " + set);

        // HashSet 전체 데이터 조회 (확장된 for 루프 사용)
        System.out.println("--- Set 전체 데이터 조회 ---");
        for (String name : set) {
            System.out.println(name);
            // 주의: 순회 중에는 요소의 추가/제거를 피해야 함 (ConcurrentModificationException 발생 가능)
        }

        // 전체 데이터 조회 (Iterator 사용)
        System.out.println("--- 전체 데이터 조회 iterator() 사용 ---");
        Iterator<String> ite = set.iterator();
        while (ite.hasNext()) {
            String name = ite.next();
            System.out.println(name);
            // Iterator를 사용하여 안전하게 요소 제거 가능
            // ite.remove();
        }
        System.out.println(set);

        System.out.println("==== Set 데이터 변경 처리 ====");
        // (실습) Set 데이터 변경 작업
        // 1. 수정(변경): 김유신 -> 김유신2
        // 2. 수정(변경): 일지매 -> 일지매2
        // 3. 홍길동 삭제 처리 (데이터 삭제 여부 확인)
        
        // 변경 전 HashSet 내용 출력
        System.out.println(set);
        
        // 데이터 변경 및 삭제 작업 실행 및 결과 출력
        System.out.println("setUpdate(set, \"김유신\", \"김유신2\") : " + setUpdate(set, "김유신", "김유신2"));
        System.out.println("setUpdate(set, \"일지매\", \"일지매2\") : " + setUpdate(set, "일지매", "일지매2"));
        System.out.println("setRemove(set, \"홍길동\") : " + setRemove(set, "홍길동"));
        System.out.println("setRemove(set, \"일지매\") : " + setRemove(set, "일지매"));
        
        // 변경 후 HashSet 내용 출력
        System.out.println(set);
    }
    
    // HashSet 내 데이터를 변경하는 메소드
    public static boolean setUpdate(HashSet<String> s, String name, String fixName) {
        if (s.contains(name)) {
            s.remove(name); // 기존 요소 제거
            s.add(fixName); // 새 요소 추가
            return true;
        }
        return false;
    }
    
    // HashSet 내 데이터를 제거하는 메소드
    public static boolean setRemove(HashSet<String> s, String name) {
        if (s.contains(name)) {
            s.remove(name); // 요소 제거
            return true;
        }
        return false;
    }
}
"홍길동".hashcode() : 54150062
str1.hashCode() : 54150062
str2.hashCode() : 54150062
true
===================================
set : [홍길동, 김유신]
set.size() : 2
set.contain("홍길동") : true
set.contain("김길동") : false
set : [홍경래, 홍길동, 김유신, 을지문덕]
--- Set 전체 데이터 조회 ---
홍경래
홍길동
김유신
을지문덕
--- 전체 데이터 조회 iterator() 사용 ---
홍경래
홍길동
김유신
을지문덕
[홍경래, 홍길동, 김유신, 을지문덕]
==== Set 데이터 변경 처리 ====
[홍경래, 홍길동, 김유신, 을지문덕]
setUpdate(set, "김유신", "김유신2") : true
setUpdate(set, "일지매", "일지매2") : false
setRemove(set, "홍길동") : true
setRemove(set, "일지매") : false
[홍경래, 을지문덕, 김유신2]

예제코드2(주민등록번호 - equals, hashCode 다루기)

import java.util.HashSet;

class Person {
    String jumin; // 주민번호
    String name; // 이름
    String address; // 주소

    public Person(String jumin, String name, String address) {
        super();
        this.jumin = jumin;
        this.name = name;
        this.address = address;
    }

    @Override
    public String toString() {
        // 객체를 문자열로 변환하는 메소드
        return "Person [jumin=" + jumin + ", name=" + name + ", address=" + address + "]";
    }

    @Override
    public int hashCode() {
        // 객체의 해시코드를 생성하는 메소드, 주민번호를 기준으로 해시코드 생성
        System.out.println(">> hashCode() 실행");
        final int prime = 31;
        int result = 1;
        result = prime * result + ((jumin == null) ? 0 : jumin.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        // 두 객체가 동일한지 비교하는 메소드, 주민번호가 같으면 동일한 객체로 간주
        System.out.println(">> equals() 실행");
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Person other = (Person) obj;
        if (jumin == null) {
            if (other.jumin != null)
                return false;
        } else if (!jumin.equals(other.jumin))
            return false;
        return true;
    }
    
}

public class HashSet_Exam2_equals {

    public static void main(String[] args) {
        // HashSet 사용 예제
        // Person 객체를 HashSet에 저장하고 중복 제거와 순서 없음을 확인

        Person p1 = new Person("951010-1234567", "홍길동", "서울");
        Person p2 = new Person("951010-1234567", "홍길동", "부산");
        System.out.println("p1 : " + p1);
        System.out.println("p2 : " + p2);
        
        // 해시코드와 equals 메소드로 두 객체가 동일한지 확인
        System.out.println("p1.hashCode() : " + p1.hashCode());
        System.out.println("p2.hashCode() : " + p2.hashCode());
        System.out.println("p1.equals(p2) : " + p1.equals(p2));
        System.out.println("=================");
        
        HashSet<Person> set = new HashSet<Person>();
        System.out.println("-- p1 입력");
        set.add(p1); // HashSet에 p1 추가
        System.out.println("-- p2 입력(주민등록번호 값 일치)");
        set.add(p2); // HashSet에 p2 추가, 하지만 p1과 동일하므로 실제로는 추가되지 않음
        System.out.println("set : " + set);
        
        // 추가적인 Person 객체들을 HashSet에 추가
        set.add(new Person("981010-1234567", "홍경래", "서울"));
        set.add(new Person("991111-1234567", "홍길동", "서울"));
        set.add(new Person("951010-1234567", "홍기동", "부산")); // 동일 데이터이므로 추가되지 않음

        // HashSet에 저장된 모든 Person 객체를 순회하며 정보 출력
        for (Person vo : set) {
            System.out.println(vo.jumin + ", " + vo.name);
        }
        System.out.println("--- contains() 사용 -------------");
        
        // 주민등록번호를 기준으로 특정 Person 객체의 존재 유무 확인
        Person p3 = new Person("951010-1234567", "", "");
        if (set.contains(p3)) {
            System.out.println("주민등록번호(" + p3.jumin + ") 데이터 있음");
        } else {
            System.out.println("주민등록번호(" + p3.jumin + ") 데이터 없음");
        }
    }
}
p1 : Person [jumin=951010-1234567, name=홍길동, address=서울]
p2 : Person [jumin=951010-1234567, name=홍길동, address=부산]
>> hashCode() 실행
p1.hashCode() : 1495763232
>> hashCode() 실행
p2.hashCode() : 1495763232
>> equals() 실행
p1.equals(p2) : true
=================
-- p1 입력
>> hashCode() 실행
-- p2 입력(주민등록번호 값 일치)
>> hashCode() 실행
>> equals() 실행
set : [Person [jumin=951010-1234567, name=홍길동, address=서울]]
------------
>> hashCode() 실행
>> hashCode() 실행
>> hashCode() 실행
>> equals() 실행
981010-1234567, 홍경래
951010-1234567, 홍길동
991111-1234567, 홍길동
--- contains() 사용 -------------
>> hashCode() 실행
>> equals() 실행
주민등록번호(951010-1234567) 데이터 있음

실습코드1

import java.util.HashSet;

public class HashSet_Lotto {

	public static void main(String[] args) {
		/*
		 * Set을 이용한 로또 만들기 : 1~45 랜덤숫자 6개를 Set에 저장
		 * 1. 로또 번호 6개를 추첨해서 Set에 저장하고 화면 출력
		 * 2. 출력은 작은 숫자부터 큰 숫자 순서대로
		 *    예) 금주의 로또번호 : 5, 8, 10, 25, 33, 41
		 */
		
		HashSet<String> set = new HashSet<String>();
		
		// HashSet<Integer> set1 = new HashSet<Integer>(); 선언 가능 했었네...?
		
		generateLotto(set);
		System.out.println(set);
		printLotto(set);
		

	}
	
	static void printLotto(HashSet<String> set) {
		int j = 0;
		for(int i = 1; i <= 45; i++) {
			String b = Integer.toString(i);
			if (set.contains(b)) {
				j++;
				System.out.print(b);
				if (set.size() > j) {
					System.out.print(", ");
				}
			}
		}
		System.out.println();
	}
	
	static void generateLotto(HashSet<String> set) {
		while (set.size() < 6) {
			long a = Math.round((Math.random() * 45) + 1);
			String s = Long.toString(a);
			set.add(s);
		}
	}
	
	

}
[22, 23, 35, 26, 8, 21]
8, 21, 22, 23, 26, 35

리팩토링

import java.util.HashSet;
import java.util.Random;
import java.util.Set;
import java.util.TreeSet;

public class HashSet_Lotto {

    public static void main(String[] args) {
        // 로또 번호를 생성하고 출력하는 메인 메소드
        Set<Integer> lottoSet = generateLotto();
        printLotto(lottoSet);
    }
    
    static Set<Integer> generateLotto() {
        // 로또 번호를 생성하는 메소드
        Set<Integer> set = new HashSet<>();
        Random random = new Random();
        
        while (set.size() < 6) {
            // 1부터 45 사이의 랜덤한 숫자를 생성하여 HashSet에 추가
            // HashSet은 중복을 허용하지 않으므로 중복된 번호는 자동으로 제외됨
            int number = random.nextInt(45) + 1;
            set.add(number);
        }
        
        return set; // 생성된 로또 번호 집합을 반환
    }
    
    static void printLotto(Set<Integer> set) {
        // 로또 번호를 정렬하여 출력하는 메소드
        // TreeSet을 사용하여 자동으로 숫자를 정렬
        TreeSet<Integer> sortedSet = new TreeSet<>(set);
        
        // 정렬된 로또 번호 출력
        System.out.println("금주의 로또번호 : " + sortedSet);
    }
}
금주의 로또번호 : [9, 17, 23, 25, 35, 43]

HashSet과 Set은 다르지만, 관련이 있다. Set은 인터페이스이고, HashSet은 Set 인터페이스를 구현하는 클래스이다.

  1. Set: Set은 집합을 나타내는 자바 컬렉션 인터페이스이다. 이 인터페이스는 중복된 요소를 허용하지 않으며, 요소들의 순서를 보장하지 않는다. Set 인터페이스는 다양한 구현체를 가지고 있는데, HashSet, TreeSet, LinkedHashSet 등이 그 예이다.

  2. HashSet: HashSet은 Set 인터페이스의 구현 클래스 중 하나로, 해시 테이블을 사용하여 요소를 저장한다. HashSet은 요소의 삽입과 검색에 있어서 상당히 빠른 성능을 제공한다. 그러나 HashSet은 요소들이 어떤 순서로 저장될지 보장하지 않는다.

요약하자면, Set은 일종의 규칙 또는 계약(contract)을 제공하는 인터페이스이며, HashSet은 그 규칙을 따르는 구체적인 클래스라고 볼 수 있다. 프로그래밍에서는 보통 Set 인터페이스 타입으로 변수를 선언하고, 그 구현으로 HashSet 또는 다른 Set 구현체를 사용하는 것이 일반적인 관행이다. 이렇게 함으로써, 나중에 다른 Set 구현으로 쉽게 변경할 수 있는 유연성을 얻을 수 있다.

실습코드(StudentVO)

// StudentVO 클래스는 학생의 성적 정보를 나타내는 클래스이다.
public class StudentVO {
	private String name; // 학생 이름
	private int kor; // 국어 점수
	private int eng; // 영어 점수
	private int math; // 수학 점수
	private int tot; // 총점
	private double avg; // 평균
	
	// 생성자: 기본 생성자와 성적 정보를 받는 생성자 두 가지
	public StudentVO() {}
	public StudentVO(String name, int kor, int eng, int math) {
		this.name = name;
		this.kor = kor;
		this.eng = eng;
		this.math = math;
		computeTotAvg(); // 총점과 평균 계산
	}
	public StudentVO(String name, int kor, int eng, int math, int tot, double avg) {
		this.name = name;
		this.kor = kor;
		this.eng = eng;
		this.math = math;
        // 총점과 평균은 입력받은 값 대신 계산하여 설정
		computeTotAvg();
	}
	
	//메소드(setter, getter) -----------------
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getKor() {
		return kor;
	}
	public void setKor(int kor) {
		this.kor = kor;
		computeTotAvg();
	}
	public int getEng() {
		return eng;
	}
	public void setEng(int eng) {
		this.eng = eng;
		computeTotAvg();
	}
	public int getMath() {
		return math;
	}
	public void setMath(int math) {
		this.math = math;
		computeTotAvg();
	}
	public int getTot() {
		return tot;
	}
	public void setTot(int tot) {
		this.tot = tot;
	}
	public double getAvg() {
		return avg;
	}
	public void setAvg(double avg) {
		this.avg = avg;
	}
	// 총점과 평균을 계산하는 메소드
	public void computeTotAvg() {
		tot = kor + eng + math;
		avg = tot * 100 / 3 / 100.0;
	}
	
	//toString : 재정의 ----------------
    // toString 메소드: 학생 정보를 문자열로 반환
	@Override
	public String toString() {
		return "StudentVO [name=" + name + ", kor=" + kor + ", eng=" + eng + ", math=" + math + ", tot=" + tot
				+ ", avg=" + avg + "]";
	}
	
    // equals 메소드: 이름을 기준으로 학생 객체의 동등성을 판단
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		StudentVO other = (StudentVO) obj;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		return true;
	}
	
    // hashCode 메소드: 이름의 해시코드를 반환
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		return result;
	}
	
    // printScore 메소드: 학생의 성적 정보를 출력
	public void printScore() {
		System.out.println("이름 : " + name + "\t 국어 : " + kor + "\t 영어 : " + eng + "\t 수학 : " + math + "\t 총점 : " + tot + "\t 평균 : " + avg);
	}	
}

// 메인메소드

public class StudentSetManagerMain {

	public static void main(String[] args) {
		/* (실습) Set을 사용한 성적 처리
		사용클래스명 : StudentVO, StudentManagerMain - main() 메소드
		1. StudentVO 클래스를 사용해서
		   3명의 학생데이터(성명,국어,영어,수학)를 만들고(저장하고)
		   "김유신", 100, 90, 81
		   "이순신", 95, 88, 92
		   "홍길동", 90, 87, 77
		2. Set 타입의 변수(set)에 저장하고
		3. set에 있는 전체 데이터 화면출력
		   성명   국어  영어  수학  총점  평균
		   -------------------------------------
		   김유신 100   90    81    271   90.33
		   ...
		4-1 김유신을 찾아서 국어 점수를 95 점으로 수정
		4-2 김유신 데이터만 출력
		   이름 : 김유신
		   국어 : 95
		   영어 : 90
		   ...
		   평균 : ??
		5. 전체 데이터 화면 출력
		========================================== */

		// HashSet을 이용한 학생 성적 관리
		HashSet<StudentVO> set = new HashSet<StudentVO>();
        // 학생 데이터 추가
		set.add(new StudentVO("김유신", 100, 90, 81));
		set.add(new StudentVO("이순신", 95, 88, 92));
		set.add(new StudentVO("홍길동", 90, 87, 77));
        
		// 성적 출력
		printScore(set);
		
        // 특정 학생 성적 수정
		fixScore(set, "김유신", 95, 90, 81);
        
        // 수정된 성적 출력
		printScore(set);
		
		
		
	}
	
	public static void ps(StudentVO student) {
		System.out.println("이름 : " + student.getName() + "\t국어 : " + student.getKor() + " \t영어 : " + student.getEng() + " \t수학 : " + student.getMath() + " \t총점 : " + student.getTot() + " \t평균 : " + student.getAvg());
	}
	
	public static void printScore(HashSet<StudentVO> set) {
		System.out.println("============================================================================================");
		System.out.println("성명 \t\t국어 \t\t영어 \t\t수학 \t\t총점 \t\t평균");
		System.out.println("============================================================================================");
		for (StudentVO student : set) {
			ps(student);
		}
	}
	
	public static void fixScore(HashSet<StudentVO> set, String name, int kor, int eng, int math) {
		StudentVO a = new StudentVO(name, kor, eng, math);
		if (set.contains(a)) {
			set.remove(a);
			set.add(a);
		}
		if (set.contains(a)) {
			for(StudentVO student : set) {
				if (student.equals(a)) {
					System.out.println("-------- 점수 수정 --------");
					ps(student);
				}
			}
		}
	}
}
============================================================================================
성명 		국어 		영어 		수학 		총점 		평균
============================================================================================
이름 : 이순신	국어 : 95 	영어 : 88 	수학 : 92 	총점 : 275 	평균 : 91.66
이름 : 홍길동	국어 : 90 	영어 : 87 	수학 : 77 	총점 : 254 	평균 : 84.66
이름 : 김유신	국어 : 100 	영어 : 90 	수학 : 81 	총점 : 271 	평균 : 90.33
-------- 점수 수정 --------
이름 : 김유신	국어 : 95 	영어 : 90 	수학 : 81 	총점 : 266 	평균 : 88.66
============================================================================================
성명 		국어 		영어 		수학 		총점 		평균
============================================================================================
이름 : 이순신	국어 : 95 	영어 : 88 	수학 : 92 	총점 : 275 	평균 : 91.66
이름 : 홍길동	국어 : 90 	영어 : 87 	수학 : 77 	총점 : 254 	평균 : 84.66
이름 : 김유신	국어 : 95 	영어 : 90 	수학 : 81 	총점 : 266 	평균 : 88.66
  • 성적 데이터의 저장, 조회, 수정을 HashSet을 사용하여 처리한다.
  • StudentVO의 equals와 hashCode 메소드는 HashSet에서 학생 객체의 동일성을 판단하는 데 중요한 역할을 한다.
  • 학생의 성적 정보를 수정할 때, HashSet의 특성상 기존 객체를 먼저 제거하고 수정된 새 객체를 추가하는 방식으로 처리한다.

TreeSet

TreeSet은 Java의 컬렉션 프레임워크의 일부로, 정렬된 순서로 객체를 저장하는 데 사용되는 자료구조다. Set 인터페이스를 구현하며, 몇 가지 중요한 특징이 있다.

특징

  1. 정렬 순서
    • TreeSet은 추가되는 요소들을 자동으로 정렬한다. 이 정렬은 자연 순서(객체가 Comparable 인터페이스를 구현한 경우) 또는 생성자에 제공된 Comparator에 따라 결정된다.
  2. 중복 허용 안 함
    • TreeSet은 중복된 요소를 허용하지 않는다. 하나의 null 값만 포함할 수 있으며, 이것도 Comparator나 요소의 자연 순서가 null을 허용하는 경우에만 가능하다.
  3. 성능
    • TreeSet의 주요 작업(추가, 삭제, 검색)의 시간 복잡도는 O(logn)O(log n)이다. 이는 내부적으로 균형 이진 트리(보통 레드-블랙 트리) 구조를 사용하기 때문이다.

주요메소드

add(E element): 요소를 추가한다.
remove(Object o): 지정된 요소를 제거한다.
contains(Object o): 특정 요소가 존재하는지 확인한다.
first(): 가장 낮은(첫 번째) 요소를 반환한다.
last(): 가장 높은(마지막) 요소를 반환한다.
higher(E e): 지정된 요소보다 큰 가장 가까운 요소를 반환한다.
lower(E e): 지정된 요소보다 작은 가장 가까운 요소를 반환한다.

예제코드

import java.util.TreeSet;

public class TreeSetExample {

    public static void main(String[] args) {
        // TreeSet 인스턴스 생성
        TreeSet<Integer> numbers = new TreeSet<>();

        // 요소 추가
        numbers.add(40);
        numbers.add(10);
        numbers.add(30);
        numbers.add(20);

        // TreeSet에 저장된 요소들 출력 (자동 정렬됨)
        System.out.println("TreeSet: " + numbers);

        // 가장 낮은 요소(first)와 가장 높은 요소(last) 출력
        System.out.println("가장 낮은 요소: " + numbers.first());
        System.out.println("가장 높은 요소: " + numbers.last());

        // 특정 요소보다 바로 높은 요소 찾기 (higher)
        System.out.println("20보다 바로 높은 요소: " + numbers.higher(20));

        // 특정 요소보다 바로 낮은 요소 찾기 (lower)
        System.out.println("20보다 바로 낮은 요소: " + numbers.lower(20));

        // 요소 제거
        boolean isRemoved = numbers.remove(30);
        System.out.println("30 제거 성공?: " + isRemoved);

        // 변경된 TreeSet 출력
        System.out.println("변경된 TreeSet: " + numbers);
    }
}
  1. TreeSet<Integer> 인스턴스를 생성한다.
  2. add() 메소드를 사용해 여러 정수들을 TreeSet에 추가한다. 이 때, TreeSet은 자동으로 요소들을 정렬한다.
  3. first()last() 메소드를 사용해 가장 낮은 요소와 가장 높은 요소를 출력한다.
  4. higher()lower() 메소드를 사용해 주어진 값보다 바로 높거나 낮은 요소를 찾는다.
  5. remove() 메소드로 특정 요소를 제거한다.
  6. TreeSet에 저장된 요소들을 출력하여 변경 사항을 확인한다.

HashSet -> TreeSet 변경

  1. 기본 방법
    • TreeSet 생성자에 HashSet을 인자로 전달하여 새 TreeSet을 생성한다.
HashSet<String> hashSet = new HashSet<>();
// HashSet에 데이터 추가
TreeSet<String> treeSet = new TreeSet<>(hashSet);
  1. 커스텀 정렬 기준 사용
    • Comparator를 사용하여 정렬 순서를 정의할 수 있다.
    • 이때 CustomObject는 정렬할 객체의 타입이고, CustomComparatorComparator<CustomObject> 인터페이스를 구현한 클래스이다.
HashSet<CustomObject> hashSet = new HashSet<>();
// HashSet에 데이터 추가
TreeSet<CustomObject> treeSet = new TreeSet<>(new CustomComparator());
treeSet.addAll(hashSet);

주의사항

  • TreeSet은 모든 요소가 상호 비교 가능해야 한다. 즉, 저장되는 객체는 Comparable 인터페이스를 구현해야 하거나, TreeSet의 생성자에 Comparator가 제공되어야 한다.
  • TreeSetHashSet과 달리 요소의 추가, 삭제, 검색에 O(logn)O(log n)의 시간 복잡도를 가집니다. 따라서 대규모 데이터를 다룰 때는 이 점을 고려해야 한다.
  • TreeSet은 중복을 허용하지 않으며, equals() 메소드 대신 compareTo() 메소드(또는 Comparator.compare())의 결과를 사용하여 동일성을 판단한다.

예시코드

import java.util.HashSet;
import java.util.TreeSet;

public class Main {
    public static void main(String[] args) {
        // HashSet 생성 및 데이터 추가
        HashSet<String> hashSet = new HashSet<>();
        hashSet.add("Banana");
        hashSet.add("Apple");
        hashSet.add("Pineapple");
        hashSet.add("Orange");

        // HashSet의 데이터를 TreeSet으로 변환
        TreeSet<String> treeSet = new TreeSet<>(hashSet);

        // TreeSet의 데이터 출력 (정렬된 순서)
        for (String fruit : treeSet) {
            System.out.println(fruit);
        }
    }
}

이 코드에서는 먼저 HashSet에 여러 과일 이름을 추가한다. 그런 다음 HashSet의 내용을 TreeSet의 생성자에 전달하여 TreeSet 객체를 생성한다. TreeSet은 기본적으로 문자열의 자연 순서(알파벳 순서)에 따라 요소를 정렬한다. 따라서 treeSet의 내용을 출력하면, hashSet에 추가된 과일 이름들이 알파벳 순서로 정렬되어 출력된다.

HashSet, TreeSet의 선택

  • 속도가 중요한 경우: 요소의 추가, 제거, 조회가 자주 일어나고, 요소의 순서가 중요하지 않다HashSet이 더 적합하다.
  • 정렬이 중요한 경우: 요소들을 정렬된 상태로 유지해야 하거나, 범위 탐색이 필요하다TreeSet을 사용하는 것이 좋다.
profile
신입사원...

0개의 댓글