[Java] Set 컬렉션 + 합집합/교집합/차집합
Set 컬렉션
Set 컬렉션은 저장 순서가 유지되지 않는다. 또한 객체를 중복해서 저장할 수 없고, 하나의 null만 저장할 수 있다. Set 컬렉션은 수학의 집합에 비유될 수 있다. 집합은 순서와 상관없고 중복이 허용되지 않기 때문이다.
Set 컬렉션에는 HashSet, LinkedHashSet, TreeSet 등이 있으며 Set 컬렉션에서 공통적으로 사용 가능한 Set 인터페이스 메서드는 다음과 같다.
import java.util.Set;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.TreeSet;
기능 | 메서드 | 설명 |
객체 추가 | boolean add(E e) | 주어진 객체를 성공적으로 저장하면 true를 리턴하고, 중복 객체면 false를 리턴 |
객체 검색 | boolean contains(Object o) | 주어진 객체가 저장되어 있는지 여부 |
isEmpty() | 컬렉션이 비어 있는지 조사 | |
Iterator<E> iterator() | 저장된 객체를 한 번씩 가져오는 반복자 리턴 | |
int size() | 저장되어 있는 전체 객체 수 리턴 | |
객체 삭제 | void clear() | 저장된 모든 객체를 삭제 |
boolean remove(Object o) | 주어진 객체를 삭제 |
HashSet
Set<E> set = new HashSet<>(); // E에 지정된 타입의 객체만 저장
Set set = new HashSet(); // 모든 타입의 객체를 저장
HashSet은 동일한 객체를 중복 저장하지 않는다. 여기서 동일한 객체란 동등 객체를 말한다. HashSet은 다른 객체라도 hashCode() 메서드의 리턴값이 같고, equals() 메서드가 true를 리턴하면 동일한 객체라고 판단하고 중복 저장하지 않는다.
문자열을 HashSet에 저장할 경우, 같은 문자열을 갖는 String 객체는 동등한 객체로 간주한다. 같은 문자열이면 hashCode()의 리턴값이 같고, equals()의 리턴값이 true가 나오기 때문이다.
import java.util.Set;
import java.util.HashSet;
public class Main
{
public static void main(String[] args) {
Set<String> set = new HashSet<>();
set.add("Java");
set.add("java");
set.add("Spring");
set.add("SPRING");
set.add("java"); // 저장하지 않음
set.add("JPA");
set.add(null);
set.add(null); // 하나의 null만 저장할 수 있음
int size = set.size();
System.out.println("size: " + size);
for (String str : set) {
System.out.println(str);
}
}
}
다음 예제는 이름과 나이가 동일할 경우 Member 객체를 HashSet에 중복 저장하지 않는다. Member 클래스를 선언할 때 이름과 나이가 동일하다면 동일한 해시코드가 리턴되도록 hashCode()를 재정의하고, equals() 메서드가 true를 리턴하도록 재정의했기 때문이다.
import java.util.Set;
import java.util.HashSet;
class Member {
private String name;
private int age;
public Member(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return this.name;
}
public int getAge() {
return this.age;
}
@Override
public int hashCode() {
return name.hashCode() + age;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Member target) {
return target.name.equals(name) && (target.age == age);
} else {
return false;
}
}
}
public class Main
{
public static void main(String[] args) {
Set<Member> set = new HashSet<>();
set.add(new Member("헬로우", 20));
set.add(new Member("프로그", 10));
set.add(new Member("헬로우", 20)); // 인스턴스는 다르지만 동등 객체이므로, 저장되지 않음
int size = set.size();
System.out.println("size: " + size);
for (Member member : set) {
System.out.println("name: " + member.getName() + ", age: " + member.getAge());
}
}
}
LinkedHashSet
LinkedHashSet은 요소를 추가된 순서대로 유지하는 Set의 구현체
import java.util.Set;
import java.util.LinkedHashSet;
public class Main
{
public static void main(String[] args) {
Set<String> set = new LinkedHashSet<>();
set.add("Orange");
set.add("Apple");
set.add("Banana");
set.add("Apple"); // 저장되지 않음
int size = set.size();
System.out.println("size: " + size);
for (String str : set) {
System.out.println(str); // 추가된 순서대로 출력됨
}
}
}
TreeSet
TreeSet은 요소를 정렬된 순서로 유지하는 Set의 구현체
import java.util.Set;
import java.util.TreeSet;
public class Main
{
public static void main(String[] args) {
Set<String> set = new TreeSet<>();
set.add("Kiwi");
set.add("Banana");
set.add("Apple");
set.add("Orange");
set.add("Apple"); // 저장하지 않음
int size = set.size();
System.out.println("size: " + size);
for (String str : set) {
System.out.println(str); // 정렬된 순서대로 출력됨
}
}
}
Comparator로 사용자 정의 정렬 기준을 제공할 수 있다.
import java.util.Set;
import java.util.TreeSet;
public class Main
{
public static void main(String[] args) {
Set<String> set = new TreeSet<>((a, b) -> b.compareTo(a)); // 역순 정렬
set.add("Kiwi");
set.add("Banana");
set.add("Apple");
set.add("Orange");
set.add("Apple"); // 저장하지 않음
int size = set.size();
System.out.println("size: " + size);
for (String str : set) {
System.out.println(str); // 역순 정렬된 순서대로 출력됨
}
}
}
만약 HashSet이나 LinkedHashSet을 정렬된 형태로 사용하고 싶다면, List로 변환한 후 정렬 할 수 있다.
import java.util.*;
public class Main
{
public static void main(String[] args) {
Set<String> set = new HashSet<>();
set.add("Kiwi");
set.add("Banana");
set.add("Apple");
set.add("Orange");
set.add("Apple"); // 저장하지 않음
// HashSet을 List로 변환 후 정렬
List<String> list = new ArrayList<>(set);
Collections.sort(list);
System.out.println("size: " + list.size());
System.out.println(list);
}
}
Set을 순회하는 방법
1. for-each 루프를 사용한 순회
2. Iterator를 사용한 순회
3. forEach 메서드를 사용한 순회
import java.util.*;
public class Main
{
public static void main(String[] args) {
Set<String> set = new HashSet<>();
set.add("Kiwi");
set.add("Banana");
set.add("Apple");
set.add("Orange");
set.add("Apple"); // 저장하지 않음
// for-each 루프를 사용한 순회
for (String element : set) {
System.out.println(element);
}
System.out.println();
// Iterator를 사용한 순회
Iterator<String> it = set.iterator();
while (it.hasNext()) {
String element = it.next();
System.out.println(element);
}
System.out.println();
// forEach 메서드를 사용한 순회
set.forEach(element -> System.out.println(element));
}
}
합집합/교집합/차집합
- 합집합(Union)
- addAll(Collection c)
- 첫 번째 집합에서 두 번째 집합의 모든 요소를 추가
- 교집합(Interection)
- retainAll(Collection c)
- 첫 번째 집합에서 두 번째 집합과 공통으로 포함된 요소만 남김
- 차집합(Difference)
- removeAll(Collection c)
- 첫 번째 집합에서 두 번째 집합의 요소를 제거
import java.util.*;
public class Main
{
public static void main(String[] args) {
Set<String> set1 = new HashSet<>();
set1.add("Kiwi");
set1.add("Banana");
set1.add("Orange");
Set<String> set2 = new HashSet<>();
set2.add("Pineapple");
set2.add("Apple");
set2.add("Banana");
// 합집합 생성
Set<String> unionSet = new HashSet<>(set1);
unionSet.addAll(set2);
// 교집합 생성
Set<String> intersectionSet = new HashSet<>(set1);
intersectionSet.retainAll(set2);
// 차집합 생성
Set<String> differenceSet = new HashSet<>(set1);
differenceSet.removeAll(set2);
// 출력
System.out.println(unionSet);
System.out.println(intersectionSet);
System.out.println(differenceSet);
}
}