1. HashMap과 HashSet의 차이
HashSet
정의
set인터페이스를 구현한 가장 대표적인 컬렉션(컨테이너).
즉, set의 특징처럼 1) 중복된 객체는 저장X, 2) Unordered Collection (원소들간의 위치적 전후관계 없음)
생성자와 메소드
생성자
HashSet ( ): HashSet객체를 생성
HashSet ( Collection c ): Collection c를 포함하는 HashSet객체를 생성
HashSet( int initialCapacity ): 파라미터 값을 초기용량으로하는 HashSet객체를 생성
메소드
1. 추가하기
- boolean add ( Object o ): 객체 o를 저장
- boolean addAll ( Collection c ): Collection c에 저장된 모든 객체들을 저장 (합집합)
2. 삭제하기
- void clear( ): 저장된 모든 객체를 삭제
- boolean remove (Object o): 객체 o를 삭제 (성공-true, 실패-false)
*HashSet은 원소들간의 위치적 전후관계가 없다 즉, 인덱스가 없으므로 객체를 지정해서 삭제한다.
- boolean removeAll (Collection c): Collection c에 저장된 모든 객체와 동일한 것들을 모두 삭제 (차집합)
- boolean retainAll (Collection c): Collection c에 저장된 모든 객체와 동일한 것들을 제외하고 모두 삭제 (교집합)
3. 접근하기
*HashSet은 원소들간의 위치적 전후관계가 없다 즉, 인덱스가 없으므로 Iterator을 사용하여 저장된 객체들에 대해 접근
Iterator it = [Iterator을 적용할 자료구조].iterator();
it.next();
4. 정렬하기
과정: HashSet → List → 정렬(Collections 클래스의 sort메소드)
HashSet은 Unordered Collection (원소들간의 위치적 전후관계X) 이므로 HashSet만으론 정렬이 불가하다.
리스트는 생성자 중 List (Collection c)가 있기 때문에 HashSet을 List 객체 생성 시 파라미터로하여 리스트화 시킬 수 있다. 또한,
Ordered Collection인 리스트는 정렬이 가능하다.
5. 포함여부확인
- boolean contains (Object o): 객체 o가 HashSet에 있는지 확인
- boolean containsAll (Collection c): 컬렉션 c에 있는 모든 객체가 HashSet에 있는지 확인
객체중복설정
이름이 같은 두 Animal 객체를 HashSet에 넣는다고 해보자.
이름이 같으면 같은 동물로 인식하는 것이 의도였지만 서로 다른 것으로 인식하여 두개의 Animal이 HashSet에 저장된다.
사용자 의도대로 인스턴스를 같은 것으로 인식하게 하려면 어떻게 해야할까? (Ex. 이름이 같으면 같은 동물, 이름-나이가 같으면 같은 사람, etc.)
1. (해당 클래스에서) equals 메소드 오버라이딩
2. (해당 클래스에서) hashCode 메소드 오버라이딩
*주의: equals메소드만 오버라이딩하면 여전히 HashSet에서 중복으로 취급한다. hashCode 메소드도 오버라이딩 해줘야 함!
왜그런지 공식문서를 살펴보자.
Object 클래스의 equals메소드 공식문서:
Note that it is generally necessary to override the "hashCode" method whenever this method is overridden, so as to maintain the general contract for the "hashCode" method, which states that equal objects must have equal hash codes.
"동일한 객체들은 반드시 동일한 해쉬코드를 가져야한다."라는 hashCode 메소드에 대한 일반적인 규칙을 유지하기 위해 equals메소드를 오버라이딩 할 때마다 hascCode메소드도 오버라이딩 해줘야한다.
Object 클래스의 hashCode메소드 공식문서:
If two objects are equal according to the "equals(Object)method", then calling the "hashCode" method on each of the two objects must produce the same integer result.
equals메소드에 따라 두 객체가 동일하다면, 두 객체에 각각에 hashCode메소드를 호출해도 동일한 정수결과가 생성되어야 한다.
요약하면, equals메소드에 따라 동일하다고 판단된 객체들은 반드시 동일한 해쉬코드를 가져야하는 것이 일반적인 규칙이므로, equals메소드와 hashCode메소드를 모두 오버라이딩 해줘야 하는 것이다.
Student인스턴스 각각의 성과 이름이 같으면 동일하게하여 equals메소드를 오버라이딩 하고, hashCode메소드는 오버라이딩하지 않았을 경우를 살펴보자.
public class HashSet객체중복설정HashCode예제 {
public static void main(String[] args) {
Student student1 = new Student("홍", "길동");
Student student2 = new Student("홍", "길동");
System.out.println("student1.equals(student2): " + student1.equals(student2));
System.out.println("student1.hashcode(): " + student1.hashCode());
System.out.println("student2.hashCode(): " + student2.hashCode());
}
}
class Student {
String firstName;
String lastName;
public Student(String firstN, String lastN) {
this.firstName = firstN;
this.lastName = lastN;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Student) {
Student tmp = (Student)obj;
return firstName.equals(tmp.firstName) && lastName.equals(tmp.lastName);
}
return false;
}
}
결과:
equals 메소드만 오버라이딩 한다면, equals메소드에 대해 동일한 객체들의 해쉬코드는 반드시 같아야 한다는 일반적인 규칙에 어긋나게 된다.
(hashCode의 기본 리턴값은 객체의 경우 객체의 주소에 대한 해쉬값, String객체의 경우 문자열에 대한 해쉬값이다. 위의 예에선 두개의 서로 다른 인스턴스이므로 당연히 서로다른 주소값에 대한 해쉬값도 다를 것이다.)
또한, 애초에 HashSet은 객체를 저장하기 전에 먼저 객체의 hashCode() 메소드를 호출해서 해시코드를 얻어낸다. 그리고 저장되어 있는 객체들의 해시코드와 비교한다. 만약 같은 해시코드가 있다면 다시 equals() 메소드로 두 객체를 비교해서 true가 나오면 동일한 객체로 판단하고 중복 저장하지 않는다.
왜 다시 equals( )로 두 객체를 비교하는가?
public class HashSet객체중복설정HashCode예제 {
public static void main(String[] args) {
Student student1 = new Student("홍", "길동");
Student student3 = new Student("홍길", "동");
System.out.println("student1.equals(student3): " + student1.equals(student3));
System.out.println("student1.hashcode(): " + student1.hashCode());
System.out.println("student3.hashCode(): " + student3.hashCode());
}
}
class Student {
String firstName;
String lastName;
public Student(String firstN, String lastN) {
this.firstName = firstN;
this.lastName = lastN;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Student) {
Student tmp = (Student)obj;
return firstName.equals(tmp.firstName) && lastName.equals(tmp.lastName);
}
return false;
}
@Override
public int hashCode() {
return (firstName+lastName).hashCode();
}
}
hashCode()의 오버라이딩을 보면, (firstName+lastName).hashCode(); 이므로
student1의 해쉬코드: 홍길동.hashCode()
student3의 해쉬코드: 홍길동.ahshCode()
즉, 둘이 같음을 볼 수 있다.
하지만, 두 학생이 진짜로 같은가? 아니다. student1은 성이 "홍"이고, student3은 성이 "홍길"이다. 따라서, hashCode( )만으로 중복을 검출하는 것이 아닌 equals( )메소드로 다시 두 객체를 비교하는 것이다.
결론:
1. equals메소드에 따라 동일하다고 판단된 객체들은 반드시 동일한 해쉬코드를 가져야하는 일반적인 규칙을 지키고
2. HashSet의 작동원리 (hashSet메소드로 해쉬코드 값 비교 후, equals메소드로 같은 객체인지 확인)에 의해
equals메소드와 hashCode메소드 둘 다 오버라이딩
HashMap
map인터페이스를 구현한 컬렉션. map을 구현했으므로 (Key, Value) 페어를 하나의 엔트리로 저장.
Key: 유일(중복X)
Value: 중복허용
'Today I Learned' 카테고리의 다른 글
TIL 2022-07-04,05 (0) | 2022.07.06 |
---|---|
TIL 2022-06-24 (0) | 2022.06.25 |
TIL 2022-06-23 (0) | 2022.06.23 |
TIL 2022-06-21 (0) | 2022.06.22 |
TIL 2022-06-17 (0) | 2022.06.17 |