일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- 자바의 정석 기초편 ch12
- 스프링 db1 - 스프링과 문제 해결
- 스프링 mvc2 - 타임리프
- 2024 정보처리기사 수제비 실기
- 스프링 입문(무료)
- 자바의 정석 기초편 ch7
- 자바의 정석 기초편 ch11
- jpa - 객체지향 쿼리 언어
- 2024 정보처리기사 시나공 필기
- 스프링 mvc1 - 서블릿
- 코드로 시작하는 자바 첫걸음
- 자바의 정석 기초편 ch4
- 스프링 mvc2 - 로그인 처리
- 스프링 고급 - 스프링 aop
- 자바의 정석 기초편 ch6
- 스프링 db2 - 데이터 접근 기술
- 자바의 정석 기초편 ch2
- 스프링 mvc1 - 스프링 mvc
- 스프링 mvc2 - 검증
- 자바의 정석 기초편 ch1
- @Aspect
- 자바 기본편 - 다형성
- 자바의 정석 기초편 ch14
- 자바의 정석 기초편 ch13
- 자바의 정석 기초편 ch9
- 자바 중급2편 - 컬렉션 프레임워크
- 자바의 정석 기초편 ch5
- 게시글 목록 api
- jpa 활용2 - api 개발 고급
- 자바 중급1편 - 날짜와 시간
- Today
- Total
나구리의 개발공부기록
컬렉션 프레임워크 - 순회, 정렬, 전체 정리, 순회(직접 구현하는 Iterable과 Iterator, 향상된 for문, 자바가 제공하는 Iterable과 Iterator), 정렬 - Comparable, Comparator, 컬렉션 유틸, 컬렉션 프레임워크 전체 정리 본문
컬렉션 프레임워크 - 순회, 정렬, 전체 정리, 순회(직접 구현하는 Iterable과 Iterator, 향상된 for문, 자바가 제공하는 Iterable과 Iterator), 정렬 - Comparable, Comparator, 컬렉션 유틸, 컬렉션 프레임워크 전체 정리
소소한나구리 2025. 2. 9. 13:01출처 : 인프런 - 김영한의 실전 자바 - 중급2편 (유료) / 김영한님
유료 강의이므로 정리에 초점을 두고 코드는 일부만 인용
1. 순회
1) 직접 구현하는 Iterable, Iterator
(1) 순회
- 순회라는 단어는 여러 곳을 돌아다닌다는 뜻인데, 자료 구조에서의 순회는 자료 구조에 들어있는 데이터를 차례대로 접근해서 처리하는 것을 뜻함
- 다양한 자료 구조는 각각의 자료 구조마다 데이터를 접근하는 방법이 모두 다름
- 배열 리스트는 index를 0부터 size까지 차례로 증가하면서 순회해야 하고, 연결 리스트는 Node.next를 사용해서 node의 끝이 null일 때 까지 순회해야 하는 것처럼 각 자료 구조의 순회 방법이 다름
- 그렇기 때문에 여러 자료구조를 다루기 위해서는 각 자료 구조의 순회 방법을 배워야하고, 순회 방법을 배우려면 자료 구조의 내부 구조도 알아야하기에 너무 많은 내용을 알아야 함
- 자료 구조를 사용하는 개발자 입장에서 보면 단순히 자료 구조에 들어있는 모든 데이터에 순서대로 접근해서 출력하거나 계산하고 싶을 뿐임
- 그러므로 자료 구조의 구현과 관계 없이 모든 자료 구조를 동일한 방법으로 순회할 수 있는 일관성 있는 방법이 있다면 자료 구조를 사용하는 개발자는 매우 편리해짐
- 자바는 이런 문제를 해결하기 위해서 Iterable과 Iterator 인터페이스를 제공함
(2) Iterable 인터페이스
- '반복 가능한'이라는 뜻으로 단순히 Iterator를 반환함
public interface Iterable<T> {
Iterator<T> iterator();
}
(3) Iterator 인터페이스
- '반복자'라는 뜻
- hasNext(): 다음 요소가 있는지 확인하고 다음 요소가 없으면 false를 반환함
- next(): 다음 요소를 반환, 내부에 있는 위치를 다음으로 이동 시킴
- 자료 구조에 들어있는 데이터를 처음부터 끝까지 순회하는 방법은 자료 구조에 다음 요소가 있는지 물어보고 있으면 다음 요소를 꺼내는 과정을 반복하면되고, 만약 다음 요소가 없다면 종료하면 됨
public interface Iterator<E> {
boolean hasNext();
E next();
}
(4-1) MyArrayIterator
- 생성자를 통해 반복자가 사용할 배열을 참조하고, 여기서 참조한 배열을 순회함
- currentIndex: 현재 인덱스, next()를 호출할 때마다 하나씩 증가함
- hasNext(): 다음 항목이 있는지 검사하고 배열의 끝에 다다르면 순회가 끝났으므로 false를 반환함
- next(): 다음 항목을 반환함
- currentIndex를 하나 증가하고 항목을 반환함
- currentIndex의 값을 -1로 해두면 처음 next()를 호출할 때 currentIndex가 0이되어 0번 인덱스를 가리키게 됨
- Iterator는 단독으로 사용할 수 없기 때문에 Iterator를 통해 순회의 대상이 되는 자료 구조가 필요함
package collection.iterable;
public class MyArrayIterator implements Iterator<Integer> {
private int currentIndex = -1;
private int[] targetArr;
public MyArrayIterator(int[] targetArr) {
this.targetArr = targetArr;
}
@Override
public boolean hasNext() {
return currentIndex < targetArr.length - 1;
}
@Override
public Integer next() {
return targetArr[++currentIndex];
}
}
(4-2) MyArray
- 배열을 가지는 매우 단순한 자료구조로 Iterable 인터페이스를 구현하고 있음
- Iterable 인터페이스는 자료 구조에 사용할 반복자(Iterator)를 반환하면 되는데, 여기서는 앞서 만든 MyArrayIterator를 반환하고 이때 MyArrayIterator는 생성자를 통해 내부 배열인 numbers를 참조함
package collection.iterable;
public class MyArray implements Iterable<Integer> {
private int[] numbers;
public MyArray(int[] numbers) {
this.numbers = numbers;
}
@Override
public Iterator<Integer> iterator() {
return new MyArrayIterator(numbers);
}
}
(4-3) MyArrayMain
- MyArray는 Iterable인터페이스를 구현하고 있는데 iterator()메서드를 통해 iterator를 반환할 수 있음, 즉 Iterable 인터페이스를 구현하면 반복할 수 있다는 뜻이고, 반복자를 반환할 수 있다는 것임
- 반환된 반복자의 hasNext()메서드를 반복해서 실행하면 어떤 자료구조든 순회할 수 있으며 여기에서는 직접 구현한 MyArray가 출력되는 것을 확인할 수 있음
package collection.iterable;
public class MyArrayMain {
public static void main(String[] args) {
MyArray myArray = new MyArray(new int[]{1, 2, 3, 4});
Iterator<Integer> iterator = myArray.iterator();
System.out.println("iterator 사용");
while (iterator.hasNext()) {
Integer value = iterator.next();
System.out.println("value = " + value);
}
}
}
/* 실행 결과
iterator 사용
value = 1
value = 2
value = 3
value = 4
*/
(5) 클래스 구조도
- MyArray는 Iterable 인터페이스를 구현하므로 MyArray는 반복할 수 있다는 의미가 됨
- Iterable 인터페이스를 구현하면 iterator()메서드를 구현해야하고 이 메서드는 Iterator 인터페이스를 구현한 반복자를 반환하며 여기서는 MyArrayIterator를 생성해서 반환함
(6) 런타임 메모리 구조도
- MyArrayIterator의 인스턴스를 생성할 때 순회할 대상을 지정해야하는데 여기서는 MyArray의 배열을 지정함
- MyArrayIterator 인스턴스는 내부에서 MyArray의 배열을 참조하기 때문에 MyArray가 가진 내부 데이터를 순회할 수 있음
(7) MyArrayIterator 순회 작동 방식
- 처음 currentIndex는 = -1이고 반복문을 통해 hasNext()가 처음 호출 되면 currentIndex가 0이되고 0번 인덱스의 값을 반환함
- 다음 hasNext()를 호출하면 currentIndex가 1로 증가하여 1번 인덱스의 값을 반환하고 이를 계속 반복하여 참조하는 배열의 마지막 인덱스의 값까지 반환함
- 마지막 인덱스에서 hasNext()를 호출하면 currentIndex(3) < length(4) -1에 의해서 종료가 됨
2) Iterable과 향상된 for문(Enhanced For Loop)
(1) MyArrayMain - 추가
- 자바는 Iterable 인터페이스를 구현한 객체에 대해서 향상된 for문을 사용할 수 있게 해줌
- 향상된 for문: for-each문으로 불리며, 자료 구조를 순회하는 것이 목적임
- 사실상 위에서 사용한 while문으로 iterator.hasNext()를 반복해서 사용하는 코드와 아래 예제의 향상된 for문은 같은 코드임
- 모든 데이터를 순회한다면 코드가 더 깔끔한 향상된 for문을 사용하는 것이 좋음
package collection.iterable;
public class MyArrayMain {
public static void main(String[] args) {
// ... 기존 코드 생략
System.out.println("for-each 사용");
for (Integer value : myArray) {
System.out.println("value = " + value);
}
}
}
(2) 정리
- 특정 자료 구조가 Iterable, Iterator를 구현한다면 해당 자료 구조를 사용하는 개발자는 단순히 hasNext(), next() 혹은 for-each문을 사용해서 순회할 수 있음
- 자료 구조가 아무리 복잡해도 해당 자료 구조를 사용하는 개발자는 동일한 방법으로 매우 쉽게 자료 구조를 순회할 수 있으며 이것이 인터페이스가 주는 큰 장점임
- 물론 자료 구조를 만드는 개발자 입장에서는 Iterable, Iterator를 구현해야 하니 수고롭겠지만 사용하는 입장에서는 매우 편리함
3) 자바가 제공하는 Iterable, Iterator
(1) 대표적인 컬렉션 프레임워크의 구조
- 자바 컬렉션 프레임워크는 다양한 자료 구조를 제공하는데 이것을 사용하는 개발자가 편리하고 일관된 방법으로 자료 구조를 순회할 수 있도록 Iterable 인터페이스를 제공하고 각각의 구현체에 맞는 Iterator도 다 구현해 두었음
- 자바 Collection 인터페이스의 상위에 Iterable이 있다는 것은 모든 컬렉션을 Iterable과 Iterator를 사용해서 순회할 수 있다는 뜻임
- Map의 경우 Key뿐만 아니라 Value까지 있기 때문에 바로 순회를 할 수가 없음
- 대신 keySet(), values()를 호출하면 Set, Collection을 반환하기 때문에 Key나 Value를 정해서 순회할 수 있으며 Entry를 Set 구조로 반환하는 entrySet()도 순회가 가능함
- 정리하면 자바가 제공하는 컬렉션 프레임워크의 모든 자료 구조는 Iterable과 Iterator를 사용해서 편리하고 일관된 방법으로 순회할 수 있으며 Iterable을 구현하기 때문에 향상된 for문도 사용할 수 있음(자바의 배열도 향상된 for문이 가능함)
(2) JavaIterableMain
- Iterator, Iterable은 인터페이스이기 때문에 다형성을 적극 활용할 수 있음
- printAll(), foreach() 메서드는 새로운 자료 구조가 추가되어도 해당 자료 구조가 Iterator, Iterable만 구현하고 있다면 코드의 변경 없이 사용할 수 있음
- java.util.ArrayList$Itr: ArrayList의 Iterator는 ArrayList의 중첩 클래스임
- java.util.HashMap$KeyIterator: HashSet 자료 구조는 내부에서 HashMap 자료 구조를 사용함, HashMap 자료 구조에서 Value를 사용하지 않으면 HashSet임
package collection.iterable;
public class JavaIterableMain {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
Set<Integer> set = new HashSet<>();
set.add(1);
set.add(2);
set.add(3);
printAll(list.iterator());
printAll(set.iterator());
foreach(list);
foreach(set);
}
private static void printAll(Iterator<Integer> iterator) {
System.out.println("iterator.getClass() = " + iterator.getClass());
while (iterator.hasNext()) {
System.out.print(iterator.next() + " ");
}
System.out.println();
}
private static void foreach(Iterable<Integer> iterable) {
System.out.println("iterator.getClass() = " + iterable.getClass());
for (Integer i : iterable) {
System.out.print(i + " ");
}
System.out.println();
}
}
/* 실행 결과
iterator.getClass() = class java.util.ArrayList$Itr
1 2 3
iterator.getClass() = class java.util.HashMap$KeyIterator
1 2 3
iterator.getClass() = class java.util.ArrayList
1 2 3
iterator.getClass() = class java.util.HashSet
1 2 3
*/
** 참고 - Iterator (반복자) 디자인 패턴
- Iterator 패턴은 객체 지향 프로그래밍에서 컬렉션의 요소들을 순회할 때 사용되는 디자인 패턴임
- 이 패턴은 컬렉션의 내부 표현 방식을 노출시키지 않으면서도 그 안의 요소에 순차적으로 접근할 수 있게 해줌
- Iterator 패턴은 컬렉션은 구현과는 독립적으로 요소들을 탐색할 수 있는 방법을 제공하므로 코드의 복잡성을 줄이고 재사용성을 높일 수 있음
2. 정렬 - Comparable, Comparator
1) 정렬하는 방법
(1) SortMain1
- Arrays.sort()를 사용하면 배열에 들어있는 데이터를 순서대로 정렬되는 것을 출력 결과로 확인할 수 있음
package collection.compare;
public class SortMain1 {
public static void main(String[] args) {
Integer[] array = {3, 2, 1};
System.out.println(Arrays.toString(array));
System.out.println("기본 정렬 후");
Arrays.sort(array);
System.out.println(Arrays.toString(array));
}
}
/* 출력 결과
[3, 2, 1]
기본 정렬 후
[1, 2, 3]
*/
(2) 정렬 알고리즘
- 먼저 가장 왼쪽에 있는 데이터와 그 다음 데이터를 비교한 후 가장 왼쪽의 데이터가 더 크면 그 둘을 교환함
- 다음 차례의 둘을 비교하고 왼쪽의 데이터가 더 크면 둘을 교환하고 이런 방식으로 처음부터 끝까지 비교하면 마지막 항목은 가장 큰 값이 됨
- 처음으로 돌아와서 다시 똑같은 방식으로 비교를 하면 최종적으로 1, 2, 3으로 정렬이 됨
- 지금 설명한 정렬은 가장 단순한 정렬의 예시로 성능이 좋지 않음
- 실제로는 정렬 성능을 높이기 위한 다양한 정렬 알고리즘이 존재하며 자바는 초기에는 퀵소트를 사용했다가 지금은 데이터가 작을 때(32개 이하)는 듀얼 피벗 퀵소트(Dual-Pivot QuickSort)를 사용하고, 데이터가 많을 때는 팀소트(TimSort)를 사용하며 평균 O(n log n)의 성능을 제공함
** 참고
- 정렬 알고리즘에 대한 이론적인 내용은 강의에서는 다루지 않으므로 별도로 자료 구조와 알고리즘을 학습하는것을 권장함
2) 비교자
(1) Comparator
- 정렬을 할 때 1, 2, 3 순서가 아니라 반대로 3, 2, 1로 정렬하고 싶다면 비교자(Comparator)를 사용하면 됨
- 이름 그대로 두 값을 비교할 때 비교 기준을 직접 제공할 수 있음
- 오름 차순의 경우 두 인수를 비교해서 첫 번째 인수가 더 작으면 음수, 두 값이 같으면 0, 첫 번째 인수가 더 크면 양수의 결과 값을 반환하면 되며 내림 차순의 경우 반환 값을 반대로 하면 됨
public interface Comparator<T> {
int compare(T o1, T o2);
}
(2) SortMain2
- Arrays.sort()를 사용할 때 비교자(Comparator)를 넘겨주면 알고리즘에서 어떤 값이 더 큰지 두 값을 비교할 때 비교자를 사용함
- 내부 클래스를 사용하여 AscComparator와 DescComparator에 Comparator를 구현하여 compare() 메서드를 각각 오름차순 내림차순이 되도록 정의하였음
- sort() 메서드를 사용 시 AscComparator를 사용하면 오름차순 정렬이 되고, DescComparator를 사용하면 내림차순 정렬이 되는 것을 확인할 수 있음
- 내림 차순 정렬에 반환조건을 보면 AscComparator의 반환 조건에 * -1을 하여 양수는 음수로, 음수는 양수로 반환되도록 하면 정렬의 결과가 반대가 됨
- new AscComparator().reversed()를 사용하면 미리 구현되어있는 내림차순 정렬을 사용할 수 있음
package collection.compare;
public class SortMain2 {
public static void main(String[] args) {
Integer[] array = {3, 2, 1};
System.out.println(Arrays.toString(array));
System.out.println("Comparator 비교");
Arrays.sort(array, new AscComparator());
System.out.println("AscComparator: " + Arrays.toString(array));
Arrays.sort(array, new DescComparator());
System.out.println("DescComparator: " + Arrays.toString(array));
Arrays.sort(array, new AscComparator().reversed()); // DescComparator와 같음
System.out.println("AscComparator.reversed: " + Arrays.toString(array));
}
static class AscComparator implements Comparator<Integer> {
@Override
public int compare(Integer o1, Integer o2) {
System.out.println("o1=" + o1 + " o2=" + o2);
return (o1 < o2) ? -1 : ((o1 == o2) ? 0 : 1);
}
}
static class DescComparator implements Comparator<Integer> {
@Override
public int compare(Integer o1, Integer o2) {
System.out.println("o1=" + o1 + " o2=" + o2);
return ((o1 < o2) ? -1 : ((o1 == o2) ? 0 : 1)) * -1; // 내림 차순(결과가 반대로)
}
}
}
/* 실행 결과
[3, 2, 1]
Comparator 비교
o1=2 o2=3
o1=1 o2=2
AscComparator: [1, 2, 3]
o1=2 o2=1
o1=3 o2=2
DescComparator: [3, 2, 1]
o1=3 o2=2
o1=2 o2=1
AscComparator.reversed: [3, 2, 1]
*/
3) 직접 만든 객체 정렬
(1) Comparable
- 자바가 기본으로 제공하는 Integer, String 같은 객체를 제외하고 MyUser와 같이 직접 만든 객체들을 정렬 하려면 정렬을 할 때 내가 만든 두 객체 중에 어떤 객체가 더 큰지 알려줄 방법이 있어야 함
- 이때 Comparable 인터페이스를 구현하면 되며, 이름 그대로 비교 가능한, 비교할 수 있는 이라는 뜻으로 객체에 비교 기능을 추가해 줌
- 자기 자신과 인수로 넘어온 객체를 비교해서 반환하면 됨
public interface Comparable<T> {
public int compareTo(T o);
}
(2) MyUser
- MyUser가 Comparable 인터페이스를 구현하여 compareTo() 메서드를 구현할 때 정렬 기준을 나이로 지정하였음
- MyUser 클래스의 기본 정렬 방식을 나이의 오름차순으로 정한 것임
- Comparable을 통해 구현한 순서를 자연 순서(Natural Ordering)이라 함
package collection.compare;
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 String getId() {
return id;
}
public int getAge() {
return 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 +
'}';
}
}
(3) SortMain3
- MyUser를 생성하여 MyUser타입 배열을 만들어서 Arrays.sort()에 해당 배열을 인자로 전달하면 기본 정렬을 시도함
- 이때 객체가 스스로 가지고 있는 Comparable 인터페이스를 사용해서 비교함
- 실행 결과를 보면 MyUser가 구현한 대로 나이(age) 오름차순으로 정렬된 것을 확인할 수 있으며 MyUser의 자연적인 순서를 사용했음
package collection.compare;
public class SortMain3 {
public static void main(String[] args) {
MyUser myUser1 = new MyUser("a", 30);
MyUser myUser2 = new MyUser("b", 20);
MyUser myUser3 = new MyUser("c", 10);
MyUser[] array = {myUser1, myUser2, myUser3};
System.out.println("기본 데이터");
System.out.println(Arrays.toString(array));
System.out.println("Comparable 기본 정렬");
Arrays.sort(array);
System.out.println(Arrays.toString(array));
}
}
/* 실행 결과
기본 데이터
[MyUser{id='a', age=30}, MyUser{id='b', age=20}, MyUser{id='c', age=10}]
Comparable 기본 정렬
[MyUser{id='c', age=10}, MyUser{id='b', age=20}, MyUser{id='a', age=30}]
*/
(4) IdComparator - 다른 방식으로 정렬
- 만약 객체가 가지고 있는 Comparable 기본 정렬이 아니라 다른 정렬을 사용하고 싶다면 Comparator를 사용하면 됨
- o1.getId().compareTo(o2.getId()): 매개변수들로 넘어온 객체의 id를 기준으로 정렬, MyUser의 Id는 String 타입인데 자바에서는 이미 compareTo() 메서드를 구현해 두었음
package collection.compare;
public class IdComparator implements Comparator<MyUser> {
@Override
public int compare(MyUser o1, MyUser o2) {
return o1.getId().compareTo(o2.getId()); // 자바에서 구현을 해두었음
}
}
(5) SortMain3 - 추가
- 객체의 기본 정렬이 아니라 정렬 방식을 외부에서 별도로 지정하고 싶다면 Arrays.sort의 인수로 비교자(Comparator)를 만들어서 넘겨 주면됨
- 이렇게 비교자를 따로 전달하면 객체가 기본으로 가지고 있는 Comparable을 무시하고 별도로 전달한 비교자를 사용해서 정렬함
- MyUser는 Comparable 인터페이스를 구현하여 나이순으로 정렬 되도록 기본 정렬이 구현되어있지만 외부에서 전달한 IdComparator로 인하여 Id순으로 정렬이 되는 것을 확인할 수 있음
package collection.compare;
public class SortMain3 {
public static void main(String[] args) {
// ... 기존 코드 동일 생략
// 추가
System.out.println("IdComparator 정렬");
Arrays.sort(array, new IdComparator());
System.out.println(Arrays.toString(array));
System.out.println("IdComparator().reversed() 정렬");
Arrays.sort(array, new IdComparator().reversed());
System.out.println(Arrays.toString(array));
}
}
/* 추가 코드 실행 결과
IdComparator 정렬
[MyUser{id='a', age=30}, MyUser{id='b', age=20}, MyUser{id='c', age=10}]
IdComparator().reversed() 정렬
[MyUser{id='c', age=10}, MyUser{id='b', age=20}, MyUser{id='a', age=30}]
*/
** 주의
- 만약 Comparable도 구현하지 않고, Comparator도 제공하지 않으면 런타임 오류가 발생함
/*
Exception in thread "main" java.lang.ClassCastException:
class collection.compare.MyUser cannot be cast to class java.lang.Comparable
*/
(6) 정리
- 객체의 기본 정렬 방법은 객체에 Comparable를 구현해서 정의
- 이렇게 하면 객체는 이름 그대로 비교할 수 있는 객체가 되어 기본 정렬 방법을 가지게 됨
- 기본 정렬 외에 다른 정렬 방법을 사용해야 하는 경우 비교자(Comparator)를 별도로 구현하여 정렬 메서드에 전달하면 되며 이 경우 전달한 Comparator가 항상 우선권을 가짐
- 자바가 제공하는 기본 객체들은 대부분 Comparable을 구현해 두었음
4) 자료 구조의 정렬
(1) SortMain4 - List와 정렬
- 정렬은 배열 뿐만 아니라 순서가 있는 List 같은 자료 구조에도 사용할 수 있음
- Set 처럼 순서가 없는 자료구조엔 당연히 사용할 수 없음
- Collections.sort(list), Collections.sort(list, newIdComparator())
- Collections 클래스의 sort() 메서드를 사용하여 정렬할 수 있으며 기본 정렬이 적용됨
- 별도의 비교자로 비교하고 싶다면 다음 인자에 비교자를 넘기면 됨
- 하지만, 이 방식보다는 객체 스스로 정렬 메서드를 가지로 있는 list.sort() 사용하는 것을 더 권장하며 둘의 결과는 같음
- Collections.reverse()로 역순 정렬도 가능함
- list.sort(null), list.sort(new IdComparator())
- 별도의 비교자가 없으므로 Comparable로 자연적인 순서로 비교하여 정렬하며 별도의 비교자로 비교하고 싶으면 인자에 비교자를 넘기면 됨
- 자바 1.8부터 사용가능하며 객체가 스스로 정렬하는 메서드를 가지고 있으므로 더 객체지향 적인 코드임
package collection.compare;
public class SortMain4 {
public static void main(String[] args) {
MyUser myUser1 = new MyUser("a", 30);
MyUser myUser2 = new MyUser("b", 20);
MyUser myUser3 = new MyUser("c", 10);
LinkedList<MyUser> list = new LinkedList<>();
list.add(myUser1);
list.add(myUser2);
list.add(myUser3);
System.out.println("기본 데이터");
System.out.println(list);
System.out.println("Comparable 기본 정렬");
list.sort(null);
System.out.println(list);
System.out.println("IdComparator 정렬");
list.sort(new IdComparator());
System.out.println(list);
list.sort(new IdComparator().reversed());
System.out.println(list);
System.out.println("Collections 사용");
Collections.sort(list);
System.out.println(list);
Collections.sort(list, new IdComparator());
System.out.println(list);
Collections.reverse(list);
System.out.println(list);
}
}
/* 실행 결과
기본 데이터
[MyUser{id='a', age=30}, MyUser{id='b', age=20}, MyUser{id='c', age=10}]
Comparable 기본 정렬
[MyUser{id='c', age=10}, MyUser{id='b', age=20}, MyUser{id='a', age=30}]
IdComparator 정렬
[MyUser{id='a', age=30}, MyUser{id='b', age=20}, MyUser{id='c', age=10}]
[MyUser{id='c', age=10}, MyUser{id='b', age=20}, MyUser{id='a', age=30}]
Collections 사용
[MyUser{id='c', age=10}, MyUser{id='b', age=20}, MyUser{id='a', age=30}]
[MyUser{id='a', age=30}, MyUser{id='b', age=20}, MyUser{id='c', age=10}]
[MyUser{id='c', age=10}, MyUser{id='b', age=20}, MyUser{id='a', age=30}]
*/
(2) SortMain5 - Tree 구조와 정렬
- TreeSet과 같은 이진 탐색 트리 구조는 데이터를 보관할 때 데이터를 정렬하면서 보관하기 때문에 정렬 기준을 제공하는 것이 필수임
- 이진 탐색 트리는 데이터를 저장할 때 왼쪽 노드에 저장해야 할 지, 오른쪽 노드에 저장해야 할 지 비교가 필요하기 때문에 TreeSet, TreeMap은 Comparable 혹은 Comparator가 필수임
- TreeSet을 생성할 때 별도의 비교자를 제공하지 않으면 객체가 구현한 Comparable을 사용하여 데이터가 삽입 될 때 기본 정렬이 됨
- TreeSet을 생성할 때 별도의 비교자를 제공하면 제공된 비교자를 사용하여 정렬이 됨
- Comparable도 구현하지 않고 Comparator도 제공하지 않으면 런타임 오류가 발생함
package collection.compare;
public class SortMain5 {
public static void main(String[] args) {
MyUser myUser1 = new MyUser("a", 30);
MyUser myUser2 = new MyUser("b", 20);
MyUser myUser3 = new MyUser("c", 10);
TreeSet<MyUser> treeSet1 = new TreeSet<>();
treeSet1.add(myUser1);
treeSet1.add(myUser2);
treeSet1.add(myUser3);
System.out.println("Comparable 기본 정렬");
System.out.println(treeSet1);
TreeSet<MyUser> treeSet2 = new TreeSet<>(new IdComparator()); // 생성 시 비교자를 제공
treeSet2.add(myUser1);
treeSet2.add(myUser2);
treeSet2.add(myUser3);
System.out.println("IdComparator 정렬");
System.out.println(treeSet2);
TreeSet<MyUser> treeSet3 = new TreeSet<>(new IdComparator().reversed());
treeSet3.add(myUser1);
treeSet3.add(myUser2);
treeSet3.add(myUser3);
System.out.println("IdComparator.reversed");
System.out.println(treeSet3);
}
}
/* 실행 결과
Comparable 기본 정렬
[MyUser{id='c', age=10}, MyUser{id='b', age=20}, MyUser{id='a', age=30}]
IdComparator 정렬
[MyUser{id='a', age=30}, MyUser{id='b', age=20}, MyUser{id='c', age=10}]
IdComparator.reversed
[MyUser{id='c', age=10}, MyUser{id='b', age=20}, MyUser{id='a', age=30}]
*/
(3) 정리
- 자바의 정렬 알고리즘은 매우 복잡하며 거의 완성형에 가까움
- 개발자가 복잡한 정렬 알고리즘은 신경 쓰지 않으면서 정렬의 기준만 간단히 변경할 수 있도록 정렬의 기준을 Comparable, Comparator 인터페이스를 통해 추상화해 두었음
- 객체의 정렬이 필요한 경우 Comparable을 통해 기본 자연 순서를 제공하고 자연 순서 외에 다른 정렬 기준이 추가로 필요하면 Comparator를 제공하면 됨
3. 컬렉션 유틸
1) 다양한 컬렉션 유틸
(1) CollectionsSortMain - 정렬
- Collections 정렬 관련 메서드
- max: 정렬 기준으로 최대 값을 찾아서 반환
- min: 정렬 기준으로 최소 값을 찾아서 반환
- shuffle: 컬렉션을 랜덤하게 섞음
- sort: 정렬 기준으로 컬렉션을 정렬
- reverse: 정렬 기준의 반대로 컬렉션을 정렬(컬렉션에 들어있는 결과를 반대로 정렬)
package collection.compare.utils;
public class CollectionsSortMain {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);
Integer max = Collections.max(list);
Integer min = Collections.min(list);
System.out.println("max = " + max);
System.out.println("min = " + min);
System.out.println("list = " + list);
Collections.shuffle(list);
System.out.println("shuffle list = " + list);
Collections.sort(list);
System.out.println("sort list = " + list);
Collections.reverse(list);
System.out.println("reverse list = " + list);
}
}
/* 실행 결과
max = 5
min = 1
list = [1, 2, 3, 4, 5]
shuffle list = [4, 2, 1, 3, 5]
sort list = [1, 2, 3, 4, 5]
reverse list = [5, 4, 3, 2, 1]
*/
(2) OfMain - 편리한 컬렉션 생성
- List, Set, Map 모두 of() 메서드를 지원하는데, 이 메서드를 사용하면 컬렉션을 편리하게 생성할 수 있으며 이렇게 생성된 컬렉션은 가변이 아니라 불변 컬렉션임
- 불변 컬렉션은 변경할 수 없으므로 of()로 생성된 컬렉션을 변경하고 호출하면 UnsupportedOperationException 예외가 발생함
- of()로 생성된 컬렉션의 클래스를 확인해보면 ImmutableCollections$ListN 처럼 출력되는 것을 확인할 수 있음
package collection.compare.utils;
public class OfMain {
public static void main(String[] args) {
// 편리한 불변 컬렉션 생성
List<Integer> list = List.of(1, 2, 3);
Set<Integer> set = Set.of(1, 2, 3);
Map<Integer, String> map = Map.of(1, "one", 2, "two");
System.out.println("list = " + list);
System.out.println("list.getClass() = " + list.getClass());
System.out.println("set = " + set);
System.out.println("set.getClass() = " + set.getClass());
System.out.println("map = " + map);
System.out.println("map.getClass() = " + map.getClass());
// list.add(4); // java.lang.UnsupportedOperationException 예외 발생
}
}
/* 실행 결과
list = [1, 2, 3]
list.getClass() = class java.util.ImmutableCollections$ListN
set = [1, 2, 3]
set.getClass() = class java.util.ImmutableCollections$SetN
map = {1=one, 2=two}
map.getClass() = class java.util.ImmutableCollections$MapN
*/
(3) ImmutableMain - 불변, 가변 전환
- 불변 리스트를 가변 리스트로 전환하려면 new ArrayList<>()를 생성할 때 생성자에 불변 리스트를 전환하면 되며 Map, Set도 모두 동일한 방식으로 전환할 수 있음
- 가변 리스트를 불변 리스트로 전환하려면 Collections.unmodifiableList()를 사용하면 되며 다양한 자료구조를 변경할 수 있도록 다양한 unmodifiableXxx()가 존재함
package collection.compare.utils;
public class ImmutableMain {
public static void main(String[] args) {
// 불변 리스트 생성
List<Integer> list = List.of(1, 2, 3);
// 가변 리스트로 변환
ArrayList<Integer> mutableList = new ArrayList<>(list);
mutableList.add(4);
System.out.println("mutableList = " + mutableList);
System.out.println("mutableList.getClass() = " + mutableList.getClass());
// 불변 리스트로 변환
List<Integer> unmodifiableList = Collections.unmodifiableList(mutableList);
System.out.println("unmodifiableList.getClass() = " + unmodifiableList.getClass());
// unmodifiableList.add(5); // java.lang.UnsupportedOperationException 예외 발생
}
}
/* 실행 결과
mutableList = [1, 2, 3, 4]
mutableList.getClass() = class java.util.ArrayList
unmodifiableList.getClass() = class java.util.Collections$UnmodifiableRandomAccessList
*/
(4) EmptyListMain - 빈 리스트 생성
- 가끔 null을 반환하지 않고 빈 리스트를 반환하는 경우에 사용함
- 빈 가변 리스트는 원하는 컬렉션의 구현체를 직접 생성하면 됨
- 빈 불변 리스트는 Collections.emptyList()와 List.of()로 생성할 수 있음
- List.of()가 더 간결하고 값이 있는 자료구조를 생성할 때에도 사용법에 일관성이 있기 때문에 자바9 이상을 사용한다면 이 기능을 권장함
package collection.compare.utils;
public class EmptyListMain {
public static void main(String[] args) {
// 빈 가변 리스트 생성
ArrayList<Object> list1 = new ArrayList<>();
ArrayList<Object> list2 = new ArrayList<>();
// 빈 불변 리스트 생성
List<Integer> list3 = Collections.emptyList(); // 자바5
List<Integer> list4 = List.of(); // 자바9
System.out.println("list3.getClass() = " + list3.getClass());
System.out.println("list4.getClass() = " + list4.getClass());
}
}
/* 실행 결과
list3.getClass() = class java.util.Collections$EmptyList
list4.getClass() = class java.util.ImmutableCollections$ListN
*/
** 참고 - Arrays.asList()
- Arrays.asList() 메서드를 사용해도 리스드를 생성할 수 있는데 이 메서드는 자바 1.2 부터 존재하였으로 List.of()를 권장함
- Arrays.asList()로 생성한 리스트는 고정된 크기를 가지지만 요소들은 변경할 수 있음
- 즉 리스트의 길이는 변경할 수 없지만 기존 위치에 있는 요소들을 다른 요소로 교체할 수 있는 애매한 불변 자료 구조가 됨
- set()을 통해 요소를 변경할 수 있으며, add(), remove()와 같은 메서드를 호출하면 크기가 변경되므로 예외가 발생함
- 리스트 내부의 요소를 변경해야 하는 경우나 Java9 이전 버전에서 작업해야 하는 경우가 아니라면 일반적으로 List.of()를 사용하는 것을 권장함
- Arrays.asList()의 인자로 배열을 전달하면 배열의 참조값을 전달해서 사용하기 때문에 set으로 변경을 하게되면 전달한 배열의 값도 변경되지만 List.of()의 경우에는 완전히 새로운 불변 리스트를 생성함
(5) EmptyListMain - 멀티스레드 동기화
- Collections.synchronizedList를 사용하면 일반 리스트를 멀티 스레드 상황에서 동기화 문제가 발생하지 않는 안전한 리스트로 만들수 있음
- 동기화 작업으로 인해 일반 리스트보다 성능은 더 느림
package collection.compare.utils;
public class SyncMain {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
System.out.println("list.getClass() = " + list.getClass());
List<Integer> synchronizedList = Collections.synchronizedList(list);
System.out.println("synchronizedList.getClass() = " + synchronizedList.getClass());
}
}
/* 실행 결과
list.getClass() = class java.util.ArrayList
synchronizedList.getClass() = class java.util.Collections$SynchronizedRandomAccessList
*/
4. 컬렉션 프레임워크 전체 정리
1) 컬렉션 프레임워크 전체 정리
(1) 자바 컬렉션 프레임 워크
- 자바 컬렉션 프레임워크는 데이터 그룹을 저장하고 처리하기 위한 통합 아키텍처를 제공함
- 인터페이스, 구현, 알고리즘으로 구성되어있으며 다양한 타입의 컬렉션(객체의 그룹이나 집합)을 효율적으로 처리할 수 있게 해줌
(2) Collection 인터페이스의 필요성
- 자바 컬렉션 프레임워크의 가장 기본적인 인터페이스로 자바에서 데이터 그룹을 다루는데 필요한 가장 기본적인 메서드들을 정의하며 다양한 컬렉션 타입들이 공통적으로 따라야 하는 기본 규약을 정의함
- List, Set, Queue와 같은 더 구체적인 컬렉션 인터페이스들은 모두 Collection 인터페이스를 확장하여 공통된 메서드들을 상속받고 추가적인 기능이나 특성을 제공함
- 이런 설계는 자바 컬렉션 프레임워크의 일관성과 재사용성을 높여줌
- 일관성: 모든 컬렉션 타입들이 Collection 인터페이스를 구현함으로써 모든 컬렉션들이 기본적인 동작을 공유한다는 것을 보장함, 개발자가 다양한 타입의 컬렉션을 다룰 때 일관된 방식으로 접근할 수 있게 해줌
- 재사용성: Collection 인터페이스에 정의된 메서드들은 다양한 컬렉션 타입들에 공통으로 적용되기 때문에 코드의 재사용성과 유지 보수성을 높여줌
- 확장성: 새로운 컬렉션 타입을 만들 때 Collection 인터페이스를 구현함으로써 기존에 정의된 알고리즘과 도구를 사용할 수 있게되어 프레임워크의 확장성을 향상 시킴
- 다형성: Collection 인터페이스를 사용함으로써 다양한 컬렉션 타입들을 같은 타입으로 다룰수 있게되어 다형성을 활용한 유연한 코드를 작성할 수 있게 해줌
- Collection은 Map을 제외한 모든 컬렉션 타입의 부모로 모든 컬렉션을 받아서 유연할게 처리할 수 있으며 대표적인 컬렉션 인터페이스로 Iterator를 제공함
- 데이터를 단순히 순회할 목적이라면 Collection을 사용하면 모든 모든 컬렉션 타입의 데이터를 순회할 수 있음
- 컬렉션 프레임워크는 크게 인터페이스, 구현, 알고리즘을 제공함
(3) Collection 인터페이스의 주요 메서드
- add(E e): 컬렉션에 요소를 추가
- remove(Object o): 주어진 객체를 컬렉션에서 제거
- size(): 컬렉션에 포함된 요소의 수를 반환
- isEmpty(): 컬렉션이 비어 있는지 확인
- contains(Object o): 컬렉션이 특정 요소를 포함하고 있는지 확인
- iterator(): 컬렉션의 요소에 접근하기 위한 반복자를 반환
- clear(): 컬렉션의 모든 요소를 제거함
(4) 자바 컬렉션 프레임워크의 핵심 인터페이스
- Collection: 단일 루트 인터페이스로 모든 컬렉션 클래스가 이 인터페이스를 상속받음
- List, Set, Queue 등의 인터페이스 - List: 순서가 있는 컬렉션을 나타내며 중복 요소를 허용하며 인덱스를 통해 요소에 접근할 수 있음
- ArrayList, LinkedList - Set: 중복 요소를 허용하지 않는 컬렉션을 나타내며 특정 위치가 없기 때문에 인덱스를 통해 요소에 접근할 수 없음
- HashSet, LinkedHashSet, TreeSet - Queue: 요소가 처리되기 전에 보관되는 컬렉션을 나타냄
- ArrayDeque, LinkedList, PriorityQueue(우선순위를 앞으로 조금더 당길 수 있음) - Map: 키와 값 쌍으로 요소를 저장하는 객체이며 Map은 Collection 인터페이스를 상속받지 않음
- HashMap, LinkedHashMap, TreeMap
(5) 각 인터페이스의 여러 구현을 제공
- List
- ArrayList: 내부적으로 배열을 사용
- LinkedList: 연결 리스트를 사용
- Set, Map
- HashSet, HashMap: 해시 테이블 사용
- LinkedHashSet, LinkedHashMap: 해시 테이블과 연결 리스트를 사용
- TreeSet, TreeMap: 레드-블랙 트리를 사용
- Queue
- LinkedList: 연결 리스트 사용
- ArrayDeque: 배열 기반의 원형 큐를 사용하며 대부분의 경우 ArrayDeque가 빠름
(6) 알고리즘
- 컬렉션 프레임워크는 데이터를 조작하고 처리하기 위한 다양한 알고리즘을 제공함
- 알고리즘은 각각의 자료 구조 자체적으로 기능을 제공하거나 Collections와 Arrays 클래스에 정적 메소드 형태로 구현되어있어 이를 통해 정렬, 순환, 검색, 변환 등의 작업을 수행할 수 있음
(7) 선택 가이드 정리
- 순서가 중요하고 중복이 허용되는 경우
- List 인터페이스를 사용, 대부분 ArrayList 사용
- ArrayList가 일반적인 선택이지만 추가/삭제 작업이 앞쪽에서 빈번한 경우 LinkedList가 성능상 더 좋음
- 중복을 허용하지 않고 순서가 중요하지 않은 경우
- Set 인터페이스를 사용, 대부분 HashSet을 사용
- 순서를 유지해야 하면 LinkedHashSet, 정렬된 순서가 필요하면 TreeSet을 사용
- 요소를 키-값 쌍으로 저장하려는 경우
- Map 인터페이스를 사용, 대부분 HashMap을 사용함
- 순서가 중요하지 않으면 HashMap, 순서를 유지해야 하면 LinkedHashMap, 정렬된 순서가 필요하면 TreeMap을 사용
- 요소를 처리하기 전에 보관해야 하는 경우
- Queue, Deque 인터페이스를 사용, 대부분 ArrayDeque를 사용함
- 스택과 큐 구조 모두 ArrayDeque를 사용하는 것이 더 빠르며 우선순위에 따라 요소를 처리해야 한다면 PriorityQueue를 고려
** 참고
- PriorityQueue는 자주 사용하지 않아서 따로 자세히 설명하지 않았음, 큐에 입력하는 요소에 우선순위를 부여할 수 있음
- 트리 구조도 충분히 빠르지만 Hash를 사용하는 자료 구조보다는 느리기 때문에 정렬할 기준이 필요 없다면 HashSet, HashMap을 사용하면 됨
5. 문제와 풀이
1) 문제
(1) 요구 사항
- 카드(Card)는 1 ~ 13까지 있으며 번호당 4개의 문양이 있음
- ♠: 스페이드
- ♥: 하트
- ♦︎: 다이아
- ♣: 클로버
- 예) 1(♠), 1(♥), 1(♦︎), 1(♣), 2(♠), 2(♥), 2(♦︎), 2(♣) ... 13(♠), 13(♥), 13(♦︎), 13(♣)
- 13 * 4 = 총 52장의 카드가 있으며 52장의 카드가 있는 카드 뭉치를 덱(Deck)이라 함
- 2명의 플레이어(Player)가 게임을 진행함
- 게임을 시작하면 다음 순서를 따름
- 덱에 있는 카드를 랜덤하게 섞음
- 각 플레이어는 덱에서 카드를 5장씩 뽑음
- 각 플레이어는 5장의 카드를 정렬된 순서대로 보여주며 정렬 기준은 다음과 같음
- 작은 숫자가 먼자 나옴
- 같은 숫자의 경우 스페이스, 하트, 다이어, 클로버 순으로 정렬 - 카드 숫자의 합계가 큰 플레이어가 승리
- 게임을 단순화 하기 위해 숫자만 출력하고 합계가 같으면 무승부임
- 이 문제는 정해진 정답이 없고 실행 결과 예시를 참고하되 자유롭게 풀면됨
- CarGameMain에 main()메서드를 만들고 시작하면되며 필요하면 클래스를 추가해도 됨
실행 결과 예시1
플레이어1의 카드: [2(♠), 7(♥), 7(♦), 8(♣), 13(♠)], 합계: 37
플레이어2의 카드: [1(♠), 1(♣), 6(♠), 9(♠), 9(♣)], 합계: 26
플레이어1 승리
실행 결과 예시1
플레이어1의 카드: [2(♦), 3(♠), 6(♥), 10(♣), 13(♦)], 합계: 34
플레이어2의 카드: [2(♠), 4(♣), 5(♠), 11(♣), 12(♥)], 합계: 34
무승부
스페이스 하트 같은 아이콘을 직접 사용하기 어려운경우 아래와 같이 적어주면 아이콘을 출력할 수 있음
- "\u2660" : 스페이드(♠)
- "\u2665" : 하트(♥)
- "\u2666" : 다이아몬드(♦)
- "\u2663" : 클로버(♣)
- 예) System.out.println("\u2660")
(2) 교안 버전
(1) Suit
- 스페이드, 하트 등의 문양의 순서는 변하지 않는다고 가정하고 ENUM의 기본 순서를 사용
package collection.compare.test.clone;
public enum Suit {
SPADE("♠️"),
HEART("❤️"),
DIAMOND("♦️"),
CLUB("♣️");
private String icon;
Suit(String icon) {
this.icon = icon;
}
public String getIcon() {
return icon;
}
}
(2) Card
- ENUM 타입은 compareTo()가 열거형의 순서인 ordinal로 구현되어있으며 이 메서드는 final로 선언되어있어서 재정의 할 수 없음
- 만약 Enum에 선언되는 순서가 변경이 되면 compareTo()의 결과가 다르게 나오기 때문에 카드 종류의 순서가 변한다고 가정한다면 별도로 비교 로직을 정의해야하고, Enum을 사용한다면 정의한 순서가 변경되지 않는다는 대전제를 지키면서 개발해야 함
package collection.compare.test.clone;
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 anotherCard) {
// 숫자를 먼저 비교하고, 숫자가 같으면 마크를 비교
if (this.rank != anotherCard.rank) {
return Integer.compare(this.rank, anotherCard.rank); // Integer에 이미 구현이 되어있음
} else {
// Enum은 Comparable을 구현하고 있음
// ordinal 순서로 구현, final메서드라서 재정의가 불가능하여 상수를 정의한 순서대로 정렬이 되도록 사용하면 됨
return this.suit.compareTo(anotherCard.suit);
}
}
@Override
public String toString() {
return rank + "(" + suit.getIcon() + ")";
}
}
(3) Deck
- 지금처럼 데이터의 수가 작다면 ArrayList를 사용해도 괜찮지만 데이터의 수가 많다면 LinkedList를 고려
- drawCard(): List의 데이터를 앞에서부터 꺼냄, 자바 21 이상에서는 return cards.removeFirst();을 사용
package collection.compare.test.clone;
public class Deck {
private List<Card> cards = new ArrayList<>();
public Deck() {
initCard();
shuffle();
}
private void shuffle() {
Collections.shuffle(cards);
}
private void initCard() {
for (int i = 1; i <= 13; i++) {
for (Suit value : Suit.values()) {
cards.add(new Card(i, value));
}
}
}
// 카드 뽑기
public Card drawCard() {
return cards.remove(0);
}
}
(4) Player
package collection.compare.test.clone;
public class Player {
private String name;
private List<Card> hand;
public Player(String name) {
this.name = name;
this.hand = new ArrayList<>();
}
// Deck에서 꺼낸 카드를 플레이어가 가짐
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());
}
public String getName() {
return name;
}
}
(5) CardGameMain
package collection.compare.test.clone;
public class CardGameMain {
public static void main(String[] args) {
Deck deck = new Deck();
Player player1 = new Player("플레이어1");
Player player2 = new Player("플레이어2");
for (int i = 0; i < 5; i++) {
player1.drawCard(deck);
player2.drawCard(deck);
}
player1.showHand();
player2.showHand();
Player winner = getWinner(player1, player2);
if (winner != null) {
System.out.println(winner.getName() + " 승리");
} else {
System.out.println("무승부");
}
}
private static Player getWinner(Player player1, Player player2) {
int sum1 = player1.rankSum();
int sum2 = player2.rankSum();
if (sum1 > sum2) {
return player1;
} else if (sum1 == sum2) {
return null;
} else {
return player2;
}
}
}