고래씌

[JAVA] 14-2. 컬렉션(Collection) - Set 본문

JAVA/JAVA 기초

[JAVA] 14-2. 컬렉션(Collection) - Set

고래씌 2023. 10. 25. 15:26

1. Set - HashSet

=> 중복값을 허용하지 않음

=> 저장된 순서를 보장하지 않음

 

▶ 구현 클래스로 HashSet, LinkedHashSet, TreeSet이 있음

 

▶ LinkedHashSet : 중복을 허용하지 않는다. 단, 저장된 순서를 유지

 

▶ TreeSet : 중복을 허용하지 않는다. 단, 오름차순 정렬 지원

 

 

 

1) 크기 구하기 : size()

		Set<String> hs1 = new HashSet<>();   // String 자료형만 들어갈 수 있음
		
		// Set객체 내부로 값을 추가 : add(추가할 값)
		hs1.add(new String("반갑습니다."));
		hs1.add(new String("반갑습니다."));
		hs1.add("여러분");
		hs1.add("안녕하세요.");
		hs1.add("안녕하세요.");
		hs1.add("여러분");
		
		System.out.println(hs1);
		
		// 크기 구하기 : size()
		System.out.println("hs1의 크기 : " + hs1.size());

☞ 중복값 저장되지 않은 것 확인

 

 

2) 값 삭제 : remove(삭제할 값)

            hs1.remove("여러분");

            System.out.println(hs1);

 

 

3) 모든 값 삭제 : clear()

		hs1.clear();
		
		System.out.println(hs1);

 


 

4) Student 클래스에 equals(), hashCode() 재정의

■ equals(), hashCode() 함수를 재정의하지 않은 Student 클래스

package com.kh.chap02_set.part01_hashSet.model.vo;

public class Student {
	
	private String name;
	private int age;
	private int score;
	
	public Student() {
		
	}

	public Student(String name, int age, int score) {
		super();
		this.name = name;
		this.age = age;
		this.score = score;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public int getScore() {
		return score;
	}

	public void setScore(int score) {
		this.score = score;
	}

	@Override
	public String toString() {
		return "Student [name=" + name + ", age=" + age + ", score=" + score + "]";
	}

}
import com.kh.chap02_set.part01_hashSet.model.vo.Student;

public class SetRun {
	
	public static void main(String[] args) {
		
		HashSet<Student> hs2 = new HashSet();   // new 객체로 생성된 Student 객체만 들어갈 수 있음
		
		hs2.add(new Student("공유", 40, 100));
		hs2.add(new Student("김갑생", 33, 30));
		hs2.add(new Student("홍길동", 24, 70));
		hs2.add(new Student("공유", 40, 100)); 

		System.out.println(hs2);

 

☞ 동일한 값이 있지만 객체가 다 서로 다른 주소값으로 들어가서 set은 주소값으로만 봤을때 동일한 객체라고 판단X

저장순서 유지 X, 중복값 저장 O => 동일객체로 판단하지 않았기 때문.
값이 추가될 때 equals(), hashCode()로 이미 들어간 값중에 현재 추가하고자 하는 값이 존재하는지 먼저 확인하기 때문.


단, 위 두 메소드(equals, hashCode)를 override하지 않으면 Object 클래스의 equals와 hashCode함수를 기준으로 동일여부를 확인함.(주소값 간의 비교를 수행)

 

 

※ 정리

▶ HashSet에 객체를 담을 때 내부적으로 equals()로 비교 + hashCode()값이 일치하는지도 비교
equals()한 결과가 true이고, hashCode() 값이 일치하면 => 동일 객체로 판단(중복값 저장하지 않음)

Object클래스의 equals() : 두 객체의 주소값을 가지고 비교해서 일치하면 true / 일치하지 않으면 false
Object클래스의 hashCode() : 두 객체의 주소값을 기반으로 해시코드값을 만들어서 반환

객체의 필드값 기준으로 동일한 객체인지 여부를 판단하게 하려면 위 두 메소드를 오버라이딩 해줘야함.

 

 


 

■ Student 클래스에 equals(), hashCode() 재정의

 

① equals()    (직접 작성)

	@Override
	public boolean equals(Object obj) {
		
		// Student.equals(비교대상 Student 객체)
		
		if(!(obj instanceof Student)) {
			return false;
		}
		
		Student other = (Student) obj;
		
		
		// 3가지 필드값이 모두 동일한 경우 동일한 객체로 판단할 예정
		if(this.name.equals(other.name) && this.age == other.age && this.score == other.score)
			return true;
		
		return false;
	}

☞ Student 클래스 필드에 선언된 name 과 age, score가 모두 일치하면 동일한 객체로 판단

this.name.equals(other.name) && this.age == other.age && this.score == other.score

 

 

② hashCode()   (직접 작성)

	// 객체의 주소값이 아닌, 필드값을 기준으로 해시코드를 만들어서 반환하도록 정의
	public int hashCode() {
		return (name+age+score).hashCode();    // 3가지 필드를 가지고 hashcode를 만듦
	}

 

 

우리는 Generate를 이용해 equals(), hashCode()를 재정의해주겠다!

package com.kh.chap02_set.part01_hashSet.model.vo;

public class Student {
	
	private String name;
	private int age;
	private int score;
	
	public Student() {
		
	}

	public Student(String name, int age, int score) {
		super();
		this.name = name;
		this.age = age;
		this.score = score;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public int getScore() {
		return score;
	}

	public void setScore(int score) {
		this.score = score;
	}

	@Override
	public String toString() {
		return "Student [name=" + name + ", age=" + age + ", score=" + score + "]";
	}
	
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Student other = (Student) obj;
		if (age != other.age)
			return false;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		if (score != other.score)
			return false;
		return true;
	}
	
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + age;
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		result = prime * result + score;
		return result;
	}
}
import com.kh.chap02_set.part01_hashSet.model.vo.Student;

public class SetRun {
	
