Logo

[Java] 최소/최대 원소 구하기 (Loop/Collections/Stream)

자바에서 최소나 최대 원소를 찾을 흔히 사용되는 3가지 코딩 스타일에 대해서 알아보겠습니다.

Loop

제일 먼저 반복문을 통해 각 원소에 하나씩 접근해서 최대값을 구하는 다음과 같은 고전적인 코드를 생각해볼 수 있습니다.

List<Integer> numbers = List.of(4, 0, 5, 2, 7, 1, 8, 6, 9, 3);
int max = -1;
for (int i = 0; i < numbers.size(); i++) {
    int number = numbers.get(i);
    if (max < number) {
        max = number;
    }
}
System.out.println("Max: " + max); // Max: 9

반복문(for)을 이용해서 지속적으로 변수(max)에 새로운 값을 할당하는 방식이 전형적인 명령형 프로그래밍(Imperative programming) 스타일을 따르고 있습니다. 많은 프로그래머들에게 가장 익숙한 방법이지만, 고수준의 비지니스 로직 중간에 이러한 저수준의 코드가 들어가게 되면 가독성에 나쁜 영향을 줄 수 있어서 저 같은 경우에는 이러한 스타일의 코딩을 가급적 피하려고 합니다. 적어도 이러한 코드는 별도의 메소드로만 빼내주어도 다름 사람들이 코드를 읽기가 훨씬 수월해집니다.

참고로 List.of() 메소드는 불변 리스트 생성을 위해서 Java9에서 추가된 메서드 입니다. 자세한 내용은 관련 포스팅를 참고 바랍니다.

Collections.max() & Collections.min()

Collections 클래스의 max() 정적 메서드를 이용하면 좀 더 간단하게 최대값을 찾을 수 있습니다.

List<Integer> numbers = List.of(4, 0, 5, 2, 7, 1, 8, 6, 9, 3);
int max = numbers.isEmpty() ? -1 : Collections.max(numbers);
System.out.println("Max: " + max); // Max: 9

Collections.max() 메서드를 사용할 때 주의 사항은 리스트가 비어있을 경우 NoSuchElementException이 발생한다는 점입니다. 따라서 위와 같이 리스트가 비어있을 경우에 대한 기본값 처리가 필요합니다.

반대로 최소값은 Collections 클래스의 min() 정적 메서드를 사용하면 됩니다.

List<Integer> numbers = List.of(4, 0, 5, 2, 7, 1, 8, 6, 9, 3);
int min = numbers.isEmpty() ? -1 : Collections.min(numbers);
System.out.println("Min: " + min); // Min: 0

Collections 클래스의 max()min()는 리스트 타입의 인자만 받는다는 한계가 있습니다. 예를 들어, 최대값을 찾으려는 대상이 배열이라면 리스트로 일단 한 번 변환을 한 후 호출이 가능하기 때문에 원소의 개수가 많을 경우 비효율적일 수 있습니다.

Stream#max() & Stream#min()

Java8의 Stream API의 max() 메서드를 사용하면 좀 더 함수형 프로그래밍(Functional programming) 스타일에 가깝게 최대값을 구할 수 있습니다.

List<Integer> numbers = List.of(4, 0, 5, 2, 7, 1, 8, 6, 9, 3);
int max = numbers.stream().max(Integer::compare).orElse(-1);
System.out.println("Max: " + max); // Max: 9

Stream API의 max() 메서드는 최대 원소룰 담고 있는 Optinal 객체를 리턴하기 때문에 위와 같이 orElse() 메서드를 이용하여 자연스럽게 기본값 처리가 가능합니다. 또한 배열이 주어지든 리스트가 주어지든 스트림으로 처리가 가능하기 떄문에 parallelStream() 메서드를 활용해서 병렬 연산을 하면 성능 상의 이점까지 노려불 수 있습니다.

max() 메서드는 이 정보를 얻기 위해서 메소드 레퍼런스나 람다 함수를 인자로 받습니다. 최대값을 구하려면 원소 간에 대소 비교를 어떻게 해야하는 알아야하기 때문입니다. 위 첫 번째 코드에서 최대값을 구하기 위해서 for 루프 내부에 있던 대소 비교 코드를 람다 함수로 구현해서 인자로 넘긴다 생각하면 이해가 쉬울 겁니다.

위 코드에서 메서드 레퍼런스 부분을 람다 함수로 바꾸면 다음과 같이 작성할 수 있습니다. Comparator 인터페이스의 compare() 메서드에 부합하도록 비교할 두 개의 원소를 인자를 받고 비교 결과를 음수나 0, 양수로 리턴하는 람다 함수를 작성하면 됩니다.

int max = numbers.stream().max((x, y) -> x - y).orElse(Integer.MIN_VALUE);

반대로 최소값은 Stream API의 min() 메서드를 사용하면 됩니다.

List<Integer> numbers = List.of(4, 0, 5, 2, 7, 1, 8, 6, 9, 3);
int min = numbers.stream().min(Integer::compare).orElse(-1);
System.out.println("Min: " + min); // Min: 0

이상으로 자바에서 배열이나 리스트로 부터 최대 혹은 최소 원소를 찾는 여러 가지 방법에 대해서 간단히 살펴보았습니다.