개발자는 기록이 답이다

Comparator로 보는 인터페이스와 정적 메서드 본문

언어/Java

Comparator로 보는 인터페이스와 정적 메서드

slow-walker 2023. 12. 27. 14:41

 

인터페이스와 정적 메서드

자바 8과 9에서 주요 인터페이스(e.g. Comparator)의 변화가 일어났다.

 

  • 기본 메서드(defaultl method)와 정적 메소드를 가질 수 있다.
  • 기본 메소드
    • 인터페이스에서 메소드 선언 뿐 아니라, 기본적인 구현체까지 제공할 수 있다.
    • 기존의 인터페이스를 구현하는 클래스에 새로운 기능을 추가할 수 있다.
  • 정적 메소드
    • 자바 9부터 private static 메소드도 가질 수 있다.
    • 단, private필드는 아직도 선언할 수 없다.

 

자바 8 이전에는 interface에서 메소드선언만 가능하고 정의하는게 불가능했었다.

그런데 나는 자바 17을 사용하는데 왜 "Interface abstract methods cannot have body"라는 컴파일 에러가 나는것 일까?

 

인터페이스에서 구현체들이 인스턴스 타입의 메소드를 정의하려면 default를 사용해서 정의하거나, 인스턴스를 만들지 않고도 호출할 수 있는 정적 메서드를 정의해야 한다.

 

인터페이스에서는 정적 메서드에 접근 지시자가 생략되어있다면 기본적으로 public이고 주로 생략해서 쓴다.

(클래스타입에서는 접근 지시자가 생략되어 있다면 default인 것과 다르다)

 

자바 8에 public한 정적 메서드가 들어왔다면, 자바 9버전부터는 private한 정적 메서드를 사용할 수 있게 되었다.

만약 여러 public한 정적 메서드들 사이에 공통적인 작업들이 있을 경우, public으로 노출하고 싶지 않아서 생긴 것이다.

 

이런 기능들이 생긴 이후로 기존 API의 기능들이 풍부해지기 시작했다.

  • default 인스턴스 메서드
  • 스태틱한 메서드

이러한 인터페이스 개선은 Effective Java item1에서도 나오는 내용이다.
이름이 "Type"인 인터페이스를 반환하는 정적 메서드가 필요하면, "Types"라는 인스턴스화 불가인 동반 클래스를 만들어야 했었는데, 이제는 그럴 필요가 없다는 것이다.

 

인터페이스화 불가라는것은 인스턴스를 만들 수 없는 (private 생성자, final클래스 - 상속을 막기 위함) 불변 클래스를 사용하는 것을 말한다. Helper와 Utility클래스를 따로 만들지 않지 않고도 유틸리티 구현이 가능하다는 얘기다.

 

이와 관련된 내용을 Comparator를 사용한 코드를 통해 살펴보자.

 

Quiz 1) 내림차순으로 정렬하는 Comparator를 만들고 List<Integer>을 정렬하라

구현 방법에는 여러가지 방법을 사용할 수 있을 것이다.

 

1) 먼저 Comparator 익명 클래스를 선언하고 sort메서드에 전달하기

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

public class Quiz {

    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>(List.of(10, 20, 30, 40, 50, 60));
        System.out.println("Before : " + list);

        Comparator<Integer> desc = new Comparator<>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2 - o1;
            }
        };
        list.sort(desc);
        System.out.println("After : " + list);
    }
}

 

2) sort()내부에서 Comparator를 익명 클래스로 선언

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

public class Quiz {

    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>(List.of(10, 20, 30, 40, 50, 60));
        System.out.println("Before : " + list);
        list.sort(new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2.compareTo(o1);
            }
        });
        System.out.println("After : " + list);
    }
}

 

3) Collections클래스의 static메소드인 sort()사용해서 정렬할 목록과, 목록 순서를 결정하는 비교자를 전달

 

    public static <T> void sort(List<T> list, Comparator<? super T> c) {
        list.sort(c);
    }

 

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class Quiz {

    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>(List.of(10, 20, 30, 40, 50, 60));
        System.out.println("Before : " + list);

        Comparator<Integer> desc = new Comparator<>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2 - o1;
            }
        };
        Collections.sort(list, desc);
        System.out.println("After : " + list);
    }
}

 

 

