☕️ Set 이란?
Set이란 컬렉션 프레임워크(collection framework)의 한 종류로써 데이터의 중복을 허용하지 않는 특징이 있다.
아래의 코드 예시를 보자.
import java.util.*;
public class SetEx {
public static void main(String[] args) {
Set<String> set = new HashSet<>();
set.add("가");
set.add("나");
set.add("가"); // 중복을 허용하지 않는다.
set.add("다");
System.out.println(set); // [가, 다, 나]
}
}
set에 '가', '나', '가', '다' 순으로 '가'를 2번 추가하였지만, Set의 특징상 중복을 허용하지 않기 때문에 최종 출력되는 결괏값을 보면 '가'는 한 개만 존재하는 것을 알 수 있다. 이처럼 Set은 중복을 허용하지 않을 때 사용된다.
만약 Set에 문자열이 아닌 인스턴스가 추가된다면 어떻게 될까?
아래 예시 코드는 Person을 Set에 담는 테스트 코드이다.
Person은 이름과 나이를 필드로 가지며, 전체코드가 의도하는 바는 이름과 나이가 같으면 중복으로 간주하는 것이다.
import java.util.*;
public class SetEx {
public static void main(String[] args) {
Set<Person> set = new HashSet<>();
set.add(new Person("choi", 20));
set.add(new Person("kim", 30));
set.add(new Person("choi", 20));
System.out.println(set); // [kim(30), choi(20), choi(20)]
}
}
class Person {
String name;
int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return name + "(" + age + ")";
}
}
우리가 의도하는 바는 이름과 나이가 같으면 중복으로 간주하는 것이었다.
하지만 실제 출력을 확인해 보면 나이가 20세인 choi라는 사람이 2번 출력되는 것을 확인할 수 있을 것이다.
이는 중복으로 간주되지 않았다는 뜻인데, 그 원인을 살펴보도록 하자.
☕️ equals() 및 hashCode()의 재정의 필요성
결론부터 말하자면 중복으로 간주되지 않았다는 것은 서로 값이 다른 것으로 판단되었다는 뜻이기도 하다.
그 원인은 Collection(HashMap, HashSet, HashTable 등)에서 객체가 논리적으로 같은지 판단할 때는 아래의 그림과 같은 과정을 거친다.
즉, 위에서 사용한 HashSet의 중복을 판단하기 위해서는 hashCode()와 equals()를 모두 재정의해야 하는 것을 의미한다.
위의 Person을 사용한 코드에서 equals()만 재정의한 코드와 equals() 및 hashCode()를 재정의한 코드를 비교해 보자.
// equals()만 재정의한 코드
import java.util.*;
public class SetEx {
public static void main(String[] args) {
Set<Person> set = new HashSet<>();
set.add(new Person("choi", 20));
set.add(new Person("kim", 30));
set.add(new Person("choi", 20));
System.out.println(set); // [kim(30), choi(20), choi(20)]
}
}
class Person {
String name;
int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return name + "(" + age + ")";
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Person)) {
return false;
}
Person p = (Person) obj;
return this.name.equals(p.name) && this.age == p.age;
}
}
첫 번째 코드와 마찬가지로 choi(20)의 값이 2개 존재하는 것을 확인할 수 있다.
이제 equals() 뿐만 아니라 hashCode()도 재정의한 코드의 결괏값을 확인해 보자.
// equals()와 hashCode() 모두 재정의한 코드
import java.util.*;
public class SetEx {
public static void main(String[] args) {
Set<Person> set = new HashSet<>();
set.add(new Person("choi", 20));
set.add(new Person("kim", 30));
set.add(new Person("choi", 20));
System.out.println(set); // [choi(20), kim(30)]
}
}
class Person {
String name;
int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return name + "(" + age + ")";
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Person)) {
return false;
}
Person p = (Person) obj;
return this.name.equals(p.name) && this.age == p.age;
}
}
드디어 의도한 바와 같이 중복이 제거된 것을 확인할 수 있게 되었다.
이처럼 컬렉션 프레임워크를 사용할 때는 equals() 뿐만 아니라 hashCode()도 재정의해야 함을 확인할 수 있게 되었다.
References.
1. Tecoble - equals와 hashCode는 왜 같이 재정의해야 할까?
2. Inpa - 자바 equals / hashCode 오버라이딩 - 완벽 이해하기
'IT' 카테고리의 다른 글
[Java] 람다식(Lambda Expressions) (1) | 2025.01.22 |
---|---|
[Java] 함수형 인터페이스(Functional Interface) (1) | 2025.01.22 |
[Java] 컬렉션 프레임워크(Collection Framework) (1) | 2025.01.22 |
[Java] 제네릭(Generic) (1) | 2025.01.22 |
[Java] 오버로딩(Overloading)과 오버라이딩(Overriding) (1) | 2025.01.22 |