Dico

[Java] Class HashSet

  • 민갤

Collection Framework

데이터 군을 저장하는 클래스들을 표준화한 설계

컬렉션 Collection :  다수의 데이터. 데이터 그룹

프레임웍 Framework :  표준화된 프로그래밍 방식.

Class HashSet

Set 인터페이스를 구현한 가장 대표적인 컬렉션.

내부적으로 HashMap을 이용해서 만들어졌으며, 해싱(hashing)을 이용해서 구현했다.

중복된 요소를 저장하지 않는다.

자체적인 저장방식에 따라 순서를 결정한다.

Public constructors

생성자설명
  HashSet()  HahSet 객체 생성
  HashSet(Collection<? extends E> c)  주어진 컬렉션을 포함하는 HashSet 객체 생성
  HashSet(int initialCapacity, float loadFactor)  초기 용량과 load factor를 지정하는 생성자
  HashSet(int initialCapacity)  주어진 값을 초기 용량으로 하는 HashSet 객체 생성

  • load factor

    컬렉션 클래스에 저장공간이 가득 차기 전에 미리 용량을 확보하기 위한 것.

    값을 0.8로 지정하면, 저장 공간의 80%가 채워져 있을 때 용량이 두 배로 늘어난다. default 0.75

Public methods

반환타입이름설명
  boolean  add(E e)  새로운 객체 저장
  void  clear()  저장된 모든 객체 삭제
  Object  clone()  HashSet을 복제해서 반환 (얕은 복사)
  boolean  contains(Object o)  지정된 객체를 포함하고 있는 지 확인
  boolean  isEmpty()  HashSet이 비어있는 지 확인
  Iterator  iterator()  Iterator를 반환
  boolean  remove(Object o)  지정된 객체를 HashSet에서 삭제. 성공하면 true
  int  size()  저장된 객체의 개수 반환

중복

중복된 값은 저장되지 않는다.

중복을 제거하는 동시에 저장한 순서를 유지하고 싶다면 LinkedHashSet을 사용한다.

Object[] objArr = { "0", new Integer(0), "1", "1", "2", "3", "3", "3" };
Set set = new HashSet();

for (int i = 0; i < objArr.length; i++) {
        set.add(objArr[i]);
}

System.out.println(set); // [0, 1, 0, 3, 2]

'"0"'과 'new Integer(0)'은 서로 다른 객체이므로 중복으로 간주되지 않는다.

add()

새로운 요소를 추가하기 전에, 기존에 저장된 요소와 같은 것인지 판별하기 위하여

추가하려는 요소의 equals()와 hashCode()를 호출한다.

package blog;

import java.util.HashSet;
import java.util.Objects;

public class Main {

    public static void main(String[] args) {
        HashSet set = new HashSet();

        set.add("abc");
        set.add("abc");
        set.add(new Test("maria", 10));
        set.add(new Test("maria", 10));

        System.out.println(set.toString()); // [abc, maria : 10]

    }
}

class Test {
    String name;
    int age;

    Test(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Test) {
            Test tmp = (Test) obj;
            return name.equals(tmp.name) && age == tmp.age;
        }
        return false;
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age); // public static int hash(Object... values)
    }

    @Override
    public String toString() {
        return name + " : " + age;
    }
}

  • equals()

    서로 같으면 true를 반환하도록 오버라이딩.

  • hashCode()

    JDK1.8부터 추가된 java.util.Objects 클랙스의 hash()를 이용해서 작성.

 

1. 동일한 객체에 대해서 여러 번 hashCode()를 호출해도 동일한 값을 반환해야 한다.

   단, 실행 시마다 동일한 int 값을 반환할 필요는 없다. 

   (Object 클래스는 객체의 주소로 해시코드를 만들기 때문에 실행 시마다 달라질 수 있다.)

Test t = new Test("maria", 10);

int hc0 = t.hashCode();                               // -1081307243
int hc1 = t.hashCode();                               // -1081307243

t.age = 20;
int hc2 = t.hashCode();                               // -1081307233

2. equals()로 true를 얻은 두 객체는 각각 hashCode()를 호출해서 얻은 결과가 반드시 같아야 한다.

Test t0 = new Test("maria", 10);
Test t1 = new Test("maria", 10);

System.out.println(t0.equals(t1));                    // true

int hc0 = t0.hashCode();                              // -1081307243
int hc1 = t1.hashCode();                              // -1081307243

3. equals()로 false를 얻은 두 객체는 hashCode()를 호출했을 때 항상 다른 int 값을 반환하는 것이 좋다.

    false일지라도 두 객체의 해시코드 값이 같을 수 있다.

    해싱을 사용하는 컬렉션의 성능을 향상시키기 위해서는 가능한 서로 다른 값을 반환하도록 작성해야 한다.

    두 객체의 해시코드가 같다고 해서 equals 메서드의 호출 결과가 반드시 true가 되지 않는다.

Example

HashSet setA = new HashSet();
HashSet setB = new HashSet();
HashSet setAnB = new HashSet();
HashSet setA_B = new HashSet();
HashSet setAuB = new HashSet();

for (int i = 0; i < 5; i++) {
    setA.add((int)(Math.random() * 10));    // [7, 4, 9, 3]
    setB.add((int)(Math.random() * 10));    // [9, 5, 6, 1]
}

Iterator it = setA.iterator();
while (it.hasNext()) {
    Object tmp = it.next();
    if (setB.contains(tmp)) {
        setAnB.add(tmp);                    // A ∩ B = [9]
    }
}

it = setA.iterator();
while (it.hasNext()) {
    Object tmp = it.next();
    if (!setB.contains(tmp)) {
        setA_B.add(tmp);                    // A - B = [4, 7, 3]
    }
    setAuB.add(tmp);
}

it = setB.iterator();
while(it.hasNext()){
    setAuB.add(it.next());                  // A ∪ B = [4, 1, 7, 9, 6, 5, 3]
}

HashSet API

참고 서적: 자바의 정석 3판 2