4) 람다표현식을 사용해서 Comparator 정의하기

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class Quiz {

    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>(List.of(10, 20, 30, 40, 50, 60));
        System.out.println("Before : " + list);

        Comparator<Integer> desc = (o1, o2) -> o2 - o1; // Lambda
        Collections.sort(list, desc);
        System.out.println("After : " + list);
    }
}

 

Quiz 2) 퀴즈 1에서 만든 Comparator를 사용해서 오름차순으로 정렬하라

기존에 만든 desc코드를 건드리지 않고(asc 코드로 바꾸지 않고) 오름차순하는 방법 : reverse()메서드

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

public class Quiz {

    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>(List.of(10, 20, 30, 40, 50, 60));
        System.out.println("Before : " + list);

        Comparator<Integer> desc = (o1, o2) -> o2 - o1;
        list.sort(desc.reversed()); // 정적메서드가 아니라 인스턴스 메서드
        System.out.println("After : " + list);
    }
}

 

이렇게 재정렬을 할 수 있는 이유는 바로 Comparator 인터페이스의 변화 때문이다.

 

Comparator 인터페이스를 살펴보면 default 메서드와 static 메서드가 함께 선언된 것을 볼 수 있다.

 

  • 선언부만 있는 campare(), equals()
  • default메서드(인스턴스 메서드) : reverserd()
  • static메서드 : Comparator.reverseOrder()
package java.util;

import java.io.Serializable;
import java.util.function.Function;
import java.util.function.ToIntFunction;
import java.util.function.ToLongFunction;
import java.util.function.ToDoubleFunction;
import java.util.Comparators;


@FunctionalInterface
public interface Comparator<T> {
    /**
     * 두 인자를 비교하여 순서를 결정합니다. 첫 번째 인자가 두 번째 인자보다 작으면 음의 정수,
     * 같으면 0, 크면 양의 정수를 반환합니다.
     *
     * @param o1 비교할 첫 번째 객체입니다.
     * @param o2 비교할 두 번째 객체입니다.
     * @return 첫 번째 인자가 두 번째 인자보다 작으면 음의 정수, 같으면 0, 크면 양의 정수를 반환합니다.
     * @throws NullPointerException 인자가 null이고 이 comparator가 null 인자를 허용하지 않는 경우 발생합니다.
     * @throws ClassCastException 인자의 타입이 이 comparator에 의해 비교할 수 없는 경우 발생합니다.
     */
    int compare(T o1, T o2);

    /**
     * 다른 객체가 이 comparator와 "동일한지"를 나타냅니다.
     * 이 메서드는 {@link Object#equals(Object)}의 일반적인 규약을 따라야 합니다.
     * 또한 이 메서드는 지정된 객체도 comparator이며 이 comparator와 동일한 순서를 적용하는 경우에만
     * {@code true}를 반환할 수 있습니다.
     * 따라서 {@code comp1.equals(comp2)}는 모든 객체 참조 {@code o1} 및 {@code o2}에 대해
     * {@link Integer#signum signum}{@code (comp1.compare(o1,
     * o2))==signum(comp2.compare(o1, o2))}를 의미합니다.
     *
     * @param   obj   비교할 참조 객체입니다.
     * @return  지정된 객체도 comparator이며 이 comparator와 동일한 순서를 적용하는 경우에만
     *          {@code true}를 반환합니다.
     * @see Object#equals(Object)
     * @see Object#hashCode()
     */
    boolean equals(Object obj);

    /**
     * 이 comparator의 역순을 적용한 comparator를 반환합니다.
     *
     * @return 이 comparator의 역순을 적용한 comparator를 반환합니다.
     * @since 1.8
     */
    default Comparator<T> reversed() {
        return Collections.reverseOrder(this);
    }

    /**
     * <em>자연 순서</em>의 역순을 적용한 comparator를 반환합니다.
     *
     * <p>반환된 comparator는 직렬화 가능하며, 비교 시 {@code null}을 사용할 때
     * {@link NullPointerException}이 발생합니다.
     *
     * @param  <T> 비교할 요소의 {@link Comparable} 타입입니다.
     * @return {@code Comparable} 객체에 대한 <i>자연 순서</i>의 역순을 적용한 comparator를 반환합니다.
     * @see Comparable
     * @since 1.8
     */
    public static <T extends Comparable<? super T>> Comparator<T> reverseOrder() {
        return Collections.reverseOrder();
    }
}