자바

15.5.4 Comparable 과 Comparator

TreeSet 의 객체와 TreeMap 의 키는 저장과 동시에 자동 오름차순으로 정렬되는데, 숫자(Integer, Double) 타입일 경우에는 값으로 정렬하고, 문자열(String) 타입일 경우에는 유니코드로 정렬한다. TreeSet과 TreeMap 은 정렬을 위해 java.lang.Comparable을 구현한 객체를 요구하는데, Integer, Double, String 은 모두 Comparable 인터페이스를 구현하고 있다. 사용자 저의 클래스도 Comparable 을 구현한다면 자동 정렬이 가능하다. Comparable에는 compareTo() 메소드가 정의되어 있기 때문에 사용자 정의 클래스에서는 이 메소드를 오버라이딩하여 다음과 같이 리턴값을 만들어 내야 한다.

리턴 타입 메소드 설명
int compareTo(T o)

주어진 객체와 같으면 0을 리턴

주어진 객체보다 적으면 음수를 리턴

주어진 객체보다 크면 양수를 리턴

다음은 나이를 기준으로 Person 객체를 오름차순으로 정렬하기 위해 Comparable 인터페이스를 구현한 것이다. 나이가 적을 경우는 -1을, 동일한 경우는 0을, 클 경우에는 1을 리턴하도록 compareTo() 메소드를 재정의하였다.

 

public class Person implements Comparable<Person>{

    public String name;
    public int age;
    
    public Person(String name, int age){
        this.name=name;
        this.age =age;
    }
    
    
    @Override
    public int compareTo(Person o) {
        // TODO Auto-generated method stub
        
        if(age<o.age)return -1;
        else if(age ==o.age) return 0;
        else
        return 1;
    }

}
 

public class ComparableExample {
    
    public static void main(String[] args) {
        
        TreeSet<Person> treeSet =new TreeSet<Person>();
        
        treeSet.add(new Person("홍길동", 45));
        treeSet.add(new Person("감자바", 25));
        treeSet.add(new Person("박지원", 31));
        
        Iterator<Person> iterator =treeSet.iterator();
        while(iterator.hasNext()){
            Person person =iterator.next();
            System.out.println(person.name + " : " + person.age);
        }
        
    }
    
}

 

TreeSet의 객체와 TreeMap의 키가 Comparable을 구현하고 있지 않을 경우에는 저장하는 순간 ClassCastException 이 발생한다. 그렇다면 Comparable 비구현 객체를 정렬하는 방법은 없을까? TreeSet 또는 TreeMap 생성자의 매개값으로 정렬자 (Comparator) 를 제공하면 Comparable 비구현 객체도 정렬시킬 수 있다.

 

TreeSet<E> treeSet =new TreeSet<E>(new AscendingComparator());

TreeMap<K, V> treeMap =new TreeMap<K, V> (new DescendingCompartor());

리턴타입 메소드 설명
int compare(T o1, T o2)

o1과 o2가 동등하다면 0을 리턴

o1이 o2보다 앞에 오게 하려면 음수를 리턴

o1이 o2보다 뒤에 오게 하려면 양수를 리턴

compare() 메소드는 비교하는 두 객체가 동등하면 0, 비교하는 값보다 앞에 오게 하려면 음수, 뒤에 오게 하려면 양수를 리턴하도록 구현하면 된다. 다음은 가격을 기준으로 Fruit 객체를 내림차순으로 정렬시키는 정렬자이다.

public class DescendingComparator implements Comparator<Fruit>{

    
    public int compare(Fruit o1, Fruit o2) {
        if(o1.price < o2.price) 
            return 1;
        else if(o1.price == o2.price)
            return 0;
        else return -1;
        
    };
    
}

class Fruit{
    public String name;
    public int price;
    
    public Fruit(String name, int price){
        this.name=name;
        this.price=price;
    }
    
}

 

다음 예제는 내림차순 정렬자를 이용해서 TreeSet에 Fruit을 저장한다. 정렬자를 주지 않고 TreeSet에 저장하면 ClassCastException 예외가 발생하지만, 정렬자를 TreeSet 의 생성자에 주면 예외가 발생하지 않고 자동적으로 내림차순 정렬되는 것을 볼 수 있다.

public class ComparatorExample {

    public static void main(String[] args) {
    /*    
        TreeSet<Fruit> treeSet =new TreeSet<Fruit>();
        treeSet.add(new Fruit("포도", 3000));
        treeSet.add(new Fruit("수박", 10000));
        treeSet.add(new Fruit("딸기", 6000));
        */
        
        TreeSet<Fruit> treeSet =new TreeSet<Fruit>(new DescendingComparator());
        treeSet.add(new Fruit("포도", 3000));
        treeSet.add(new Fruit("수박", 10000));
        treeSet.add(new Fruit("딸기", 6000));
        Iterator<Fruit> iterator =treeSet.iterator();
        while(iterator.hasNext()){
            Fruit fruit =iterator.next();
            System.out.println(fruit.name + " : " +fruit.price);
        }
        
    }
    
}
 

 

