본문 바로가기

Java

Set에서 Sort를 하기위한 Comparable구현 그리고 InnerClass의 사용


마땅히 제목이 떠오르는게 없네요...^^;
오늘 코딩을 하다가 설계에 대해 모르는 것을 풍대리님께 물어물어 가다가
알게 된 사실...^^;

집에 돌아와서 다시 기억을 더듬어서 코딩을 해보고 이렇게 정리를해봅니다.

1. Set collection과 Comparable 인터페이스.

Set collection에 add로써 추가되는 객체는 Comparable 인터페이스를 구현하고 있어야합니다.
이것을 기본으로하여 Set collection에 추가되는 객체들은 순서대로 정렬되게 됩니다.
물론, 정렬순서는 각 객체에서 구현하고 있는 compareTo(Object o) 에 따라 달라질겁니다.

public class Test3 {
 public static void main(String[] args) {
  Set a = new TreeSet();
  
  a.add("aaaaaaaaaaaaaaaaa");
  a.add("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
  a.add("A");
  a.add("BBBBBBBBB");
  a.add("11111111111");
  
  Iterator iter = a.iterator();
  for(;iter.hasNext();){
   System.out.println(iter.next());
  }
 }
}

11111111111
A
BBBBBBBBB
aaaaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb

String은 기본적으로 Comparable 인터페이스를 구현하고 있기 때문에 위와같은 결과가 나오게 됩니다.

만약, Comparable 인터페이스를 구현하지 않았다면, 문제가 발생할겁니다.
String의 기본적인 구현이 위와 같은 방식인데 저는 문자의 길이순으로 (긴것부터 차례로..) 정렬되는 Set이 필요했습니다. 그래서, 만들어낸 것이 아래의 클래스였습니다.

2. Comparable 인터페이스를 구현하자.


public class StringLength implements Comparable {
 String str;
 
 public StringLength(String str) {
  this.str = str;
 }
 public String getStr() {
  return this.str;
 }
 
 @Override
 public int compareTo(Object o) {
  String m = ((StringLength)o).getStr();
  int targetLength = m.length;
  
  return str.length() == targetLength
    ? str.compareTo(((StringLength)obj).str)
    : (str.length() < targetLength ? 1 : -1); 

}
 
 public String toString() {
  return this.str;
 }
}
이것을 테스트하는 소스는 아래와 같습니다.
public class Test {
 public static void main(String[] args) {
  Set a = new TreeSet();
  
  a.add(new StringLength("aaaaaaaaaaaaaaaaa"));
  a.add(new StringLength("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"));
  a.add(new StringLength("A"));
  a.add(new StringLength("BBBBBBBBB"));
  a.add(new StringLength("11111111111"));
  
  Iterator iter = a.iterator();
  for(;iter.hasNext();){
   System.out.println(iter.next());
  }
 }
}
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
aaaaaaaaaaaaaaaaa
11111111111
BBBBBBBBB
A
새로운 클래스가 하나 생긴 것이 찝찝하긴 했지만 아쉬운대로 사용하고 있었습니다. 하지만 정말 불편했죠..
단지 저 정렬순서를 위해서 StringLength라는 클래스를 만들었지만, 이 녀석은 String의 좋은 기능들을 쓰기에는
많이 불편했죠. 더군다나 이것은 불필요하게 메모리를 더 소모하게 됩니다. 그러다가, 풍대리님이 가르쳐주신 방법이 하나 있었습니다.

3. InnerClass를 활용해보자.
TreeSet 클래스를 보면
public TreeSet(Comparator<? super E> comparator) {
...
}
이런 생성자가 있습니다.

설명을 보면, Constructs a new, empty tree set, sorted according to the specified comparator.
@param comparator the comparator that will be used to order this set.
라고 되어있습니다. 여기서 Compartor를 구현해주면 그 구현된 로직에 따라서 정렬이 될 것 같습니다.

아래의 코드가 바로 InnerClass를 사용한 것입니다.
public class Test2 {
 public static void main(String[] args) {
  Set<String> a = new TreeSet<String>(new Comparator() {
   @Override
   public int compare(Object o1, Object o2) {
    String m = (String) o1;
    String t = (String) o2;
    
     return o1.length() == t.length() ? m.compareTo(t) :
                                     (m.length() > t.length() ? -1 : 1);
   }
   
  }
);
  
  a.add("aaaaaaaaaaaaaaaaa");
  a.add("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
  a.add("A");
  a.add("BBBBBBBBB");
  
  Iterator iter = a.iterator();
  for(;iter.hasNext();){
   System.out.println(iter.next());
  }
 }
}

bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
aaaaaaaaaaaaaaaaa
11111111111
BBBBBBBBB
A


한가지 이상한 부분이 보이실 것입니다...
Comparator는 인터페이스인데 객체를 생성 할 수 있다?
저도 이부분이 이상했습니다. 실제로 우리가 객체를 생성에서 참조변수에 객체의 메모리 레퍼런스를 할당해주는
형식을 보면..

Interface a = new InterfaceImpl(); 이런식입니다.

좌변은 인터페이스 우변은 구현체죠.

위 InnerClass를 사용한부분에서 compare를 Override를 해주었기 때문에 구현체로써의 역할을 할 수 있는 것 이라고 합니다.

그렇다고 한다면 이런 것도 가능하겠지요?

Comparator b = new Comparator();

이것은 당연히 컴파일에러를 발생 시킬 것입니다.
이런식이라면 가능하겠죠?

  Comparator b = new Comparator() {

   @Override
   public int compare(Object o1, Object o2) {
    ....
    return 0;
   }
  };


이제 마지막 의문점이 하나 더 있었습니다.

Comparator 인터페이스를 보면 구현되지 않은 메서드가 하나 더 있습니다.

 
boolean equals(Object obj);


그런데 위에서는 위 equals(Object obj) 메서드는 구현하지 않았습니다. 그런데도 객체로써 생성이 되고 있지요.

자바에서 모든 클래스는 Object의 하위 클래스들입니다.

이부분은 아마 이것과 관련이 있을 것 같습니다. Object의 equals 메서드가 대신 구현하고 있는 것이지요.


중요 !!

작성했던 소스에 버그가 있어서 내용을 수정하였습니다. ^^;;

기존 소스대로라면, Set에서 길이가 같으면 String의 내용이 다르더라도 같은 객체로 판단해서
Set에 add를 시키지 않는 듯 합니다.

예전 소스에서 작성했던 campare가

public int compare(Object o1, Object o2) {

    if ( ((String)o1).length() < ((String)o2).length() ) {
     return 1;
    }

    if ( ((String)o1).length() > ((String)o2).length() ) {
     return -1;
    }

    return 0;
   }



이런 식이었는데 위 테스트에서 길이는 같고 문자가 다른 녀석을 add해보면 추가가 안되는 것을 아실 수 있을겁니다.
(아.. 오늘 이거 가지고 삽질을..어흑..)

그래서 아래와 같이
길이가 같을 경우에는 String에 구현되어있는 compareTo 메서드를 이용해서 String의 본래 sort순서를 따르도록
수정하였습니다. 참고해주세요 ^^

     return o1.length() == t.length() ? m.compareTo(t) :
                                     (m.length() > t.length() ? -1 : 1);