💡HashSet Class
Set 인터페이스는 순서가 없는 데이터 집합으로, 요소를 구분하는 식별자가 없다는 특징이 있다.
방을 식별하는 번호나 식별자가 없다는 말은 똑같은 데이터가 2개 들어 있으면 데이터를 구분할 수 있는 방법이 없다는 의미이다. 따라서 Set은 데이터 중복을 허용하지 않는다.
ArrayList 클래스와 비교해보며 HashSet의 특징을 알아보도록 하자!
HashSet 클래스의 활용
HashSet 선언하기
// ArrayList<String> list = new ArrayList<String>();
HashSet<String> set = new HashSet<String>();
요소 추가하기
// list.add("강아지");
// list.add("고양이");
// list.add("거북이");
set.add("강아지");
set.add("고양이");
set.add("거북이");
HashSet 클래스는 데이터를 추가할 때 ArrayList 클래스와 동일하게 add() 메소드를 사용한다.
요소 개수 확인하기
// System.out.println(list.size());
System.out.println(set.size());
요소 읽기
// System.out.println(list); // [강아지, 고양이, 거북이]
System.out.println(set); // [고양이, 거북이, 강아지]
ArrayList 클래스는 인덱스가 있으며, 데이터에 순서가 있는 컬렉션이다. 그래서 내가 넣은 요소를 순서대로 관리하는 반면, HashSet 클래스는 순서가 없는 컬렉션이므로 순서에 관계없이 데이터가 출력된다.
요소 검색하기
System.out.println(set.contains("고양이")); // true
contains() 메소드를 사용하여 배열 내에 데이터가 존재하는 지 검색할 수 있다.
요소 삭제하기
// list.remove(1);
// list.remove("거북이");
// System.out.println(list); // [강아지, 고양이]
set.remove("거북이");
System.out.println(list); // [강아지, 고양이]
HashSet 클래스는 방번호가 없기 때문에 데이터를 지울 때 인덱스가 아닌 값으로 밖에 지울 수 없다.
마찬가지로 indexOf, 데이터 삽입 등의 기능에 있어 제한적이다.
데이터 중복 발생
// list.add("고양이"); // 중복
// System.out.println(list); // [강아지, 고양이, 거북이, 고양이]
set.add("고양이"); // 중복
System.out.println(set); // [고양이, 거북이, 강아지]
HashSet 클래스에 add() 메소드로 중복된 데이터를 추가하려고 했지만 데이터가 들어가지 않았다.
System.out.println(set.add("고양이")); // false
이를 boolean형으로 출력해보면 false가 출력되는 것을 볼 수 있다.
이러한 중복값을 자동으로 배제하는 특성이 단점처럼 보일 수 있으나, 오히려 set 인터페이스를 사용할만한 이유가 된다.
실제 중복이 발생하는 소스코드를 살펴보며 set 인터페이스의 장점을 알아보도록 하자!
HashSet 배열 정렬
HashSet<Integer> set3 = new HashSet<Integer>();
set3.add(20);
set3.add(50);
set3.add(30);
set3.add(10);
set3.add(40);
System.out.println(set3); // [50, 20, 40, 10, 30]
ArrayList<Integer> list = new ArrayList<Integer>(set3);
Collections.sort(list);
System.out.println(list); // [10, 20, 30, 40, 50]
HashSet 배열의 데이터를 정렬하고자 할 때 Set과 List간에 변환하는 방법을 이용한다.
리스트를 바꿔서 sort() 메소드를 호출한 다음에 출력을 하면 정렬이 이루어진 것을 확인할 수 있다.
Set 인터페이스의 장점
Case 1. ArrayList Class
ArrayList<Integer> lotto = new ArrayList<Integer>();
for (int i=0; i<6; i++) {
int n = (int)(Math.random() * 45) + 1;
boolean flag = false;
for (int j=0; j<i; j++) {
if (lotto.get(j) == n) {
flag = true;
break;
}
}
if (!flag) {
lotto.add(n);
} else {
i--;
}
}
System.out.println(lotto); // [4, 5, 38, 24, 41, 10]
ArrayList 클래스를 사용했을 때 문제점은 동일한 값이 발생할 수 있다는 점이다.
따라서 중복 문제에 대한 사전 처리를 개발자가 직접 구현해야 한다는 단점이 존재한다.
Case 2. HashSet Class
HashSet<Integer> lotto2 = new HashSet<Integer>();
while (lotto2.size() < 6) {
int n = (int)(Math.random() * 45) + 1;
lotto2.add(n);
}
그러나 HashSet 클래스는 자동으로 중복을 배제하므로, 중복값는 넣고 싶어도 애초에 넣을 수가 없다. 그래서 ArrayList로 난수를 생성할 때 했던 중복 검사를 할 필요가 사라진다.
중복이 발생하면 값을 배열에 넣지 않으므로, 만들어진 난수가 6개일 경우 반복문을 빠져나오게 하고, 그 전까지는 배열에 계속 난수를 생성시키면 된다.
이렇듯 중복값을 저장할 때 장점이 있어 보편적으로 중복값을 허용하지 않을 때에는 Set 인터페이스를 사용하는 편이다.
요소 탐색하기
for (int num : lotto2) {
System.out.println(num);
}
foreach문을 사용하면 배열에 있는 값을 하나씩 꺼내와서 사용할 수 있다.
또한 향상된 for문은 Iterator 동작을 편리하게 구현할 수 있게 해준다.