수박 : 10000
딸기 : 6000
포도 : 3000
 

15.6 LIFO 와 FIFO 컬렉션

후입선출(LIFO : Last In First Out) 은 나중에 넣은 객체가 먼저 빠져나가는 자료구조를 말한다. 반대로 선입선출(FIFO : Fisrt In First Out) 은 먼저 넣은 객체가 먼저 빠져나가는 구조를 말한다.

컬렉션 프레임워크에는 LIFO 자료구조를 제공하는 스택(Stack) 클래스와 FIFO 자료구조를 제공하는 큐(Queue) 인터페이스를 제공하고 있다. 다음은 스택과 큐의 구조를 보여준다.

스택(Stack)을 응용한 대표적인 예가 JVM 스택 메모리이다. 스택 메모리에 저장된 변수는 나중에 저장된 것부터 제거된다. 큐(Queue)를 응용한 대표적인 예가 스레드풀(ExecutorService)의 작업 큐이다. 작업 큐는 먼저 들어온 작업부터 처리한다.

 

15.6.1 Stack

Stack 클래스는 LIFO 자료구조를 구현한 클래스이다. 다음은 Stack 클래스의 주요 메소들이다.

리턴타입 메소드 설명
E push(E item) 주어진 객체를 스택에 넣는다.
E peek() 스택의 맨 위 객체를 가져온다. 객체를 스택에서 제거하지 않는다.
E pop() 스택의 맨 위 객체를 가져온다. 객체를 스택에서 제거한다.

 

Stack 객체를 생성하기 위해서는 저장할 객체 타입을 파라미터로 표기하고 기본 생성자를 호출하면 된다.

 

Stack<E> stack =new Stack<E>();

 

다음은 택시에서 많이 볼 수 있는 동전케이스를 Stack 클래스로 구현한 예제이다.

동전케이스는 위에만 오픈되어 있는 스택 구조를 가지고 있다. 먼저 넣은 동전은 제일 밑에 깔리고 나중에 넣은 동전이 위에 쌓이기 때문에 Stack에서 동전을 빼면 마지막에 넣은 동전이 먼저 나온다.

 

public class Coin {

    private int value;
    
    public Coin(int value){
        this.value =value;
    }
    
    public int getValue(){
        return value;
    }
    
}

 

public class StackExample {

    public static void main(String[] args) {
        
        Stack<Coin> coinBox =new Stack<Coin>();
        
        coinBox.push(new Coin(100));
        coinBox.push(new Coin(50));
        coinBox.push(new Coin(500));
        coinBox.push(new Coin(10));
        
        while(!coinBox.isEmpty()){
            Coin coin =coinBox.pop();
            System.out.println("꺼내온 동전 :" + coin.getValue() + "원");
        }
    }    
    
}

 

꺼내온 동전 :10원
꺼내온 동전 :500원
꺼내온 동전 :50원
꺼내온 동전 :100원

 

15.6.2 Queue

Queue 인터페이스 FIFO 자료구조에서 사용되는 메소드를 정의하고 있다. 다음은 Queue 인터페이스에 정의되어 있는 메소드를 보여준다.

리턴타입 메소드 설명
boolean offer(E e) 주어진 객체를 넣는다.
E peek() 객체 하나를 가져온다. 객체를 큐에서 제거하지 않는다.
E poll() 객체 하나를 가져온다. 객체를 큐에서 제거한다.

 

Queue 인터페이스를 구현한 대표적인 클래스는 LinkedList 이다. LinkedList 는 List 인터페이스를 구현했기 때문에 List 컬렉션이기도 하다.

다음 코드는 LinkedList 객체를 Queue 인터페이스 타입으로 변환한 것이다.

 

Queue<E> queue =new LinkedList<E>();

다음은 Queue를 이용해서 간단한 메시지 큐를 구현한 예제이다. 먼저 넣은 메시지가 반대쪽으로 먼저 나오기 때문에 넣은 순서대로 메시지가 처리된다.


public class Message {

    public String command;
    public String to;
    
    public Message(String command, String to){
        
        this.command=command;
        this.to=to;
    }
    
}

 

public class QueueExample {

