Programing/JAVA

JAVA Collection Framework 정리, 자바 컬렉션, List, Set, Map, Queue, Stream 2부 Set

리커니 2018. 11. 13.
반응형

JAVA Collection Framework 정리, 자바 컬렉션, List, Set, Map, Queue, Stream 2부 Set

 

1부에서는 List에 대해서 알아보았습니다.

 

Link : JAVA Collection Framework 정리, 자바 컬렉션, List, Set, Map, Queue Stream 1부 List

 

Collection 정리 2부에서는 Set 에 대해서 다루도록 하겠습니다.

 

List와 다르게 Set은 저장순서를 유지하는 Index를 갖지 않기 때문에 for문을 사용할 수 없고,

중복 데이터를 허용하지 않는다는 특징이 있습니다.

 

1) HashSet

 

그렇기 때문에 Java 에서는 Iterator 메소드를 제공해서 Set 내의 데이터에 접근 할 수 있도록 했습니다.

아래 코드를 보겠습니다. (Iterator Pattern)

Set<String> hSet = new HashSet<String>();
hSet.add("Java");
hSet.add("Javascript");
hSet.add("C");
hSet.add("C++");
hSet.add("Javascript");
System.out.println(hSet.size());
     
for(Iterator<String> itr = hSet.iterator(); itr.hasNext();){
    System.out.println(itr.next());
}

 

[출력결과]

 

4
Java
C++
C
JavaScript

 

출력결과를 보시면 Javascript가 중복으로 add 되었지만 중복을 제거하고 출력되는 것을 보실 수 있습니다.

그럼 다시 비슷한 아래의 코드를 보시죠.

 

Set<String> hSet = new HashSet<String>();
        
String js = "Javascript";
String js2 = new String("Javascript");
        
System.out.println(js==js2);
        
hSet.add("Java");
hSet.add(js);
hSet.add("C");
hSet.add("C++");
hSet.add(js2);
System.out.println(hSet.size());
        
for(Iterator<String> itr = hSet.iterator(); itr.hasNext();){
    System.out.println(itr.next());
}

 

기본적으로 String은 인스턴스 변수라고 알고 있습니다. 주소값이 저장되는 변수이죠.

리터럴을 사용한 String변수('='으로 초기화)는 String Liberal Pool 영역에 같은 문자열이 있는 지 검색 후 있으면 기존 개체의 주소값을, 없다면 새로 저장되고 그 주소값을 가지게 되죠.

 

하지만 new를 사용한 String 변수는 리터럴 방식과 같은 과정을 거치지만, Heap 영역에 객체를 생성하고 그 객체가 리터럴 방식과 같은 주소 값을, 그리고 인스턴스 변수인 js2는 Heap 영역의 객체의 주소값을 가지게 됩니다.

 

그렇기 떄문에 6Line의 결과는 false가 되죠.

그렇다는 것은 js와 js2의 메모리 주소 값이 다르다는 결론인데, 위의 코드 출력결과는 어떨까요?

 

[출력결과]

 

false
4
Java
C++
C
Javascript

 

중복이 제거되어 출력되게 됩니다.

이유는 Hash 알고리즘에 있습니다.

HashSet, HashMap, HashTable 과 같은 Hash 알고리즘을 사용하는 Collection들은 객체의 동등성 비교를 위해 HashCode와 equals 메소드를 호출합니다. 그래서 두 객체의 결과가 같다면 같은 객체로 판단을 하게 되죠.

 

Set<String> hSet = new HashSet<String>();
        
String js = "Javascript";
String js2 = new String("Javascript");
        
System.out.println(js.hashCode());
System.out.println(js.hashCode());
        
System.out.println(js==js2);
System.out.println(js.equals(js2));
        
hSet.add("Java");
hSet.add(js);
hSet.add("C");
hSet.add("C++");
hSet.add(js2);
System.out.println(hSet.size());
        
