비교자 - Comparator 그런데 정렬을 할 때 1, 2, 3 순서가 아니라 반대로 3, 2, 1로 정렬하고 싶다면 어떻게 해야할까? 이때는 비교자( Comparator )를 사용하면 된다. 이름 그대로 두 값을 비교할 때 비교 기준을 직접 제공할 수 있다.
public interface Comparator<T> {
int compare(T o1, T o2);
}
```
두 인수를 비교해서 결과 값을 반환하면 된다.
첫 번째 인수가 더 작으면 음수, 예( -1 )
두 값이 같으면 0
첫 번째 인수가 더 크면 양수, 예( 1 )
package javaBasic2.collection.comparable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
public class SortMain {
public static void main(String[] args) {
Integer[] a = {1,5,2,12};
System.out.println("원본배열");
System.out.println(Arrays.toString(a));
Arrays.sort(a,new AscComapator());
System.out.println(Arrays.toString(a));
Arrays.sort(a,new DescComapator());
System.out.println(Arrays.toString(a));
}
static class AscComapator implements Comparator<Integer>{
public AscComapator() {
System.out.println("오름차순");
}
@Override
public int compare(Integer o1, Integer o2) {
System.out.println("o1 : "+o1+" o2 : "+o2);
return (o1<o2)?-1 : (01==02)? 0 : 1;
}
}
static class DescComapator implements Comparator<Integer>{
public DescComapator() {
System.out.println("내림차순");
}
@Override
public int compare(Integer o1, Integer o2) {
System.out.println("o1 : "+o1+" o2 : "+o2);
return ((o1<o2)?-1 : (01==02)? 0 : 1)*-1;
}
}
}
Arrays.sort() 를 사용할 때 비교자( Comparator )를 넘겨주면 알고리즘에서 어떤 값이 더 큰지 두 값을 비교할때, 비교자를 사용한다. Arrays.sort(array, new AscComparator()) Arrays.sort(array, new DescComparator())
정렬을 반대로 new AscComparator().reversed()
new DescComparator()와 같은결과가 나옴
정렬2 - Comparable , Comparator
자바가 기본으로 제공하는 Integer , String 같은 객체를 제외하고 MyUser 와 같이 직접 만든 객체를 정렬하려면 어떻게 해야 할까?
이때는 Comparable 인터페이스를 구현해보자
```java
public interface Comparable<T> {
public int compareTo(T o);
}
package javaBasic2.collection.comparable;
public class Myuser implements Comparable<Myuser>{
private String id;
private int age;
public Myuser(String id , int age) {
this.id = id;
this.age = age;
}
public int getAge() {
return this.age;
}
@Override
public int compareTo(Myuser o) {
return this.age<o.age?-1 : (this.age==o.age)? 0 :1;
}
@Override
public String toString() {
return "Myuser [id=" + id + ", age=" + age + "]";
}
}
package javaBasic2.collection.comparable;
import java.util.Arrays;
public class MyuserMain {
public static void main(String[] args) {
Myuser myuser1 = new Myuser("동곤자바", 31);
Myuser myuser2 = new Myuser("비교자", 3);
Myuser myuser3 = new Myuser("참 쉽죠", 10);
Myuser myuser4 = new Myuser("!!!", 1);
Myuser[] a = {myuser1,myuser2,myuser3,myuser4};
System.out.println("정렬 전");
System.out.println(Arrays.toString(a));
System.out.println("정렬 후 (오름차순)");
Arrays.sort(a);
System.out.println(Arrays.toString(a));
}
}
정렬 전
[Myuser [id=동곤자바, age=31], Myuser [id=비교자, age=3], Myuser [id=참 쉽죠, age=10], Myuser [id=!!!, age=1]]
정렬 후 (오름차순)
[Myuser [id=!!!, age=1], Myuser [id=비교자, age=3], Myuser [id=참 쉽죠, age=10], Myuser [id=동곤자바, age=31]]
Arrays.sort(array) 기본 정렬을 시도한다. 이때는 객체가 스스로 가지고 있는 Comparable 인터페이스를 사용해서 비교한다. MyUser 가 구현한 대로 나이( age ) 오름차순으로 정렬된 것을 확인할 수 있다.
Comparable을 구현함으로써
객체도 정렬이 가능해짐!!!
아이디를 기준으로도 정렬을만들 수 있다.
package javaBasic2.collection.comparable;
import java.util.Comparator;
public class Idcomparator implements Comparator<Myuser>{
@Override
public int compare(Myuser o1, Myuser o2) {
return o1.getId().compareTo(o2.getId());
}
}
package javaBasic2.collection.comparable;
import java.util.Arrays;
public class MyuserMain {
public static void main(String[] args) {
Myuser myuser1 = new Myuser("a", 31);
Myuser myuser2 = new Myuser("b", 3);
Myuser myuser3 = new Myuser("d", 10);
Myuser myuser4 = new Myuser("c", 1);
Myuser[] a = {myuser1,myuser2,myuser3,myuser4};
System.out.println("정렬 전");
System.out.println(Arrays.toString(a));
System.out.println("age정렬 후 (오름차순)");
Arrays.sort(a);
System.out.println(Arrays.toString(a));
System.out.println("Id정렬(Idcomparator)");
Arrays.sort(a,new Idcomparator());
System.out.println(Arrays.toString(a));
}
}
정렬 전
[Myuser [id=a, age=31], Myuser [id=b, age=3], Myuser [id=d, age=10], Myuser [id=c, age=1]]
age정렬 후 (오름차순)
[Myuser [id=c, age=1], Myuser [id=b, age=3], Myuser [id=d, age=10], Myuser [id=a, age=31]]
Id정렬(Idcomparator)
[Myuser [id=a, age=31], Myuser [id=b, age=3], Myuser [id=c, age=1], Myuser [id=d, age=10]]
Arrays.sort(array, Comparator) 기본 정렬이 아니라 정렬 방식을 지정하고 싶다면 Arrays.sort 의 인수로 비교자( Comparator )를 만들어서 넘겨 주면 된다. 이렇게 비교자를 따로 전달하면 객체가 기본으로 가지고 있는 Comparable 을 무시하고, 별도로 전달한 비 교자를 사용해서 정렬한다.
Comparable, Comparator 정리 객체의 기본 정렬 방법은 객체에 Comparable 를 구현해서 정의한다.
이렇게 하면 객체는 이름 그대로 비교할 수 있는객체가 되고
기본 정렬 방법을 가진다.
그런데 기본 정렬 외에 다른 정렬 방법을 사용해야 하는 경우 비교자( Comparator )를 별도로 구현해서 정렬 메서드에 전달하면 된다.
이 경우 전달한 Comparator 가 항상 우선권을 가진다. 자바가 제공하는 Integer , String 같은 기본 객체들은 대부분 Comparable 을 구현해 두었다.
정렬2 - Comparable , Comparator
정렬은 배열뿐만아니라 List같은 자료구조에도활용가능
package javaBasic2.collection.comparable;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
public class SortMain2 {
public static void main(String[] args) {
Myuser myuser1 = new Myuser("a", 31);
Myuser myuser2 = new Myuser("b", 3);
Myuser myuser3 = new Myuser("d", 10);
Myuser myuser4 = new Myuser("c", 1);
ArrayList<Myuser> list = new ArrayList<>();
list.add(myuser1);
list.add(myuser2);
list.add(myuser3);
list.add(myuser4);
System.out.println("원본 리스트");
System.out.println(list+"\n");
System.out.println("기본정렬(Comparable)");
list.sort(null);
System.out.println(list);
System.out.println("\nIdComparable정렬");
list.sort(new Idcomparator());
System.out.println(list);
}
}
list.sort(null) 별도의 비교자가 없으므로 Comparable 로 비교해서 정렬한다. 자연적인 순서로 비교한다.
Collections.sort(list, new IdComparator()) 별도의 비교자로 비교하고 싶다면 다음 인자에 비교자를 넘기면 된다. 하지만 이 방식보다는 객체 스스로 정렬 메서드를 가지고 있는 list.sort() 사용을 더 권장한다.
참고로 둘의결과는 같다.
list.sort(new IdComparator()) 전달한 비교자로 비교한다.
Tree구조와 정렬
이진 탐색 트리는 데이터를 저장할 때 왼쪽 노드에 저장해야 할 지, 오른쪽 노드에 저장해야 할 지 비교가 필요하다. 따라서 TreeSet , TreeMap 은 Comparable 또는 Comparator 가 필수이다.
package javaBasic2.collection.comparable;
import java.util.Set;
import java.util.TreeSet;
public class SortMain3 {
public static void main(String[] args) {
Myuser myuser1 = new Myuser("a", 31);
Myuser myuser2 = new Myuser("b", 3);
Myuser myuser3 = new Myuser("d", 10);
Myuser myuser4 = new Myuser("c", 1);
Set<Myuser> set = new TreeSet<>();
set.add(myuser1);
set.add(myuser2);
set.add(myuser3);
set.add(myuser4);
System.out.println("기본정렬(Comparable)");
System.out.println(set);
Set<Myuser> set2 = new TreeSet<>(new Idcomparator());
set2.add(myuser1);
set2.add(myuser2);
set2.add(myuser3);
set2.add(myuser4);
System.out.println("\nIdComparator정렬");
System.out.println(set2);
}
}
new TreeSet<>() TreeSet 을 생성할 때 별도의 비교자를 제공하지 않으면 객체가 구현한 Comparable 을 사용한다.
new TreeSet<>(new IdComparator()) ``` TreeSet 을 생성할 때 별도의 비교자를 제공하면 Comparable 대신
비교자( Comparator )를 사용해서 정렬한다.
컬렉션유틸
컬렉션을 편리하게다룰 수 있는 다양한기능알아보기!!
package javaBasic2.collection.utils;
import java.util.ArrayList;
import java.util.Collections;
public class CollectionSortMain {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(5);
list.add(4);
list.add(12);
list.add(87);
list.add(43);
list.add(23);
System.out.println("리스트 : "+list);
Integer max = Collections.max(list);
Integer min = Collections.min(list);
System.out.println("최솟값 : "+min);
System.out.println("최대값 : "+max);
Collections.shuffle(list);
System.out.println("shuffle : "+list);
Collections.sort(list);
System.out.println("sort : "+list);
Collections.reverse(list);
System.out.println("reverse : "+list);
}
}
리스트 : [1, 5, 4, 12, 87, 43, 23]
최솟값 : 1
최대값 : 87
shuffle : [43, 5, 12, 1, 4, 23, 87]
sort : [1, 4, 5, 12, 23, 43, 87]
reverse : [87, 43, 23, 12, 5, 4, 1]
Collections 정렬 관련 메서드 max : 정렬 기준으로 최대 값을 찾아서 반환한다. min : 정렬 기준으로 최소 값을 찾아서 반환한다. shuffle : 컬렉션을 랜덤하게 섞는다. sort : 정렬 기준으로 컬렉션을 정렬한다. reverse : 정렬 기준의 반대로 컬렉션을 정렬한다.
가변-->불변 : Collections.unmodifiablexxxx() 를 사용(xxx를 여기선 collection사용)
불변->가변 : new ArrayList<>(list);
멀티스레드 동기화
package javaBasic2.collection.utils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class SyncMain {
public static void main(String[] args) {
ArrayList<Object> list = new ArrayList<Object>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
System.out.println("!!list class!!");
System.out.println(list.getClass());
List<Object> syncList = Collections.synchronizedList(list);
System.out.println();
System.out.println("!!synchronizedList.class!!");
System.out.println(syncList.getClass());
}
}
!!list class!!
class java.util.ArrayList
!!synchronizedList.class!!
class java.util.Collections$SynchronizedRandomAccessList
Collections.synchronizedList 를 사용하면 일반 리스트를
멀티스레드 상황에서 동기화 문제가 발생하지 않는 안전한 리스트로 만들 수 있다. 동기화 작업으로 인해 일반 리스트보다 성능은 더 느리다. 이 부분은 멀티스레드를 학습해야 이해할 수 있으므로 지금은 이런 것이 있다 정도만 참고하고 넘어가자.
컬렉션 프레임워크 전체정리
Collection 인터페이스의 필요성
Collection 인터페이스는 자바 컬렉션 프레임워크의 가장 기본적인 인터페이스로, 자바에서 데이터 그룹을 다루는 데 필요한 가장 기본적인 메서드들을 정의한다.
. List , Set , Queue 와 같은 더 구체적인 컬렉션 인터페이스들은 모두 Collection 인터페이스를 확장 (extend)하여, 공통된 메서드들을 상속받고 추가적인 기능이나 특성을 제공한다.
이러한 설계는 자바 컬렉션 프레임워크의 일관성과 재사용성을 높여준다.
일관성: 모든 컬렉션 타입들이 Collection 인터페이스를 구현함으로써, 모든 컬렉션들이 기본적인 동작을 공 유한다는 것을 보장한다. 이는 개발자가 다양한 타입의 컬렉션을 다룰 때 일관된 방식으로 접근할 수 있게 해준다. 재사용성: Collection 인터페이스에 정의된 메서드들은 다양한 컬렉션 타입들에 공통으로 적용된다. 이는 코 드의 재사용성을 높이고, 유지 보수를 용이하게 한다. 확장성: 새로운 컬렉션 타입을 만들 때 Collection 인터페이스를 구현함으로써, 기존에 정의된 알고리즘과 도 구를 사용할 수 있게 된다. 이는 프레임워크의 확장성을 향상시킨다. 다형성: Collection 인터페이스를 사용함으로써, 다양한 컬렉션 타입들을 같은 타입으로 다룰 수 있다. 이는 다형성을 활용해서 유연한 코드를 작성할 수 있게 해준다.
Collection 인터페이스의 주요 메서드
Collection 인터페이스에는 다음과 같은 주요 메서드들이 포함된다. add(E e) : 컬렉션에 요소를 추가한다. remove(Object o) : 주어진 객체를 컬렉션에서 제거한다. size() : 컬렉션에 포함된 요소의 수를 반환한다. isEmpty() : 컬렉션이 비어 있는지 확인한다. contains(Object o) : 컬렉션이 특정 요소를 포함하고 있는지 확인한다. iterator() : 컬렉션의 요소에 접근하기 위한 반복자를 반환한다. clear() : 컬렉션의 모든 요소를 제거한다
Collection 은 Map 을 제외한 모든 컬렉션 타입의 부모이다. 따라서 모든 컬렉션을 받아서 유연하게 처리할 수 있 다
대표적으로 컬렉션 인터페이스는 iterator 를 제공한다. 따라서 데이터를 단순히 순회할 목적이라면 Collection 을 사용하면 모든 컬렉션 타입의 데이터를 순회할 수 있다.
인터페이스
자바 컬렉션 프레임워크의 핵심 인터페이스는 다음과 같다:
Collection: 단일 루트 인터페이스로, 모든 컬렉션 클래스가 이 인터페이스를 상속받는다. List , Set , Queue 등의 인터페이스가 여기에 포함된다.
List: 순서가 있는 컬렉션을 나타내며, 중복 요소를 허용한다. 인덱스를 통해 요소에 접근할 수 있다. 예: ArrayList , LinkedList
Set: 중복 요소를 허용하지 않는 컬렉션을 나타낸다. 특정 위치가 없기 때문에 인덱스를 통해 요소에 접근할 수 없 다. 예: HashSet , LinkedHashSet , TreeSet
Queue: 요소가 처리되기 전에 보관되는 컬렉션을 나타낸다. 예: ArrayDeque , LinkedList , PriorityQueue
Map: 키와 값 쌍으로 요소를 저장하는 객체이다. Map 은 Collection 인터페이스를 상속받지 않는다. 예: HashMap , LinkedHashMap , TreeMap
구현
자바는 각 인터페이스의 여러 구현을 제공한다: List: ArrayList 는 내부적으로 배열을 사용하며, LinkedList 는 연결 리스트를 사용한다.
Set: HashSet 은 해시 테이블을, LinkedHashSet 은 해시 테이블과 연결 리스트를, TreeSet 은 레드-블랙 트리를 사용한다.
Map: HashMap 은 해시 테이블을, LinkedHashMap 은 해시 테이블과 연결 리스트를, TreeMap 은 레드-블랙 트리를 사용한다.
Queue: LinkedList 는 연결 리스트를 사용한다. ArrayDeque 는 배열 기반의 원형 큐를 사용한다. 대부분 의 경우 ArrayDeque 가 빠르다.
선택 가이드
순서가 중요하고 중복이 허용되는 경우: List 인터페이스를 사용하자. ArrayList 가 일반적인 선택이지만, 추 가/삭제 작업이 앞쪽에서 빈번한 경우에는 LinkedList 가 성능상 더 좋은 선택이다.
중복을 허용하지 않고 순서가 중요하지 않은 경우: HashSet 을 사용하자. 순서를 유지해야 하면 LinkedHashSet 을, 정렬된 순서가 필요하면 TreeSet 을 사용하자
요소를 키-값 쌍으로 저장하려는 경우: Map 인터페이스를 사용하자. 순서가 중요하지 않다면 HashMap 을, 순서 를 유지해야 한다면 LinkedHashMap 을, 정렬된 순서가 필요하면 TreeMap 을 사용하자
요소를 처리하기 전에 보관해야 하는 경우: Queue , Deque 인터페이스를 사용하자. 스택, 큐 구조 모두 ArrayDeque 를 사용하는 것이 가장 빠르다.
실무 선택 가이드
List 의 경우 대부분 ArrayList 를 사용한다. Set 의 경우 대부분 HashSet 을 사용한다. Map 의 경우 대부분 HashMap 을 사용한다. Queue 의 경우 대부분 ArrayDeque 를 사용한다.
예제
요구사항 카드( Card )는 1 ~ 13까지있다. 각 번호당 다음 4개의 문양이 있다. ♠: 스페이드 ♥: 하트 ◆ : 다이아 ♣: 클로버 예) 1(♠), 1(♥), 1( ◆ ), 1(♣), 2(♠), 2(♥), 2( ◆ , 2(♣) ... 13(♠), 13(♥), 13( ◆ ), 13(♣) 따라서 13 * 4 = 총 52장의 카드가 있다. 52장의 카드가 있는 카드 뭉치를 덱( Deck )이라 한다.
2명의 플레이어( Player )가 게임을 진행한다.
게임을 시작하면 다음 순서를 따른다. 1. 덱에 있는 카드를 랜덤하게 섞는다.
2. 각 플레이어는 덱에서 카드를 5장씩 뽑는다
.3. 각 플레이어는 5장의 카드를 정렬된 순서대로 보여준다.
정렬 기준은 다음과 같다.작은 숫자가 먼저 나온다.
같은 숫자의 경우 ♠, ♥, ◆, ♣ 순으로 정렬한다. ♠가 가장 먼저 나온다. 예) 1(♠), 1(♥), 2( ◆ ), 3(♣) 순서로 출력된다. 4. 카드 숫자의 합계가 큰 플레이어가 승리한다. 게임을 단순화 하기 위해 숫자만 출력한다. 합계가 같으면 무승부이다. 실행 결과 예시 ``` 플레이어1의 카드: [2(♠), 7(♥), 7(♦), 8(♣), 13(♠)], 합계: 37 플레이어2의 카드: [1(♠), 1(♣), 6(♠), 9(♠), 9(♣)], 합계: 26 플레이어1 승리 ``` ``` 플레이어1의 카드: [2(♦), 3(♠), 6(♥), 10(♣), 13(♦)], 합계: 34 플레이어2의 카드: [2(♠), 4(♣), 5(♠), 11(♣), 12(♥)], 합계: 34 무승부 ``` 참고 스페이드, 하트 같은 아이콘을 직접 사용하기 어려운 경우 다음과 같이 \ (백슬래시 backslash)와 함께 다음 코드를 적 어주면 아이콘을 출력할 수 있다. "\u2660" : 스페이드(♠) "\u2665" : 하트(♥) "\u2666" : 다이아몬드( ◆ ) "\u2663" : 클로버(♣) 예) System.out.println("\u2660") 이 문제는 정해진 정답이 없다. 실행 결과 예시를 참고하되, 자유롭게 풀면 된다. CardGameMain 에 main() 메서드를 만들고 시작하자. 필요하면 클래스를 추가해도 된다.
package javaBasic2.collection.comparable.test;
public class Card implements Comparable<Card> {
private final int rank;
private final Suit suit;
public Card(int rank, Suit suit) {
this.rank = rank;
this.suit = suit;
}
public int getRank() {
return rank;
}
public Suit getSuit() {
return suit;
}
@Override
public int compareTo(Card o) {
if(this.rank != o.rank) {
return Integer.compare(this.rank, o.rank);
}
else { //카드의숫자가같다면 문양으로비교
return this.suit.compareTo(o.suit);
}
}
@Override
public String toString() {
return "Card [rank=" + rank + ", suit=" + suit + "]";
}
}
Suit 는 ENUM 타입이다.
스페이드, 하트 등의 문양의 순서는 변하지 않는다고 가정하고, ENUM의 기본 순서를 사용한다. ENUM 타입은 compareTo() 가 열거형의 순서인 ordinal 로 구현되어 있다. 그리고 ENUM의 compareTo() 메서드는 final 선언되어 있어서 재정의 할 수 없다.
package javaBasic2.collection.comparable.test;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Deck {
List<Card> card = new ArrayList<Card>();
public Deck() {
initCard();
sufflecard();
}
private void initCard() {
for (int i = 1; i <= 13; i++) {
for(Suit suit : Suit.values()) {
card.add(new Card(i, suit));//리스트에 13*4장의 카드를집어넣음(카드팩만들기)
}
}
}
private void sufflecard() {
Collections.shuffle(card);//무작위로섞기
}
public Card drawCard() {
return card.remove(0); //카드꺼내기(앞에서부터)
}
}
Deck객체를 생성하면
(13*4)장의카드가생성 + 무작위로섞음
package javaBasic2.collection.comparable.test;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
public class Player {
private String name;
private List<Card> hand;
public Player(String name) {
this.name = name;
this.hand = new ArrayList<>();
}
public String getName() {
return this.name;
}
public void drawCard(Deck deck) {
hand.add(deck.drawCard());
}
public int rankSum() {
int value=0;
for (Card card : hand) {
value+=card.getRank();
}
return value;
}
public void showHand() {
hand.sort(null);
System.out.println(name+"의 덱 :"+hand+" 점수 : "+rankSum());
}
}
drawCard : hand리스트에 deck한장(점수,이름) 을 뽑아서 넣음
rankSum : hand에 들어있는 숫자들을 모두 합해서 반환
showHand : 카드를 정렬 후 출력
package javaBasic2.collection.comparable.test;
public class CardGameMain {
public static void main(String[] args) {
Deck deck = new Deck();
Player player1 = new Player("동곤자바");
Player player2 = new Player("화이팅");
for (int i = 0; i < 5; i++) {
player1.drawCard(deck);//deck안에 셔플기능이있음
player2.drawCard(deck);
}
player1.showHand();
player2.showHand();
int winner = getWinner(player1, player2);
switch(winner) {
case -1 : System.out.println(player2.getName()+"승리");
break;
case 0 : System.out.println("동점");
break;
case 1 : System.out.println(player1.getName()+"승리");
break;
}
}
private static int getWinner(Player p1, Player p2) {
return p1.rankSum()>p2.rankSum() ? 1:
(p1.rankSum()==p2.rankSum())? 0:-1;
}
}