    public static void main(String[] args) {
        
        Queue<Message> messageQueue =new LinkedList<Message>();
        
        messageQueue.offer(new Message("sendMail", "홍길동"));
        messageQueue.offer(new Message("sendSMS", "최준호"));
        messageQueue.offer(new Message("sendKakaotalk", "홍두께"));
        
        while(!messageQueue.isEmpty()){
            Message message =messageQueue.poll();
            switch (message.command) {
                case "sendMail":
                    System.out.println(message.to +"님에게 메일을 보냅니다.");
                    break;
                    
                
                
                case "sendSMS":
                    System.out.println(message.to + "님에게 SMS 보냅니다.");
                    break;
                    
                case "sendKakaotalk":
                    System.out.println(message.to + "님에게 카카오톡을 보냅니다.");
                    break;
                default:
                    break;
            }
        }
        
        
    }
    
    
}
홍길동님에게 메일을 보냅니다.
최준호님에게 SMS 보냅니다.
홍두께님에게 카카오톡을 보냅니다.
 

 

15.7 동기화된 컬렉션

컬렉션 프레임워크의 대부분의 클래스들은 싱글 스레드 환경에서 사용할 수 있도록 설계되었다. 그렇기 때문에 여러 스레드가 동시에 컬렉션에 접근한다면 의도하지 않게 요소가 변경될 수 있는 불안전한 상태가 된다. Vector 와 Hashtable 은 동기화된(synchronized) 메소드로 구성되어 있기 때문에 멀티 스레드 환경에서 안전하게 요소를 처리할 수 있지만, ArrayList와 HashSet, HashMap 은 동기화된 메소드로 구성되어 있지 않아 멀티 스레드 환경에서 안전하지 않다.

경우에 따라서 ArrayList, HashSet, HashMap 을 싱글 스레드 환경에서 사용하다가 멀티 스레드 환경으로 전달할 필요도 있을 것이다. 이런 경우을 대비해서 컬렉션 프레임워크는 비동기화된 메소드를 동기화된 메소드로 래핑하는 Collections의 synchroinizedXXX() 메소드를 제공하고 있다. 매개값으로 비동기화된 컬렉션을 대입하면 동기화된 컬렉션을 리턴하다.

리턴타입 메소드(매개변수) 설명
List<T> syschronizedList(List<T> list) List 를 동기화된 List로 리턴
Map<k,v> synchronizedMap(Map<K, V> m) Map을 동기화된 Map으로 리턴
Set<T> sysnchronizedSet(Set<T>s) Set을 동기화된 S

 

다음 코드는 ArrayList 를 Collections.sysnchronizedList() 메소드를 사용해서 동기화된 List로 변환한다.

List<T> list =Collections.synchronizedList(new ArrayList<T>());

다음 코드는 HashSet을 Collections.synchronizedSet() 메소드를 사용해서 동기화된 Set 으로 변환한다.

Set<E> set =Collections.synchronizedSet(new HashSet<E>());

다음 코드는 HashMap 을 Collections.synchronizedMap() 메소드를 사용해서 동기화된 Map 으로 변환한다.

Map<K, V> map =Collections.synchronizedMap(new HashMap<K, V>());

 

 

15.8 병렬 처리를 위한 컬렉션

동기화된(synchronized) 컬렉션은 멀티 스레드 환경에서 하나의 스레드가 요소를 안전하게 처리 하도록 도와주지만, 전체 요소를 빠르게 처리하지는 못한다. 하나의 스레드가 요소를 처리할 때 전체 잠금이 발생하여 다른 스레드는 대기 상태가 된다. 그렇게 때문에 멀티 스레드가 병렬적으로 컬렉션의 요소들을 처리할 수 없다. 자바는 멀티 스레드가 컬렉션의 요소를 병렬적으로 처리할 수 있도록 특별한 컬렉션을 제공하고 있다.

java.util.concurrent 패키지의 ConcurrentHashMap 과 ConcurrentLinkedQueue 이다. ConcurrentHashMap 은 Map 구현 클래스이고, ConcurrentLinedQueue 는 Queue 구현 클래스이다.

Map<k, v> map =new ConcurrentHashMap<K, V>();

ConcurrentLinkedQueue 는 락-프리(lock-free) 알고리즘을 구현한 컬렉션이다. 락-프리 알고리즘은 여러 개의 스레드가 동시에 접근할 경우, 잠금을 사용하지 않고도 최소한 하나의 스레드가 안전하게 요소를 저장하거나 얻도록 해준다. 다음은 ConcurrentLinkedQueue 를 생성하는 코드이다.

 

Queue<E> queue =new ConcurrentLinedQueue<E>();

 

사용하는 방법은 다른 Queue 구현 객체와 마찬가지로 Queue 인터페이스의 메소드를 호출하면 된다.

 

 

 

 

 

 

 

 

java

 

about author

PHRASE

Level 60  머나먼나라

학식도 미덕도 건강이 없으면 퇴색한다. -몽테뉴

댓글 ( 4)

댓글 남기기

작성

자바 목록    more