	public static void main(String[] args) {
		
		HashSet<Student> hs2 = new HashSet(); 
		
		hs2.add(new Student("공유", 40, 100));
		hs2.add(new Student("김갑생", 33, 30));
		hs2.add(new Student("홍길동", 24, 70));
		hs2.add(new Student("공유", 40, 100)); 

		System.out.println(hs2);

 

☞ 다시 실행한 결과, 중복값 "공유" 객체가 하나 삭제된 것을 확인

 

Student 클래스에 equals() 오버라이딩 : 세 필드값이 모두 일치하면 true, 하나라도 다를 시 false 반환

Student 클래스에 hashCode() 오버라이딩 : 세 필드값 기반으로 해시코드 값을 생성

 

 


5) Set의 반복문

※ Set → 데이터가 무작위로 저장되어 있음 => 즉, index 개념 無 => 기본반복문 활용할 수 없다!!!!!

 

■ Set내부에 담겨있는 데이터를 순차적으로 가져오고자 할 때?

 

방법 ① for문(향상된 for문만 사용 가능)

        HashSet<Student> hs2 = new HashSet();   // new 객체로 생성된 Student 객체만 들어갈 수 있음

        hs2.add(new Student("공유", 40, 100));
        hs2.add(new Student("김갑생", 33, 30));
        hs2.add(new Student("홍길동", 24, 70));
        hs2.add(new Student("공유", 40, 100)); 

        // ① for문 (향상된 for문만 사용 가능)
        for(Student s : hs2) {
            System.out.println(s);
        }

 

 

방법 ② Set계열 클래스를 ArrayList클래스로 변환

        HashSet<Student> hs2 = new HashSet();   // new 객체로 생성된 Student 객체만 들어갈 수 있음

        hs2.add(new Student("공유", 40, 100));
        hs2.add(new Student("김갑생", 33, 30));
        hs2.add(new Student("홍길동", 24, 70));
        hs2.add(new Student("공유", 40, 100)); 

        // ② Set계열 클래스를 ArrayList클래스로 변환
  //		ArrayList<Student> list = new ArrayList(); // 비어있는 리스트 생성
  //		list.addAll(hs2); // 리스트에 hs2에 담겨있는 객체들을 모두 추가하는 방식

        ArrayList<Student> list = new ArrayList<>(hs2); // hs2에 담겨있는 모든 객체들이 추가된 채로 리스트가 생성

        for(int i=0; i<list.size(); i++) {
            System.out.println(list.get(i));
        }

 

방법 ③ Iterator

HashSet클래스에서 제공하는 iterator 반복자를 이용한 방법

        HashSet<Student> hs2 = new HashSet();   // new 객체로 생성된 Student 객체만 들어갈 수 있음

        hs2.add(new Student("공유", 40, 100));
        hs2.add(new Student("김갑생", 33, 30));
        hs2.add(new Student("홍길동", 24, 70));
        hs2.add(new Student("공유", 40, 100)); 

        // ③ Iterator
        // HashSet클래스에서 제공하는 iterator 반복자를 이용한 방법
        Iterator<Student> it = hs2.iterator();

        while(it.hasNext()) {
            System.out.println(it.next());
        }

 

 

2. LinkedHashSet  /  TreeSet

▶ LinkedHashSet - 저장순서를 기록

▶ TreeSet - 값의 정렬을 지원

		// LinkedHashSet
		Set<Integer> set1 = new LinkedHashSet<>();  // 순서가 유지되는 Set
		set1.add(34);
		set1.add(25);
		set1.add(100);
		set1.add(1);
		set1.add(134);
		set1.add(44434);
		System.out.println(set1);
		
		
		// TreeSet    //오름차순 정렬
		Set<Integer> set2 = new TreeSet<>(set1);  
		System.out.println(set2);  // comparTo 함수가 호출되어 자동으로 오름차순 정렬이 됨

 

☞ LinkedHashSet은 순서가 유지

☞ TreeSet은 Comprable 인터페이스가 구현되어 있으므로 오름차순으로 정렬되어 출력 되어 오름차순 정렬이 됨.

 

☞ Student 객체를 정렬할때는 Set<Student> set2 = new TreeSet<>();

☞ Student 클래스에 comparTo 메소드를 재정의하여 호출하면 됨