본문 바로가기

# 02/Java

[윤성우 열혈자바] 23-3. Set<E> 인터페이스를 구현하는 컬렉션 클래스들

반응형

Set<E>을 구현하는 클래스의 특성과 HashSet<E> 클래스


Set<E> 인터페이스를 구현하는 제네릭 클래스들은 다음 두 가지 특성을 갖는다.

  • 저장 순서가 유지되지 않는다.
  • 데이터의 중복 저장을 허용하지 않는다.


public static void main(String[] args) {

Set<String> set = new HashSet<>();

set.add("Toy");

set.add("Box");

set.add("Robot");

set.add("Box");

System.out.println("인스턴스 수 : " + set.size());


// 반복자를 이용한 전체 출력

for (Iterator<String> itr = set.iterator(); itr.hasNext();  )

System.out.print(itr.next() + '\t');

System.out.println();


// for-each문을 이용한 전체 출력

for(String s : set)

System.out.print(s + '\t');

System.out.println();

}




출력 결과를 통해 동일 인스턴스가 저장되지 않음을 알 수 있다.


그렇다면 동일 인스턴스의 기준은?








동일 인스턴스에 대한 기준은?


public boolean equals(Object obj)

Object 클래스의 equals 메소드 호출 결과를 근거로 동일 인스턴스를 판단한다.


public int hashCode()

그런데 그에 앞서 Object 클래스의 hashCode 메소드 호출 결과가 같아야 한다.


정리하면,

두 인스턴스가 hashCode 메소드 호출 결과로 반환하는 값이 동일해야 한다.

그리고 이어서 두 인스턴스를 대상으로 equals 메소드의 호출 결과 true가 반환되면 동일 인스턴스로 간주한다.







해쉬 알고르즘의 이해


분류 대상 : 3, 5, 7, 12, 25, 31

적용 해쉬 알고리즘 : num % 3


이렇듯 분류를 해 놓으면 탐색의 속도가 매우 빨라진다.

즉, 존재 유무 확인이 매우 빠르다.


분류 결과 : 


Object 클래스의 hashCode 메소드는 이렇듯 인스턴스들을 분류하는 역할을 한다.









HashSet<E> 인스턴스에 저장할 클래스 정의 예


class Num {

private int num;

public Num(int n) { num = n; }


@Override

public String toString() { return String.valueOf(num); }


@Override

public int hashCode() {

return num % 3;                                // num의 값이 같으면 부류도 같다.

}


@Override

public boolean equals(Object obj) {            // num의 값이 같으면 true 반환

if(num==((Num)obj).num)

return true;

else

return false;

}

}








hashCode 메소드의 다양한 정의의 예


class Car {

private String model;

private String color;

. . . .


@Override

public int hashCode() {

return (model.hashCode() + color.hashCode()) / 2;

}

. . . .

}


모든 인스턴스 변수의 정보를 다 반영하여 해쉬 값을 얻으려는 노력이 깃든 문장.

결과적으로 더 세밀하게 나뉘고, 따라서 그만큼 탐색 속도가 높아진다.








해쉬 알고리즘 일일이 정의하기 조금 그렇다면...


public static int hash(Object ... values)

-> java.util.Object에 정의된 메소드, 전달된 인자 기반의 해쉬 값 반환



@Override

public int hashCode() {

return Object.hash(model, color);            // 전달인자 model, color 기반 해쉬 값 반환

}


전달된 인자를 모두 반영한 해쉬 값을 반환한다.








TreeSet<E> 클래스의 이해와 활용


Set<E> 인터페이스를 구현하는 TreeSet<E> 클래스

트리(Tree) 자료구조를 기반으로 인스턴스를 저장, 이는 정렬 상태가 유지되면서 인스턴스가 저장됨을 의미


