1. HashSet
HashSet은 Set 인터페이스를 구현한 가장 대표적인 컬렉션이며, Set인터페이스의 특징대로 중복된 요소를 저장하지 않는다. 따라서 새로운 요소를 추가할 때는 add, addAll메서드를 사용하는데 만약 이미 저장되어 있는 요소와 중복된 요소를 추가하고자 하면 이 메서드들은 false를 반환해 추가에 실패했다는 것을 알려준다. 따라서 이러한 특징을 이용하면 컬렉션 내의 중복 요소들을 쉽게 제거할수 있다.
ArrayList와 달리 HashSet은 저장순서를 유지하지 않으므로 저장순서를 유지하고자 한다면 LinkedHashSet을 이용해야한다.
메서드 | 설명 |
HashSet() | 객체 생성 |
HashSet(Collection c) | 주어진 컬렉션을 포함하는 HashSet 객체 생성 |
HashSet(int initialCapacity) | 주어진 값을 초기용량으로 하는 HashSet객체 생성 |
HashSet(int initialCapacity, float loadFactor) | 초기용량과 load factor을 지정하는 생성자 |
boolean add(Object o) | 새로운 객체를 저장 |
boolean addAll(Collection c) | 주어진 컬렉션에 저장된 모든 객체들을 추가 (합집합) |
void clear() | 저장된 모든 객체를 삭제 |
Object clone() | HashSet을 복제해서 반환 (얕은 복사) |
boolean contains(Object o) | 지정된 객체를 포함되고 있는지 알려준다. |
boolean containsAll(Collection c) | 주어진 컬렉션에 저장된 모든 객체들을 포함하고 있는지 알려준다 |
boolean isEmpty() | HashSet이 비어있는지 알려준다. |
Iterator iterator() | Iterator이 비어있는지 알려준다. |
boolean remove(Object o) | 지정된 객체를 HashSet에서 삭제 |
boolean removeAll(Collection c) | 주어진 컬렉션에 저장된 모든객체와 동일한 것들을 HashSet에서 삭제(차집합) |
boolean retainAll(Collection c) | 주어진 컬렉션에 저장된 모든객체와 동일한 것만 남기고 HashSet에서 삭제(교집합) |
int size() | 저장된 객체의 개수를 반환 |
Object[] toArray() | 저장된 객체들을 객체배열의 형태로 반환 |
Object[] toArray(Object[] a) | 저장된 객체들을 주어진 객체배열(a)에 담는다. |
- load factor : 컬렉션 클래스에 저장공간이 가득 차기 전에 미리 용량을 확보하기 위한 것으로 만약 0.8로 지정하면, 저장공간의 80%가 채워졌을때 용량이 2배로 늘어난다. 기본값은 0.75 즉 75%이다.
//객체 생성
HashSet<Integer> hs1 = new HashSet<>();
hs1.add(1);
HashSet<Integer> hs2 = new HashSet<>(hs1); //주어진 컬렉션을 포함하는 객체 생성
HashSet<Integer> hs3 = new HashSet<>(10, 0.8F);//로드팩터가 0.8
System.out.println(hs2.contains(2));
System.out.println(hs2.addAll(hs1)); //[1] 에 [1] 추가해서 false
System.out.println(hs2); //그대로 [1]
hs2.remove(1); //객체 삭제
hs2.removeAll(hs1);//컬렉션 삭제
- HastSet 예제 1 :중복 저장 유무 확인
public static void main(String[] args) {
Object[] objArr = {"1", 1, "2","2","2","4"};
HashSet<Object> hs = new HashSet<>();
//asList는 배열을 List로 변환
//후에 HashSet에 저장
hs.addAll(Arrays.asList(objArr));
System.out.println(hs); //1,1,2,4,
}
- HastSet 예제2 : 중복된 값은 저장되지 않는 HashSet을 이용한 로또번호 만들기
public static void main(String[] args) {
Set<Integer> set = new HashSet<>();
for(int i =0; i < 6; i++){
int num = (int)(Math.random() *45);//0.0 ~ 0.1사이의 랜덤 숫자
set.add(num);
}
List<Integer> list = new LinkedList<>(set); //LinkedList(Collection c)
Collections.sort(list);//크기순으로 정렬
System.out.println(list);
}
- HastSet 예제3 : 빙고 만들기 - HashSet은 저장된 순서를 보장하지 않고 자체적인 저장방식에 따라 순서가 결정되기 때문에 몇번 실행을 하면 같은 숫자가 비슷한 위치에 나온다 . 따라서 HashSet보다 저장순서를 유지하는 LinkedHashSet이 더 나은 선택이다.
public static void main(String[] args) {
//HashSet<Object> set1 = new HashSet<>(); //자체적인 저장방식에 따라 순서가 결정
HashSet<Integer> set1 = new LinkedHashSet<>(); //
int[][] board = new int[5][5]; //2차원 배열
for (int i = 0; set1.size() < 25; i++) { //25번 add
set1.add((int) (Math.random() * 50) + 1);//1을 더하는 이유는 1부터 시작하기 위해서
}
Iterator it = set1.iterator();
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[i].length; j++) {
board[i][j] = (int) it.next(); //next의 반환값은 Object
System.out.print((board[i][j] < 10 ? " " : " ") + board[i][j]);
}
System.out.println();
}
// for(int[] tmp: board){
// System.out.println(Arrays.toString(tmp));
// }
- HastSet 예제 4 : name과 age가 같으면 같은 사람으로 인식하기
public class HashSetEx3 {
public static void main(String[] args) {
Set<Object> set = new HashSet<>();
set.add("abe");
set.add("abe");
set.add(new Person("Lucy", 10));
set.add(new Person("Lucy", 10));
System.out.println(set); //[abe, Lucy:10, Lucy:10]
}
}
class Person {
String name;
int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() { //toString을 재정의
return name + ":" + age; //따라서 직접 호출하지 않아도 문자열 출력시,자동으로 호출된다.
}
}
위의 예제의 결과를 보면 Hashset인데도 불구하고 결과는 [abe, Lucy:10, Lucy:10] 로,name과 age의 값이 같음에도 불구하고 서로 다른 것으로 인식해 2번 출력되었다. 그렇다면 두 인스턴스를 같은것으로 인식하게 하려면 어떻게 해야할까?
- HastSet 예제 4 : name과 age가 같으면 같은 사람으로 인식하기 2
public class HashSetEx4 {
public static void main(String[] args) {
Set<Object> set = new HashSet<>();
set.add("abe");
set.add("abe");
Person2 p1 = new Person2("Lucy", 10);
Person2 p2 = new Person2("Lucy", 20);
System.out.println(System.identityHashCode(p1));
System.out.println(System.identityHashCode(p2));
System.out.println("객체 비교#: " + (p1 == p2)); //false ,new 연산자로 만들면 주소값이 다르며, ==는 객체의 주소값을 비교한다.
System.out.println(System.identityHashCode(p1.age));
System.out.println(System.identityHashCode(p2.age));//10,20으로 값이 다르기때문에 인스턴스변수의 주소값이 디름. 10,10이면 값게 나옴.
System.out.println("인스턴스 변수 주소비교#: " + (p1.age == p2.age)); //false
set.add(p1);
set.add(p2);
System.out.println(set); //[abe, Lucy:10, Lucy:10]
}
}
class Person2 {
String name;
Integer age;
//생성자
Person2(String name, int age) {
this.name = name;
this.age = age;
}
public String toString() { //toString을 재정의
return name + ":" + age; //따라서 직접 호출하지 않아도 문자열 출력시,자동으로 호출
}
//name과 age가 같으면 서로 true를 반환
@Override
public boolean equals(Object obj) {
System.out.println(obj); //p2더할때 obj는 p2
if (obj instanceof Person2) { //자식관계확인
Person2 tmp = (Person2) obj;
return name.equals(tmp.name) && age == tmp.age; //String이므로 주소가아닌 문자열 비교
}
return false;
}
public int hashCode() {
return (name + age).hashCode();//해쉬코드란 각 객체의 주소값을 변환해 생성한 객체의 고유한 정수값
}
}
HashSet의 add()메서드는 새로운 요소를 추가하기전에 기존에 저장된 요소와 같은 것인지 판별하기 위해 추가하려는 요소의 equals()와 hashCode()를 호출하기때문에 메서드를 목적에 맞게 오버라이딩해야한다.
그래서 String 클래스에서 같은 내용의 문자열에 대한 equals() 호출결과가 true인 것과 같이 Person 클래스에서도 두 인스턴스의 name, age가 서 로 같으면 true를 반환하도록 equals()를 오버라이딩했다.
- HastSet 예제 5 : 합집합, 교집합, 차집합
public static void main(String[] args) {
HashSet<Object> setA = new HashSet<>();
HashSet<Object> setB = new HashSet<>();
HashSet<Object> setHap = new HashSet<>(); //합집합
HashSet<Object> setKyo = new HashSet<>();// 교집합
HashSet<Object> setCha = new HashSet<>(); //차집합
setA.add("1");
setA.add("2");
setA.add("3");
setA.add("4");
setA.add("5");
System.out.println("A = " + setA);
setB.add("4");
setB.add("5");
setB.add("6");
setB.add("7");
setB.add("8");
System.out.println("B = " + setB);
//교집합
Iterator it = setB.iterator();
while (it.hasNext()) {
Object tmp = it.next();
if (setA.contains(tmp)) {
setKyo.add(tmp);
}
}
//차집합
it = setA.iterator();
while (it.hasNext()) {
Object tmp = it.next();
if (!setB.contains(tmp)) {
setCha.add(tmp);
}
}
//합집합
it = setA.iterator();
while (it.hasNext()) {
setHap.add(it.next());
}
it = setB.iterator();
while (it.hasNext()) {
setHap.add(it.next());
}
System.out.println(setHap);
System.out.println(setKyo);
System.out.println(setCha);
}
+ System.identityHashCode()
참고로 System.identityHashCode()과 hashCode() 모두 객체의 고유한 값을 리턴한다는 점에서 같다. 하지만 차이가 있는데 hashCode()는 하위 클래스에서 오버라이드가 가능하기때문에 객체마다 유일한 값을 갖고있지않다. 예를들어 String의 hashcode가 갖다면 객체주소는 달라도 문자열이 동일하다는 것을 의미한다. 하지만System.identityHashCode()는 오버라이드가 안되며 객체의 고유한 hashcode를 리턴한다. 따라서 System.identityHashCode() 를 사용하는 것이 좋다.
출처 : 자바의 정석
'Java' 카테고리의 다른 글
[JAVA] HashMap과 Hashtable (0) | 2023.12.07 |
---|---|
[JAVA] TreeSet (0) | 2023.12.07 |
[Java] Stack과 Queue (1) | 2023.12.06 |
[Java] LinkedList (1) | 2023.12.06 |
[Java] ArrayList (1) | 2023.12.06 |