이번 포스팅은 지난 번과 마찬가지로 내용이 많지는 않다. Arrays는 Java 배열 객체를 조금 더 쉽게 다루기 위해 배열에 특화된 몇몇 매서드가 static으로 정의되어 있기 때문에 사용법 자체가 어렵거나 이해를 요하는 부분이 거의 없다. 다만, Arrays에서 제공하는 sort(), 즉 정렬 기능을 하는 매서드의 경우, Comparable 인터페이스에 정의된 내용을 구현하여 사용하기 때문에 Comparable과 이와 연관된 Comparator 인터페이스에 대해서는 확실히 짚고 넘어가야 한다(그리고 개념이 처음 접하면 약간 어렵다).
오늘의 포스팅 내용을 시작해보자.
1. Arrays 클래스
Arrays 클래스는 java.util 패키지에 속한 클래스로, Java의 배열을 다루기 위한 매서드들이 정의되어 있다. 서두에 언급한대로 이 매서드들은 전부 static으로 정의되었기 때문에 Arrays 클래스는 인스턴스화가 전혀 필요없는 클래스다.
String 배열과 int 배열을 하나씩 생성하고, Arrays 클래스의 static 매서드를 호출하여 몇몇 기능들을 살펴보자.
배열의 전체 내용을 출력하기 위해서는 Object 클래스로부터 상속받은 toString() 매서드를 사용하면 된다. 또한 배열을 복사하여 다른 배열 객체 변수에 저장하기 위해서는 copyOf()라는 매서드를 사용한다.
배열 내 모든 요소 값을 하나의 단일 값으로 교체하려면 fill()이라는 매서드를 사용한다. setAll() 이라는 매서드도 있으나, 이 매서드의 매개변수는 아직 포스팅에서 다루지 않은 내용이 포함되어 이 포스팅에서는 다루지 않는다.
Object 클래스로부터 상속받는 equals() 매서드 외에도 이차원 이상의 배열 값 비교를 위한 deepEquals()도 상속받는다. 이 매서드의 사용 예시는 링크를 달아놓은 포스팅에도 작성한 내용이라, 이 포스팅에서는 사용 예시를 작성하지 않는다.
Arrays 클래스에서 유심히 봐야하는 매서드는 sort와 binarySearch() 매서드다. binarySearch()는 매개변수로 지정한 값이 배열 내에 존재하는 경우 해당 값의 index 번호를 반환한다. Vector나 List 등 배열과 유사한 객체에서 값 검색 시 indexOf()라는 이름읨 매서드를 사용한 것과 달리 Arrays 클래스는 binarySearch()라는 이름의 매서드를 제공하는 것에 대해 의문이 들 수 있을 듯 하다. indexOf()는 특정 값을 찾기 위해 0번 index부터 순차적으로 값을 비교해나가지만, binarySearch()는 자료구조에서 검색에 사용되는 방식 중 하나인 이진 검색(BinarySearch)를 사용한다.
이진 검색 관련 포스팅에서 필자가 작성한 내용 중, 이진 검색 사용을 위해 값들이 오름차순 또는 내림차순 정렬이 선행되어 있어야 한다는 내용이 있다. 즉, Java에서 Arrays로 배열 내 특정 값을 찾기 위해서는 먼저 대상 배열의 모든 요소를 오름차순이나 내림차순으로 정렬해야 한다. 먼저 정렬 없이 strArr1과 intARr1 배열에서 특정 값을 검색해보자. index가 제대로 나타나지 않음을 확인할 수 있다.
배열의 오름차순 정렬을 위해 Arrays는 sort()라는 매서드를 제공한다. sort()는 배열 내 요소 값을 비교하는데, 비교를 위해 각 요소들의 유니코드값을 사용한다. 이 매서드를 적용하여 strArr1과 intArr1이 정렬되는 내용을 확인하고, 다시 값들의 index를 검색해보자.
정상적으로 binarySearch()의 결과값이 반환됨을 알 수 있다.
만약 문자열 배열 내 각 요소의 시작 문자가 대/소문자가 뒤섞여 있는 경우라면 아스키/유니코드 값에 따라 대문자 단어들이 앞으로 배치된다. 하지만 sort() 매서드에 추가 매개변수로 String.CASE_INSENSITIVE_ORDER를 추가하면, 대소문자 관계없이 사전적인 순서에 따라 문자열이 정렬된다.
그럼, 여기서 의문이 든다. String.CASE_INSENSITIVE_ORDER는 무슨 역할을 하기에 대소문자 관계없이 값을 정렬할 수 있도록 만드는 것일까? String 클래스의 필드인 CASE_INSENSITIVE_ORDER는 아래와 같이 정의되어 있다.
내용을 보면 CASE_INSENSITIVE_ORDER는 "문자열을 정렬하는 Comparator"라고 적혀있다. 그럼, sort() 매서드와 Comparator는 무슨 관계인 것일까? 이제 Comparator와 Comparable 인터페이스에 대해 알아보자.
2. Comparable, Comparator 인터페이스
정수형 배열에서 sort() 매서드를 Arrays 클래스를 통해 호출하면 어떻게 자동으로 오름차순 정렬을 해주는 것일까? sort() 매서드에 대한 공식 문서의 설명을 보자.
sort()를 진행하는 배열 내 요소는 모두 Comparator에 정의되어 상호 비교가 가능(Mutually Comparable)해야 한다고 되어 있다. 그리고, sort()를 호출하면 Comparator 내부에 존재하는 compare()라는 매서드에 의해, 요소의 두 인자값이 비교되는 것이고 말이다. 즉, 값을 오름차순과 내림차순으로 비교하기 위해서는 Comparator 인터페이스가 적용되는 Wrapper, String, 날짜/시간 관련 클래스 등이 요소로 지정되어야 한다.
그럼, Comparator는 무엇일까?
Comparator는 함수형 인터페이스(* 추후 포스팅할 람다식에서 함수형 인터페이스에 대한 내용을 다룰 예정이다)의 일종으로 데이터 집단의 비교와 관련된 기능을 수행하는 인터페이스다. compare에 의해 반환되는 값은 1, 0, -1인데, 1이 반환되면 오름차순으로, -1이 반환되면 내림차순으로 정렬된다.
그럼, sort() 매서드의 두 번 째 매개인자를 Comparator.compare에 의해 반환되는 값을 직접 명시할 수는 없을까? 필자의 포스팅을 계속 따라오셨던 분이라면, 이제 Java Documentation에서 이는 불가능한 일이라는 것을 직접 확인할 수 있다.
즉, 아무리 열심히 sort(strArr1, -1)을 호출해봐야 돌아오는 것은 내림차순 결과가 아니라 예외라는 것이다. 여기서 잠깐 정수 객체 클래스인 Integer의 선언부를 살펴보자.
Integer는 Comparable이라는 인터페이스를 구현하고 있다. 위에서 본 Comparator와는 다른 인터페이스다. 하지만 이름만 다를 뿐 Comparable은 Comparator와 담당하는 기능은 유사하다.
차이점이 몇 가지 존재하는데, 우선 Comparator는 비교를 위해 compare()라는 이름의 매서드를 사용하고, Comparable은 compareTo()를 사용한다는 것이다.
두 번 째로 Comparable은 java.lang 패키지에 포함되어 있으나, Comparator는 java.util 패키지에 포함되어 있다.
세 번 째로 Comparable은 비교 대상 객체의 클래스에서 구현하며, compareTo() 매서드를 클래스 내에 오버라이딩으로 정의하면, 클래스 객체 간 비교 시 compareTo() 매서드 내용에 따라 기본 정렬이 진행된다. 하지만 Comparator는 비교할 객체 클래스에 적용하는 것이 아니라 정렬 기준을 정의하는데 사용한다.
마지막으로 Comparable은 단순히 기본 정렬기준을 구현하는데에만 사용되나 Comparator는 기본 정렬 기준 외에 프로그래머가 정의한 정렬 기준을 사용하고 싶을 때 Comparator를 구현한 매서드를 만들어 sort()의 두 번 째 매개변수로 반환값이 대입되도록 하면 된다. 이 때문에 위의 예시에서 나타난 strArr1과 intArr1은 내림차순을 바로 구현할 수 없다. 이를 구현하기 위해서는 Comparator를 별도의 클래스에 구현하는 과정을 거쳐야 한다.
** 추가 내용: Comparable 또한 기본 정렬 외에 다른 정렬 방식을 구현하는 것이 가능하다. 자세한 내용은 Stream 관련 포스팅을 참고하자.
필자는 sort()로 나온 결과를 역으로 출력하기 위해 내림차순(DescendingOrder)이라는 이름의 클래스를 선언하고, 정렬 기준을 만들기 위해 Comparator 인터페이스를 구현했다.
필자가 정의한 클래스의 compare 코드를 보면, Comparator를 구현한 DescendingOrder는 sort()에서 정렬을 진행할 배열 내 존재하는 요소들을 어떻게 비교할 것인지 그 방법을 정의한 것이 Comparator라고 보면 된다. 즉, 비교를 위해 사용되는 객체의 클래스 대부분이 기본 정렬 기준을 진행하는 Comparable만 구현하기 때문에 Comparable의 compareTo() 매서드를 사용하여 Comparator.compare() 매서드를 정의해주는 것이다. 반환값에 -1이 곱셈 연산이 된 것을 확인할 수 있는데, 이는 내림차순을 정의하기 위한 것이다.
DescendingOrder 클래스의 인스턴스를 sort() 매서드의 두 번 째 인자로 호출하고 결과를 보면 아래와 같이 나타난다.
만약 역순 배열도 대소문자 구분없이 진행하려면 compare() 매개변수인 obj1, obj2에 대한 추가 코드를 아래와 같이 작성해야 한다.
Comparable과 Comparator는 배열의 정렬과 관련되어 있어 반드시 알고 넘어가야하는 부분이나, Java에서 Comparable이 기본 정렬만을 담당하고, Comparator는 기본 정렬 외에 추가 정렬 방식을 지정할 수 있다는 것을 이해하지 못하면 학습에 있어서 정체되기 매우 쉬운 부분이다. 필자 역시 Comparable과 Comparator에 대한 내용이 잘 정리된 자료들이 없어서 Java Documentation과 몇 십 번의 에러들을 겪고 나서야 이틀만에 이 부분을 완전히 이해할 수 있게 되었다...(젠장 내 주말...)
다음 포스팅에서는 중복을 허용하지 않으나 자료의 순서는 고려하지 않는 Set에 대한 내용을 알아보려 한다. Set에서 가장 흔하게 사용하는 HashSet과 TreeSet에 대한 내용을 다룰 예정이다.
Fin.
'Java > Java Basic' 카테고리의 다른 글
[Java Basic] 37 - 컬렉션 프레임워크7 (HashMap, TreeMap, Properties) (0) | 2022.09.07 |
---|---|
[Java Basic] 36 - 컬렉션 프레임워크6 (HashSet, TreeSet) (0) | 2022.09.05 |
[Java Basic] 34 - 컬렉션 프레임워크4 (Iterator, ListIterator, Enumeration) (0) | 2022.09.02 |
[Java Basic] 33 - 컬렉션 프레임워크3 (Stack, Queue) (0) | 2022.09.01 |
[Java Basic] 32 - 컬렉션 프레임워크2 (ArrayList, LinkedList) (0) | 2022.09.01 |
댓글