public static void main(String[] args) {

TreeSet<Integer> tree = new TreeSet<Integer>();

tree.add(3);

tree.add(1);

tree.add(2);

tree.add(4);

System.out.println("인스턴스 수 : " + tree.size());


// for-each문에 의한 반복

for(Integer n : tree)

System.out.print(n.toString() + '\t');

System.out.println();


// Iterator 반복자에 의한 반복

for(Iterator<Integer> itr = tree.iterator(); itr.hasNext();  )

System.out.print(itr.next().toString() + '\t');

System.out.println();

}



반복자의 인스턴스 참조 순서는 오름차순을 기준으로 한다는 특징이 있다.











TreeSet 인스턴스에 저장될 것을 고려한 클래스의 예


class Person implements Comparable<Person> {

private String name;

private int age;

. . .

@Override

public int compareTo(Person p) {

return this.age - p.age;

}

}


Comparable<T> 인터페이스의 구현 결과를 근거로 저장이 이뤄지고 또 참조가 진행이 된다.


따라서 TreeSet<T>에 저장할 인스턴스들은 모두 Comparable<T> 인터페이스를 구현한 클래스의 인스턴스이어야 함. 아니면 예외 발생!!!









Comparator<T> 인터페이스 기반으로 TreeSet<E>의 정렬 기준 제시하기


public interface Comparator<T>

-> int compare(T o1, T o2)의 구현을 통해 정렬 기준을 결정할 수 있다.

      • o1이 o2보다 크면 양의 정수 반환
      • o1이 o2보다 작으면 음의 정수 반환
      • o1과 o2가 같다면 0 반환


위 인터페이스를 구현한 클래스의 인스턴스를 TreeSet<E>의 다음 생성자를 통해 전달!


public TreeSet(Comparator<? super E> comparator) 








Comparator<T> 인터페이스 기반 TreeSet<E>의 예


class Person implements Comparable<Person> {

String name;

int age;

. . .

@Override

public int compareTo(Person p) {

return this.age - p.age;

}

}


  • p1이 p2보다 크면 양의 정수 반환

  • p1이 p2보다 작으면 음의 정수 반환

  • p1과 p2가 같다면 0 반환



class PersonComparator implements Comparator<Person> {

public int compare(Person p1, Person p2) {

return p2.age - p1.age;

}

}




public static void main(String[] args) {

TreeSet<Person> tree = new TreeSet<>(new PersonComparator());

tree.add(new Person("YOON", 37));

tree.add(new Person("HONG", 53));

tree.add(new Person("PARK", 22));


for (Person p : tree)

System.out.println(p);

}



Person 클래스에 TreeSet을 위한 정렬 기준이 마련되어 있으나 

Comparator 구현 인스턴스를 전달하여 새로운 기준을 제공!









Comparator<T> 인터페이스 기반 TreeSet<E>의 예 하나 더


class StringComparator implements Comparator<String> {

public int compare(String s1, String s2) {

return s1.length() - s2.length();

}

}


public static void main(String[] args) {

TreeSet<String> tree = new TreeSet<>(new StringComparator());

tree.add("Box");

tree.add("Rabbit");

tree.add("Robot");


String 클래스의 정렬 기준은 사전 편찬순이다.

이를 길이 순으로 바꾸는 문장.


for (String s : tree)

System.out.print(s.toString() + '\t');

System.out.println();

}










중복된 인스턴스의 삭제!


public static void main(String[] args) {        중복을 허용하는 리스트

List<String> lst = Arrays.asList("Box", "Toy", "Box", "Toy");

ArrayList<String> list = new ArrayList<>(lst);


for(String s : list)

System.out.print(s.toString() + '\t');

System.out.println();


// 중복된 인스턴스를 걸러 내기 위한 작업                public HashSet(Collection<? extends E> c)

HashSet<String> set = new HashSet<>(list);                -> 다른 컬렉션 인스턴스로부터 HashSet<E> 인스턴스 생성


중복을 허용 않는 집합

// 원래대로 ArrayList<String> 인스턴스로 저장물을 옮긴다.

list = new ArrayList<>(set);


for(String s : list)

System.out.print(s.toString() + '\t');

System.out.println();

}





반응형