for(Iterator<String> itr = hSet.iterator(); itr.hasNext();){
    System.out.println(itr.next());
}

 

[출력결과]

 

-2112506483
-2112506483
false
true
4
Java
C++
C
Javascript

 

set 의 add 메소드 전에 HashCode 메소드가 실행되어 같은 값인지 확인을 하고 같다고 판단하면 중복으로 판단합니다. 그렇기 때문에 중복된 데이터가 출력되지 않는 것이죠.

 

그렇다면 여기서 한 발자국 더 나아가 생각해 볼 수 있는 것은

중복을 판단하는 기준인 HashCode 와 equals 메소드를 Override 한다면 개발자가 중복의 기준을 정할 수 있겠구나.

하는 것 입니다.

 

여기서 주의 해야 할 점이 있습니다.

equals와 hashCode 메소드는 항상 같이 오버라이딩 해야 정확한 동등비교가 가능하다는 것입니다.

비교하고자 하는 두 인스턴스의 hashcode 값이 같다고 반드시 같은 인스턴스는 아닐 수 있으며,

hashcode는 기본적으로 heap에 생성된 모든 인스턴스들에 대해 다른값을 리턴하기 때문에,

오버라이딩 시에는 같은 인스턴스가 같은 객체라는 결론이 나오도록 오버라이딩 해야 합니다.

 

 

 

2) TreeSet

 

TreeSet은 정렬이 필요한 Set이 필요한 로직에 사용합니다.

기본 TreeSet은 오름차순으로 정렬되며, 정렬의 기준은 개발자가 정의 할 수 있습니다.

기존 코드를 TreeSet으로만 수정해 보도록 하죠.

 

Set<String> hSet = new TreeSet<String>();
        
hSet.add("Java");
hSet.add("Javascript");
hSet.add("C");
hSet.add("C++");
hSet.add(new String("Javascript"));
System.out.println(hSet.size());
        
for(Iterator<String> itr = hSet.iterator(); itr.hasNext();){
    System.out.println(itr.next());
}

 

[출력결과]

 

4
C
C++
Java
Javascript

 

여기에서 볼 수 있는 것은 Collection 클래스에서 상속관계로 연결된 List, Set, Queue들은 같은 메소드를 사용하기 때문에 용도에 따라 선언부만 변경하면 다른 성격을 가진 코드가 된다는 것 입니다.!

 

그럼 위의 코드를 변경해 문자열의 길이가 긴 것부터 짧은 순으로 출력이 되도록 변경을 해보도록 하겠습니다.

이를 위해서는 Comparable interface를 implements 한 후 compareTo 메소드를 override 하여 구현을 하여야 합니다.

 

Comparable interface를 implements 하는 class를 생성합니다.

 

class SortTest implements Comparable<SortTest>{
    private String language;
        
    public SortTest(String language) {
        super();
        this.language = language;
    }

    @Override
    public String toString() {
        return "SortTest [language=" + language + "]";
    }

    @Override
    public int compareTo(SortTest st1) {
        if(language.length()>st1.language.length()){
            return -1;
        }else if(language.length()<st1.language.length()){
            return 1;    
        }else{
            return 0;
        }
    }
}

 

그리고 기존 코드를 약간 수정 합니다. 기존의 String 대신 Class로 수정합니다.

 

Set<SortTest> hSet = new TreeSet<SortTest>();
        
hSet.add(new SortTest("Java"));
hSet.add(new SortTest("Javascript"));
hSet.add(new SortTest("C"));
hSet.add(new SortTest("C++"));
System.out.println(hSet.size());
        
for(Iterator<SortTest> itr = hSet.iterator(); itr.hasNext();){
    System.out.println(itr.next().toString());
}

 

[출력결과]

 

4
SortTest [language=Javascript]
SortTest [language=Java]
SortTest [language=C++]
SortTest [language=C]


 

이 처럼 개발가 원하는 정렬 기준에 따라 출력을 할 수 있습니다.

반응형

댓글

💲 추천 글