관리 메뉴

나구리의 개발공부기록

컬렉션 프레임워크 - 순회, 정렬, 전체 정리, 순회(직접 구현하는 Iterable과 Iterator, 향상된 for문, 자바가 제공하는 Iterable과 Iterator), 정렬 - Comparable, Comparator, 컬렉션 유틸, 컬렉션 프레임워크 전체 정리 본문

인프런 - 실전 자바 로드맵/실전 자바 - 중급 2편

컬렉션 프레임워크 - 순회, 정렬, 전체 정리, 순회(직접 구현하는 Iterable과 Iterator, 향상된 for문, 자바가 제공하는 Iterable과 Iterator), 정렬 - Comparable, Comparator, 컬렉션 유틸, 컬렉션 프레임워크 전체 정리

소소한나구리 2025. 2. 9. 13:01
728x90

출처 : 인프런 - 김영한의 실전 자바 - 중급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)가 게임을 진행함
  • 게임을 시작하면 다음 순서를 따름
    1. 덱에 있는 카드를 랜덤하게 섞음
    2. 각 플레이어는 덱에서 카드를 5장씩 뽑음
    3. 각 플레이어는 5장의 카드를 정렬된 순서대로 보여주며 정렬 기준은 다음과 같음
      - 작은 숫자가 먼자 나옴
      - 같은 숫자의 경우 스페이스, 하트, 다이어, 클로버 순으로 정렬
    4. 카드 숫자의 합계가 큰 플레이어가 승리
      - 게임을 단순화 하기 위해 숫자만 출력하고 합계가 같으면 무승부임
  • 이 문제는 정해진 정답이 없고 실행 결과 예시를 참고하되 자유롭게 풀면됨
  • 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;
        }
    }
}

 

728x90