글 작성자: 취업중인 피터팬
728x90

java에서 정렬을 할 때 빼고 이야기할 수 없는 객체가 ComparableComparator입니다.

이 두 가지를 봤을 때 다른 점은 어렵지 않으나 java안에서 사용하고 구현하는 것이 쉽지 않고 당황스럽습니다.

저자는 백준 정렬 문제를 풀면서 한 번은 게시해야겠다고 느껴 Comparable과 Comparator에 대해 내가 공부하고 이해한 것을 최대한 쉽게 정리해서 올리려 합니다..

 

보통 정렬 하면 어떤 것이 떠오르나요? 숫자로 된 배열을 정렬하는 방식이 떠오릅니다. 그리고 아래 코드는 제가 생각할 때 숫자를 정렬할 때 가장 많이 쓰이는 코드입니다.

import java.util.Arrays;

public class Main{
    public static void main(String[] args) {
        int[] array = {3,2,1,4,5,6,7,24,5,1,3};

        Arrays.sort(array);

        for(int i=0; i<array.length; i++) {
            System.out.printf("%d\t",array[i]);
        }
    }
}

 

 

보통 Arrays.sort를 사용해 정렬을 합니다. 하지만 코드를 짜다 보면 저렇게 평범한 정렬 방식을 만나지만은 않습니다. 하물며 내림차순으로 변경해서 정렬하려고 해도 해당 코드를 변경해주어야 합니다.

import java.util.Arrays;
import java.util.Comparator;

public class Main{
    public static void main(String[] args) {
        Integer[] array = {3,2,1,4,5,6,7,24,5,1,3};

        Arrays.sort(array, Comparator.reverseOrder());

        for(int i=0; i<array.length; i++) {
            System.out.printf("%d\t",array[i]);
        }
    }
}

 

 

해당 자료형을 Integer로 변경해 주고 Comparator.reverseOrder()를 인자로 넘겨주어야 합니다.

사실 Comparator.reverseOrder()은 Comparator의 객체입니다. 자바에서 내림차순으로 변경하기 쉽도록 해당 객체를 제공해 준 것이죠. 

원래는 다음과 같은 코드로 진행되어야 합니다.

import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;

public class Main{
    public static void main(String[] args) {
        Integer[] array = {3,2,1,4,5,6,7,24,5,1,3};

        Arrays.sort(array, new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2 - o1;
            }
        });

        for(int i=0; i<array.length; i++) {
            System.out.printf("%d\t",array[i]);
        }
    }
}

 

여기서 처음 볼 수 있는 게 Comparator입니다. 

Comparator은 무엇이길래 sort 함수의 인자로 넘겨지고 compare 함수를 Override 하는 것일까요?

그리고 Comparator 하면 항상 언급되는 Comparable은 무엇이고 언제 쓰이는 것일까요?

 

 


 

 

Comparable과 Comparator 모두 interface입니다. 즉, 해당 인터페이스 내에 구현된 모든 함수를 재정의 해야 한다는 것을 의미합니다.

 

각 인터페이스에서는 어떤 함수가 정의되어 있을까요? 공식 api 문서를 한번 보겠습니다.

 

 

[Comparable]

https://docs.oracle.com/javase/8/docs/api/java/lang/Comparable.html#method.summary

 

Comparable (Java Platform SE 8 )

This interface imposes a total ordering on the objects of each class that implements it. This ordering is referred to as the class's natural ordering, and the class's compareTo method is referred to as its natural comparison method. Lists (and arrays) of o

docs.oracle.com

 

[Compartor]

https://docs.oracle.com/javase/8/docs/api/java/util/Comparator.html#method.summary

 

Comparator (Java Platform SE 8 )

Compares its two arguments for order. Returns a negative integer, zero, or a positive integer as the first argument is less than, equal to, or greater than the second. In the foregoing description, the notation sgn(expression) designates the mathematical s

docs.oracle.com

 

 

 

 

공식 문서와 함께 실제로 자바에서 만들어져 있는 Comparable과 Comparator의 코드를 보겠습니다.

 

[Comparable] - package java.lang에 라이브러리 형식으로 들어가 있습니다.

더보기
/*
 * Copyright (c) 1997, 2021, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package java.lang;
import java.util.*;

/**
 * This interface imposes a total ordering on the objects of each class that
 * implements it.  This ordering is referred to as the class's <i>natural
 * ordering</i>, and the class's {@code compareTo} method is referred to as
 * its <i>natural comparison method</i>.<p>
 *
 * Lists (and arrays) of objects that implement this interface can be sorted
 * automatically by {@link Collections#sort(List) Collections.sort} (and
 * {@link Arrays#sort(Object[]) Arrays.sort}).  Objects that implement this
 * interface can be used as keys in a {@linkplain SortedMap sorted map} or as
 * elements in a {@linkplain SortedSet sorted set}, without the need to
 * specify a {@linkplain Comparator comparator}.<p>
 *
 * The natural ordering for a class {@code C} is said to be <i>consistent
 * with equals</i> if and only if {@code e1.compareTo(e2) == 0} has
 * the same boolean value as {@code e1.equals(e2)} for every
 * {@code e1} and {@code e2} of class {@code C}.  Note that {@code null}
 * is not an instance of any class, and {@code e.compareTo(null)} should
 * throw a {@code NullPointerException} even though {@code e.equals(null)}
 * returns {@code false}.<p>
 *
 * It is strongly recommended (though not required) that natural orderings be
 * consistent with equals.  This is so because sorted sets (and sorted maps)
 * without explicit comparators behave "strangely" when they are used with
 * elements (or keys) whose natural ordering is inconsistent with equals.  In
 * particular, such a sorted set (or sorted map) violates the general contract
 * for set (or map), which is defined in terms of the {@code equals}
 * method.<p>
 *
 * For example, if one adds two keys {@code a} and {@code b} such that
 * {@code (!a.equals(b) && a.compareTo(b) == 0)} to a sorted
 * set that does not use an explicit comparator, the second {@code add}
 * operation returns false (and the size of the sorted set does not increase)
 * because {@code a} and {@code b} are equivalent from the sorted set's
 * perspective.<p>
 *
 * Virtually all Java core classes that implement {@code Comparable}
 * have natural orderings that are consistent with equals.  One
 * exception is {@link java.math.BigDecimal}, whose {@linkplain
 * java.math.BigDecimal#compareTo natural ordering} equates {@code
 * BigDecimal} objects with equal numerical values and different
 * representations (such as 4.0 and 4.00). For {@link
 * java.math.BigDecimal#equals BigDecimal.equals()} to return true,
 * the representation and numerical value of the two {@code
 * BigDecimal} objects must be the same.<p>
 *
 * For the mathematically inclined, the <i>relation</i> that defines
 * the natural ordering on a given class C is:<pre>{@code
 *       {(x, y) such that x.compareTo(y) <= 0}.
 * }</pre> The <i>quotient</i> for this total order is: <pre>{@code
 *       {(x, y) such that x.compareTo(y) == 0}.
 * }</pre>
 *
 * It follows immediately from the contract for {@code compareTo} that the
 * quotient is an <i>equivalence relation</i> on {@code C}, and that the
 * natural ordering is a <i>total order</i> on {@code C}.  When we say that a
 * class's natural ordering is <i>consistent with equals</i>, we mean that the
 * quotient for the natural ordering is the equivalence relation defined by
 * the class's {@link Object#equals(Object) equals(Object)} method:<pre>
 *     {(x, y) such that x.equals(y)}. </pre><p>
 *
 * In other words, when a class's natural ordering is consistent with
 * equals, the equivalence classes defined by the equivalence relation
 * of the {@code equals} method and the equivalence classes defined by
 * the quotient of the {@code compareTo} method are the same.
 *
 * <p>This interface is a member of the
 * <a href="{@docRoot}/java.base/java/util/package-summary.html#CollectionsFramework">
 * Java Collections Framework</a>.
 *
 * @param <T> the type of objects that this object may be compared to
 *
 * @author  Josh Bloch
 * @see java.util.Comparator
 * @since 1.2
 */
public interface Comparable<T> {
    /**
     * Compares this object with the specified object for order.  Returns a
     * negative integer, zero, or a positive integer as this object is less
     * than, equal to, or greater than the specified object.
     *
     * <p>The implementor must ensure {@link Integer#signum
     * signum}{@code (x.compareTo(y)) == -signum(y.compareTo(x))} for
     * all {@code x} and {@code y}.  (This implies that {@code
     * x.compareTo(y)} must throw an exception if and only if {@code
     * y.compareTo(x)} throws an exception.)
     *
     * <p>The implementor must also ensure that the relation is transitive:
     * {@code (x.compareTo(y) > 0 && y.compareTo(z) > 0)} implies
     * {@code x.compareTo(z) > 0}.
     *
     * <p>Finally, the implementor must ensure that {@code
     * x.compareTo(y)==0} implies that {@code signum(x.compareTo(z))
     * == signum(y.compareTo(z))}, for all {@code z}.
     *
     * @apiNote
     * It is strongly recommended, but <i>not</i> strictly required that
     * {@code (x.compareTo(y)==0) == (x.equals(y))}.  Generally speaking, any
     * class that implements the {@code Comparable} interface and violates
     * this condition should clearly indicate this fact.  The recommended
     * language is "Note: this class has a natural ordering that is
     * inconsistent with equals."
     *
     * @param   o the object to be compared.
     * @return  a negative integer, zero, or a positive integer as this object
     *          is less than, equal to, or greater than the specified object.
     *
     * @throws NullPointerException if the specified object is null
     * @throws ClassCastException if the specified object's type prevents it
     *         from being compared to this object.
     */
    public int compareTo(T o);
}

 

[Comparator] - package java.util에 라이브러리 형식으로 들어가 있습니다.

더보기
/*
 * Copyright (c) 1997, 2021, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

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;

/**
 * A comparison function, which imposes a <i>total ordering</i> on
 * some collection of objects.  Comparators can be passed to a sort
 * method (such as {@link Collections#sort(List,Comparator)
 * Collections.sort} or {@link Arrays#sort(Object[],Comparator)
 * Arrays.sort}) to allow precise control over the sort order.
 * Comparators can also be used to control the order of certain data
 * structures (such as {@linkplain SortedSet sorted sets} or
 * {@linkplain SortedMap sorted maps}), or to provide an ordering for
 * collections of objects that don't have a {@linkplain Comparable
 * natural ordering}.<p>
 *
 * The ordering imposed by a comparator {@code c} on a set of elements
 * {@code S} is said to be <i>consistent with equals</i> if and only if
 * {@code c.compare(e1, e2)==0} has the same boolean value as
 * {@code e1.equals(e2)} for every {@code e1} and {@code e2} in
 * {@code S}.<p>
 *
 * Caution should be exercised when using a comparator capable of imposing an
 * ordering inconsistent with equals to order a sorted set (or sorted map).
 * Suppose a sorted set (or sorted map) with an explicit comparator {@code c}
 * is used with elements (or keys) drawn from a set {@code S}.  If the
 * ordering imposed by {@code c} on {@code S} is inconsistent with equals,
 * the sorted set (or sorted map) will behave "strangely."  In particular the
 * sorted set (or sorted map) will violate the general contract for set (or
 * map), which is defined in terms of {@code equals}.<p>
 *
 * For example, suppose one adds two elements {@code a} and {@code b} such that
 * {@code (a.equals(b) && c.compare(a, b) != 0)}
 * to an empty {@code TreeSet} with comparator {@code c}.
 * The second {@code add} operation will return
 * true (and the size of the tree set will increase) because {@code a} and
 * {@code b} are not equivalent from the tree set's perspective, even though
 * this is contrary to the specification of the
 * {@link Set#add Set.add} method.<p>
 *
 * Note: It is generally a good idea for comparators to also implement
 * {@code java.io.Serializable}, as they may be used as ordering methods in
 * serializable data structures (like {@link TreeSet}, {@link TreeMap}).  In
 * order for the data structure to serialize successfully, the comparator (if
 * provided) must implement {@code Serializable}.<p>
 *
 * For the mathematically inclined, the <i>relation</i> that defines the
 * <i>imposed ordering</i> that a given comparator {@code c} imposes on a
 * given set of objects {@code S} is:<pre>
 *       {(x, y) such that c.compare(x, y) &lt;= 0}.
 * </pre> The <i>quotient</i> for this total order is:<pre>
 *       {(x, y) such that c.compare(x, y) == 0}.
 * </pre>
 *
 * It follows immediately from the contract for {@code compare} that the
 * quotient is an <i>equivalence relation</i> on {@code S}, and that the
 * imposed ordering is a <i>total order</i> on {@code S}.  When we say that
 * the ordering imposed by {@code c} on {@code S} is <i>consistent with
 * equals</i>, we mean that the quotient for the ordering is the equivalence
 * relation defined by the objects' {@link Object#equals(Object)
 * equals(Object)} method(s):<pre>
 *     {(x, y) such that x.equals(y)}. </pre>
 *
 * In other words, when the imposed ordering is consistent with
 * equals, the equivalence classes defined by the equivalence relation
 * of the {@code equals} method and the equivalence classes defined by
 * the quotient of the {@code compare} method are the same.

 * <p>Unlike {@code Comparable}, a comparator may optionally permit
 * comparison of null arguments, while maintaining the requirements for
 * an equivalence relation.
 *
 * <p>This interface is a member of the
 * <a href="{@docRoot}/java.base/java/util/package-summary.html#CollectionsFramework">
 * Java Collections Framework</a>.
 *
 * @param <T> the type of objects that may be compared by this comparator
 *
 * @author  Josh Bloch
 * @author  Neal Gafter
 * @see Comparable
 * @see java.io.Serializable
 * @since 1.2
 */
@FunctionalInterface
public interface Comparator<T> {
    /**
     * Compares its two arguments for order.  Returns a negative integer,
     * zero, or a positive integer as the first argument is less than, equal
     * to, or greater than the second.<p>
     *
     * The implementor must ensure that {@link Integer#signum
     * signum}{@code (compare(x, y)) == -signum(compare(y, x))} for
     * all {@code x} and {@code y}.  (This implies that {@code
     * compare(x, y)} must throw an exception if and only if {@code
     * compare(y, x)} throws an exception.)<p>
     *
     * The implementor must also ensure that the relation is transitive:
     * {@code ((compare(x, y)>0) && (compare(y, z)>0))} implies
     * {@code compare(x, z)>0}.<p>
     *
     * Finally, the implementor must ensure that {@code compare(x,
     * y)==0} implies that {@code signum(compare(x,
     * z))==signum(compare(y, z))} for all {@code z}.
     *
     * @apiNote
     * It is generally the case, but <i>not</i> strictly required that
     * {@code (compare(x, y)==0) == (x.equals(y))}.  Generally speaking,
     * any comparator that violates this condition should clearly indicate
     * this fact.  The recommended language is "Note: this comparator
     * imposes orderings that are inconsistent with equals."
     *
     * @param o1 the first object to be compared.
     * @param o2 the second object to be compared.
     * @return a negative integer, zero, or a positive integer as the
     *         first argument is less than, equal to, or greater than the
     *         second.
     * @throws NullPointerException if an argument is null and this
     *         comparator does not permit null arguments
     * @throws ClassCastException if the arguments' types prevent them from
     *         being compared by this comparator.
     */
    int compare(T o1, T o2);

    /**
     * Indicates whether some other object is &quot;equal to&quot;
     * this comparator.  This method must obey the general contract of
     * {@link Object#equals(Object)}.  Additionally, this method can
     * return {@code true} <i>only</i> if the specified object is also
     * a comparator and it imposes the same ordering as this
     * comparator.  Thus, {@code comp1.equals(comp2)} implies that
     * {@link Integer#signum signum}{@code (comp1.compare(o1,
     * o2))==signum(comp2.compare(o1, o2))} for every object reference
     * {@code o1} and {@code o2}.<p>
     *
     * Note that it is <i>always</i> safe <i>not</i> to override
     * {@code Object.equals(Object)}.  However, overriding this method may,
     * in some cases, improve performance by allowing programs to determine
     * that two distinct comparators impose the same order.
     *
     * @param   obj   the reference object with which to compare.
     * @return  {@code true} only if the specified object is also
     *          a comparator and it imposes the same ordering as this
     *          comparator.
     * @see Object#equals(Object)
     * @see Object#hashCode()
     */
    boolean equals(Object obj);

    /**
     * Returns a comparator that imposes the reverse ordering of this
     * comparator.
     *
     * @return a comparator that imposes the reverse ordering of this
     *         comparator.
     * @since 1.8
     */
    default Comparator<T> reversed() {
        return Collections.reverseOrder(this);
    }

    /**
     * Returns a lexicographic-order comparator with another comparator.
     * If this {@code Comparator} considers two elements equal, i.e.
     * {@code compare(a, b) == 0}, {@code other} is used to determine the order.
     *
     * <p>The returned comparator is serializable if the specified comparator
     * is also serializable.
     *
     * @apiNote
     * For example, to sort a collection of {@code String} based on the length
     * and then case-insensitive natural ordering, the comparator can be
     * composed using following code,
     *
     * <pre>{@code
     *     Comparator<String> cmp = Comparator.comparingInt(String::length)
     *             .thenComparing(String.CASE_INSENSITIVE_ORDER);
     * }</pre>
     *
     * @param  other the other comparator to be used when this comparator
     *         compares two objects that are equal.
     * @return a lexicographic-order comparator composed of this and then the
     *         other comparator
     * @throws NullPointerException if the argument is null.
     * @since 1.8
     */
    default Comparator<T> thenComparing(Comparator<? super T> other) {
        Objects.requireNonNull(other);
        return (Comparator<T> & Serializable) (c1, c2) -> {
            int res = compare(c1, c2);
            return (res != 0) ? res : other.compare(c1, c2);
        };
    }

    /**
     * Returns a lexicographic-order comparator with a function that
     * extracts a key to be compared with the given {@code Comparator}.
     *
     * @implSpec This default implementation behaves as if {@code
     *           thenComparing(comparing(keyExtractor, cmp))}.
     *
     * @param  <U>  the type of the sort key
     * @param  keyExtractor the function used to extract the sort key
     * @param  keyComparator the {@code Comparator} used to compare the sort key
     * @return a lexicographic-order comparator composed of this comparator
     *         and then comparing on the key extracted by the keyExtractor function
     * @throws NullPointerException if either argument is null.
     * @see #comparing(Function, Comparator)
     * @see #thenComparing(Comparator)
     * @since 1.8
     */
    default <U> Comparator<T> thenComparing(
            Function<? super T, ? extends U> keyExtractor,
            Comparator<? super U> keyComparator)
    {
        return thenComparing(comparing(keyExtractor, keyComparator));
    }

    /**
     * Returns a lexicographic-order comparator with a function that
     * extracts a {@code Comparable} sort key.
     *
     * @implSpec This default implementation behaves as if {@code
     *           thenComparing(comparing(keyExtractor))}.
     *
     * @param  <U>  the type of the {@link Comparable} sort key
     * @param  keyExtractor the function used to extract the {@link
     *         Comparable} sort key
     * @return a lexicographic-order comparator composed of this and then the
     *         {@link Comparable} sort key.
     * @throws NullPointerException if the argument is null.
     * @see #comparing(Function)
     * @see #thenComparing(Comparator)
     * @since 1.8
     */
    default <U extends Comparable<? super U>> Comparator<T> thenComparing(
            Function<? super T, ? extends U> keyExtractor)
    {
        return thenComparing(comparing(keyExtractor));
    }

    /**
     * Returns a lexicographic-order comparator with a function that
     * extracts an {@code int} sort key.
     *
     * @implSpec This default implementation behaves as if {@code
     *           thenComparing(comparingInt(keyExtractor))}.
     *
     * @param  keyExtractor the function used to extract the integer sort key
     * @return a lexicographic-order comparator composed of this and then the
     *         {@code int} sort key
     * @throws NullPointerException if the argument is null.
     * @see #comparingInt(ToIntFunction)
     * @see #thenComparing(Comparator)
     * @since 1.8
     */
    default Comparator<T> thenComparingInt(ToIntFunction<? super T> keyExtractor) {
        return thenComparing(comparingInt(keyExtractor));
    }

    /**
     * Returns a lexicographic-order comparator with a function that
     * extracts a {@code long} sort key.
     *
     * @implSpec This default implementation behaves as if {@code
     *           thenComparing(comparingLong(keyExtractor))}.
     *
     * @param  keyExtractor the function used to extract the long sort key
     * @return a lexicographic-order comparator composed of this and then the
     *         {@code long} sort key
     * @throws NullPointerException if the argument is null.
     * @see #comparingLong(ToLongFunction)
     * @see #thenComparing(Comparator)
     * @since 1.8
     */
    default Comparator<T> thenComparingLong(ToLongFunction<? super T> keyExtractor) {
        return thenComparing(comparingLong(keyExtractor));
    }

    /**
     * Returns a lexicographic-order comparator with a function that
     * extracts a {@code double} sort key.
     *
     * @implSpec This default implementation behaves as if {@code
     *           thenComparing(comparingDouble(keyExtractor))}.
     *
     * @param  keyExtractor the function used to extract the double sort key
     * @return a lexicographic-order comparator composed of this and then the
     *         {@code double} sort key
     * @throws NullPointerException if the argument is null.
     * @see #comparingDouble(ToDoubleFunction)
     * @see #thenComparing(Comparator)
     * @since 1.8
     */
    default Comparator<T> thenComparingDouble(ToDoubleFunction<? super T> keyExtractor) {
        return thenComparing(comparingDouble(keyExtractor));
    }

    /**
     * Returns a comparator that imposes the reverse of the <em>natural
     * ordering</em>.
     *
     * <p>The returned comparator is serializable and throws {@link
     * NullPointerException} when comparing {@code null}.
     *
     * @param  <T> the {@link Comparable} type of element to be compared
     * @return a comparator that imposes the reverse of the <i>natural
     *         ordering</i> on {@code Comparable} objects.
     * @see Comparable
     * @since 1.8
     */
    public static <T extends Comparable<? super T>> Comparator<T> reverseOrder() {
        return Collections.reverseOrder();
    }

    /**
     * Returns a comparator that compares {@link Comparable} objects in natural
     * order.
     *
     * <p>The returned comparator is serializable and throws {@link
     * NullPointerException} when comparing {@code null}.
     *
     * @param  <T> the {@link Comparable} type of element to be compared
     * @return a comparator that imposes the <i>natural ordering</i> on {@code
     *         Comparable} objects.
     * @see Comparable
     * @since 1.8
     */
    @SuppressWarnings("unchecked")
    public static <T extends Comparable<? super T>> Comparator<T> naturalOrder() {
        return (Comparator<T>) Comparators.NaturalOrderComparator.INSTANCE;
    }

    /**
     * Returns a null-friendly comparator that considers {@code null} to be
     * less than non-null. When both are {@code null}, they are considered
     * equal. If both are non-null, the specified {@code Comparator} is used
     * to determine the order. If the specified comparator is {@code null},
     * then the returned comparator considers all non-null values to be equal.
     *
     * <p>The returned comparator is serializable if the specified comparator
     * is serializable.
     *
     * @param  <T> the type of the elements to be compared
     * @param  comparator a {@code Comparator} for comparing non-null values
     * @return a comparator that considers {@code null} to be less than
     *         non-null, and compares non-null objects with the supplied
     *         {@code Comparator}.
     * @since 1.8
     */
    public static <T> Comparator<T> nullsFirst(Comparator<? super T> comparator) {
        return new Comparators.NullComparator<>(true, comparator);
    }

    /**
     * Returns a null-friendly comparator that considers {@code null} to be
     * greater than non-null. When both are {@code null}, they are considered
     * equal. If both are non-null, the specified {@code Comparator} is used
     * to determine the order. If the specified comparator is {@code null},
     * then the returned comparator considers all non-null values to be equal.
     *
     * <p>The returned comparator is serializable if the specified comparator
     * is serializable.
     *
     * @param  <T> the type of the elements to be compared
     * @param  comparator a {@code Comparator} for comparing non-null values
     * @return a comparator that considers {@code null} to be greater than
     *         non-null, and compares non-null objects with the supplied
     *         {@code Comparator}.
     * @since 1.8
     */
    public static <T> Comparator<T> nullsLast(Comparator<? super T> comparator) {
        return new Comparators.NullComparator<>(false, comparator);
    }

    /**
     * Accepts a function that extracts a sort key from a type {@code T}, and
     * returns a {@code Comparator<T>} that compares by that sort key using
     * the specified {@link Comparator}.
     *
     * <p>The returned comparator is serializable if the specified function
     * and comparator are both serializable.
     *
     * @apiNote
     * For example, to obtain a {@code Comparator} that compares {@code
     * Person} objects by their last name ignoring case differences,
     *
     * <pre>{@code
     *     Comparator<Person> cmp = Comparator.comparing(
     *             Person::getLastName,
     *             String.CASE_INSENSITIVE_ORDER);
     * }</pre>
     *
     * @param  <T> the type of element to be compared
     * @param  <U> the type of the sort key
     * @param  keyExtractor the function used to extract the sort key
     * @param  keyComparator the {@code Comparator} used to compare the sort key
     * @return a comparator that compares by an extracted key using the
     *         specified {@code Comparator}
     * @throws NullPointerException if either argument is null
     * @since 1.8
     */
    public static <T, U> Comparator<T> comparing(
            Function<? super T, ? extends U> keyExtractor,
            Comparator<? super U> keyComparator)
    {
        Objects.requireNonNull(keyExtractor);
        Objects.requireNonNull(keyComparator);
        return (Comparator<T> & Serializable)
            (c1, c2) -> keyComparator.compare(keyExtractor.apply(c1),
                                              keyExtractor.apply(c2));
    }

    /**
     * Accepts a function that extracts a {@link java.lang.Comparable
     * Comparable} sort key from a type {@code T}, and returns a {@code
     * Comparator<T>} that compares by that sort key.
     *
     * <p>The returned comparator is serializable if the specified function
     * is also serializable.
     *
     * @apiNote
     * For example, to obtain a {@code Comparator} that compares {@code
     * Person} objects by their last name,
     *
     * <pre>{@code
     *     Comparator<Person> byLastName = Comparator.comparing(Person::getLastName);
     * }</pre>
     *
     * @param  <T> the type of element to be compared
     * @param  <U> the type of the {@code Comparable} sort key
     * @param  keyExtractor the function used to extract the {@link
     *         Comparable} sort key
     * @return a comparator that compares by an extracted key
     * @throws NullPointerException if the argument is null
     * @since 1.8
     */
    public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
            Function<? super T, ? extends U> keyExtractor)
    {
        Objects.requireNonNull(keyExtractor);
        return (Comparator<T> & Serializable)
            (c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
    }

    /**
     * Accepts a function that extracts an {@code int} sort key from a type
     * {@code T}, and returns a {@code Comparator<T>} that compares by that
     * sort key.
     *
     * <p>The returned comparator is serializable if the specified function
     * is also serializable.
     *
     * @param  <T> the type of element to be compared
     * @param  keyExtractor the function used to extract the integer sort key
     * @return a comparator that compares by an extracted key
     * @see #comparing(Function)
     * @throws NullPointerException if the argument is null
     * @since 1.8
     */
    public static <T> Comparator<T> comparingInt(ToIntFunction<? super T> keyExtractor) {
        Objects.requireNonNull(keyExtractor);
        return (Comparator<T> & Serializable)
            (c1, c2) -> Integer.compare(keyExtractor.applyAsInt(c1), keyExtractor.applyAsInt(c2));
    }

    /**
     * Accepts a function that extracts a {@code long} sort key from a type
     * {@code T}, and returns a {@code Comparator<T>} that compares by that
     * sort key.
     *
     * <p>The returned comparator is serializable if the specified function is
     * also serializable.
     *
     * @param  <T> the type of element to be compared
     * @param  keyExtractor the function used to extract the long sort key
     * @return a comparator that compares by an extracted key
     * @see #comparing(Function)
     * @throws NullPointerException if the argument is null
     * @since 1.8
     */
    public static <T> Comparator<T> comparingLong(ToLongFunction<? super T> keyExtractor) {
        Objects.requireNonNull(keyExtractor);
        return (Comparator<T> & Serializable)
            (c1, c2) -> Long.compare(keyExtractor.applyAsLong(c1), keyExtractor.applyAsLong(c2));
    }

    /**
     * Accepts a function that extracts a {@code double} sort key from a type
     * {@code T}, and returns a {@code Comparator<T>} that compares by that
     * sort key.
     *
     * <p>The returned comparator is serializable if the specified function
     * is also serializable.
     *
     * @param  <T> the type of element to be compared
     * @param  keyExtractor the function used to extract the double sort key
     * @return a comparator that compares by an extracted key
     * @see #comparing(Function)
     * @throws NullPointerException if the argument is null
     * @since 1.8
     */
    public static<T> Comparator<T> comparingDouble(ToDoubleFunction<? super T> keyExtractor) {
        Objects.requireNonNull(keyExtractor);
        return (Comparator<T> & Serializable)
            (c1, c2) -> Double.compare(keyExtractor.applyAsDouble(c1), keyExtractor.applyAsDouble(c2));
    }
}

 

살펴보시면 아시겠지만 Comparable과 달리 Comparator은 메서드가 여러 개 있습니다. 

이것에 대해 궁금하시다면 아래 글을 펴보시면 됩니다.

더보기

원래는 INTERFACE라 함은 추상메서드와 상수로 이루어진 CLASS라고 생각하시면 됩니다.

추상메서드는 안에 내용이 없는 껍데기 메서드라고 생각하시면 됩니다.

 

예를 들어 사람이라는 INTERFACE가 있다고 가정해 봅시다.

public interface People {

    public static final int eyes = 2;
    int noes = 1;//제어자는 생략 가능
    int hand = 2;//제어자는 생략 가능

    public abstract void eat(String food);
    void sleep();//제어자는 생략 가능
}

 

상수와 추상메서드의 접근제어자와 선언문은 인터페이스에서는 모두 공통임으로 생략이 가능합니다.

해당 인터페이스는 구현하기 위해서는 상속이 필수적으로 필요합니다.

class Student_people implements People{
    
    @Override
    public void eat(String food) {
        System.out.println("i'm eat " + food);
    }

    @Override
    public void sleep() {
        System.out.println("Sleeping..");
    }
}

 

위와 같이 상속받은 클래스에서 모든 추상 메서드를 구현해 주어야 정상적으로 인터페이스를 사용할 수 있습니다. 

 

하지만 java8부터는 인터페이스에 일반 메서드를 구현할 수 있도록 개정되었습니다. comparator 인터페이스 안에는 대부분 default 함수로 이루어져 있는 것을 볼 수 있습니다.

아래 Comparator 인터페이스 코드를 보면 반가운 함수가 보입니다.

 public static <T extends Comparable<? super T>> Comparator<T> reverseOrder() {
        return Collections.reverseOrder();
    }

 

저희가 내림차순으로 sort를 하기 위해 사용했던 메서드입니다. 사실 Collections.reverseOrder() 함수를 사용해도 내림차순으로 만들어 주는 데는 큰 무리가 없습니다. Sort에서 Comparator인자를 받고 있기 때문에  Comparator을 명시적으로 넣어주는 게 좀 더 좋습니다. 인터페이스에 일반 메서드를 넣을 수 있게 됨으로 위와 같은 코드를 작성할 수 있게 된 것입니다.

그리고 모든 메서드를 재정의 할 필요 없이 추상메서드만 정의하면 되기 때문에 compara 함수만 재정의 해도 문제없이 코드를 돌릴 수 있게 된 것입니다.

 

 


 

그럼 본격적으로 Comparable과 Comparator이 무슨 일을 하는지 알아보겠습니다.

 

위에서는 배열을 정렬하는 데 해당 인터페이스를 구현했습니다.

보통 많은 사람들이 해당 인터페이스를 정렬하는 데 사용한다고 생각합니다.

틀린 말은 아닙니다. 실제로 정렬하는데 가장 많이 등장하는 인터페이스이기도 합니다.

하지만 중요한 건 그것이 실제 기능은 아니라는 것입니다. 

 

Comparable과 Comparator의 실제 기능은 객체를 비교하는 것입니다.

 

 

객체를 비교한다? 무슨 소리일까요?

 

원래 무언가를 비교할 때 비교 연산자를 사용합니다.

public class Main {
    public static void main(String[] args) {
        Integer[] array = {3, 2, 1, 4, 5, 6, 7, 24, 5, 1, 3};

        if (array[0] > array[1]) {
            System.out.println("[0]이 [1]보다 큽니다.");
        } else if (array[0] < array[1]) {
            System.out.println("[0]이 [1]보다 작습니다.");
        } else {
            System.out.println("[0]와 [1]이 같습니다.");
        }
    }
}

 

 

너무 쉽고 당연한 이야기를 했나요?

해당 비교 연산자로 웬만한 값들은 비교할 수 있습니다.

숫자, 문자, 길이 등등 많은 것들을 비교할 수 있습니다. 

하지만 객체를 비교하기 위해서는 어떻게 해야 할까요?

public class Main {
    public static void main(String[] args) {
        Computer com1 = new Computer(1,1,1,0);
        Computer com2 = new Computer(2,1,1,1);
        
        if(com1 > com2){
            System.out.println("com1이 com2보다 큽니다.");
        }

    }
}

class Computer {
    public int monitor;
    public int keyboard;
    public int mouse;
    public int speaker;

    public Computer(int monitor, int keyboard, int mouse, int speaker) {
        this.monitor = monitor;
        this.keyboard = keyboard;
        this.mouse = mouse;
        this.speaker = speaker;
    }

    public void pri_interface(){
        System.out.println("모니터가 " + monitor + "개 연결되어 있습니다.");
        System.out.println("키보드가 " + keyboard + "개 연결되어 있습니다.");
        System.out.println("마우스가 " + mouse + "개 연결되어 있습니다.");
        System.out.println("스피커가 " + speaker + "개 연결되어 있습니다.");
    }

    public void calculation(int a, int b){
        int sum = a+b;
        System.out.println("a+b=" + sum);
    }
}

 

위와 같이 코드를 짜게 되면 에러가 나게 됩니다.

딱 봐도 기준이 없으니깐 어떤 게 크고 어떤 게 작은지 알 수가 없습니다.

그래서 코드도 에러가 나는 것이죠.

그래서 객체를 비교할 때는 기준이 필요합니다. 아래코드를 보겠습니다.

public class Main {
    public static void main(String[] args) {
        Computer com1 = new Computer(1,1,1,0);
        Computer com2 = new Computer(2,1,1,1);

        if(com1.monitor > com2.monitor){
            System.out.println("com1의 입출력 장치가 com2의 입출력 장치보다 많습니다.");
        }else if(com1.monitor < com2.monitor){
            System.out.println("com1의 입출력 장치가 com2의 입출력 장치보다 적습니다.");
        }else{
            System.out.println("com1의 입출력 장치와 com2의 입출력 장치가 같습니다.");
        }

    }
}

class Computer {
    public int monitor;
    public int keyboard;
    public int mouse;
    public int speaker;

    public Computer(int monitor, int keyboard, int mouse, int speaker) {
        this.monitor = monitor;
        this.keyboard = keyboard;
        this.mouse = mouse;
        this.speaker = speaker;
    }

    public void pri_interface(){
        System.out.println("모니터가 " + monitor + "개 연결되어 있습니다.");
        System.out.println("키보드가 " + keyboard + "개 연결되어 있습니다.");
        System.out.println("마우스가 " + mouse + "개 연결되어 있습니다.");
        System.out.println("스피커가 " + speaker + "개 연결되어 있습니다.");
    }

    public void calculation(int a, int b){
        int sum = a+b;
        System.out.println("a+b=" + sum);
    }
}

 

 

위에 코드를 보면 모니터의 개수가 객체를 비교하는 기준이 됩니다. 

근데 이 객체를 비교하는 기준을 매번 사람들한테 모니터가 기준이야!라고 알려주기도 번거롭고 보통 모니터를 통해서 개수 비교를 하는 일이 많다고 생각해 봅시다.

좀 더 코드의 가독성을 높이기 위해 Comparable을 구현해 놓는 것입니다.

public class Main {
    public static void main(String[] args) {
        Computer com1 = new Computer(1,1,1,0);
        Computer com2 = new Computer(2,1,1,1);

        if(com1.compareTo(com2) > 0){
            System.out.println("com1의 입출력 장치가 com2의 입출력 장치보다 많습니다.");
        } else if (com1.compareTo(com2) < 0) {
            System.out.println("com1의 입출력 장치가 com2의 입출력 장치보다 적습니다.");
        } else{
            System.out.println("com1의 입출력 장치와 com2의 입출력 장치가 같습니다.");
        }
    }
}

class Computer implements Comparable<Computer>{
    public int monitor;
    public int keyboard;
    public int mouse;
    public int speaker;

    public Computer(int monitor, int keyboard, int mouse, int speaker) {
        this.monitor = monitor;
        this.keyboard = keyboard;
        this.mouse = mouse;
        this.speaker = speaker;
    }

    public void pri_interface(){
        System.out.println("모니터가 " + monitor + "개 연결되어 있습니다.");
        System.out.println("키보드가 " + keyboard + "개 연결되어 있습니다.");
        System.out.println("마우스가 " + mouse + "개 연결되어 있습니다.");
        System.out.println("스피커가 " + speaker + "개 연결되어 있습니다.");
    }

    public void calculation(int a, int b){
        int sum = a+b;
        System.out.println("a+b=" + sum);
    }

    @Override
    public int compareTo(Computer o) {
        return this.monitor - o.monitor;
    }
}

 

 

아래는 Comparator을 구현해 놓은 코드입니다. 아래코드를 보면 뭔가 부자연스럽습니다. Comparator은 보통 해당 객체 안에 구현하지 않습니다. 그것에 대해서는 나중에 설명하고 일단은 비교하기 위한 인터페이스 정도도만 알고 계시면 기본은 모두 이해하신 겁니다. 

import java.util.Comparator;

public class Main {
    public static void main(String[] args) {
        Computer com1 = new Computer(1,1,1,0);
        Computer com2 = new Computer(2,1,1,1);

        if(com1.compare(com1,com2) > 0){
            System.out.println("com1의 입출력 장치가 com2의 입출력 장치보다 많습니다.");
        } else if (com1.compare(com1,com2) < 0) {
            System.out.println("com1의 입출력 장치가 com2의 입출력 장치보다 적습니다.");
        } else{
            System.out.println("com1의 입출력 장치와 com2의 입출력 장치가 같습니다.");
        }
    }
}

class Computer implements Comparator<Computer> {
    public int monitor;
    public int keyboard;
    public int mouse;
    public int speaker;

    public Computer(int monitor, int keyboard, int mouse, int speaker) {
        this.monitor = monitor;
        this.keyboard = keyboard;
        this.mouse = mouse;
        this.speaker = speaker;
    }

    public void pri_interface(){
        System.out.println("모니터가 " + monitor + "개 연결되어 있습니다.");
        System.out.println("키보드가 " + keyboard + "개 연결되어 있습니다.");
        System.out.println("마우스가 " + mouse + "개 연결되어 있습니다.");
        System.out.println("스피커가 " + speaker + "개 연결되어 있습니다.");
    }

    public void calculation(int a, int b){
        int sum = a+b;
        System.out.println("a+b=" + sum);
    }

    @Override
    public int compare(Computer o1, Computer o2) {
        return o1.monitor - o2.monitor;
    }
}

 

 

 

위에 2개 코드는 보면 뭔가 더 어려운 것처럼만 느껴지고 가독성의 효과는 느껴지지 않습니다.

하지만 잘 생각해 봅시다.

만약에 입출력 장치를 비교하는 기준이 모니터에서 스피커로 변경됐다고 생각해 봅시다.

위에 com1.monitor로 짠 코드는 com1.monitor를 모두 speaker로 변경해야 합니다. 만약에 비교문을 여러 군대 반복해서 썼다면 그 모든 비교문을 변경해야 하고 코드별로 어떻게 영향을 끼치는지 확인해야 할 것입니다. 그리고 해당 인터페이스를 구현해 놓으면 다른 사람이 이 코드를 보고 아 이 객체는 이것을 기준으로 비교하는구나라고 알 수 있습니다. 이것이 실제 운영할 때 생각보다 많이 중요합니다.

하지만 Comparable, Comparator를 사용해서 비교했다면 클래스 안에 함수만 변경해 주면 됩니다. 

 

 

이처럼 Comparable, Comparator은 코드의 독립성과 가독성을 높여주는데 매우 크게 기여하는 인터페이스입니다.

 

 


 

 

많은 분들이 이미 아셨겠지만 Comparable과 Comparator의 기본 동작 원리는 양수, 음수, 0을 기준으로 이루어집니다.

양수이면 비교한 객체가 더 큰 것이고

음수이면 비교한 객체가 더 작은 것이고

0이면 비교한 객체가 같은 것이지요

compare, comparaTo 함수에 return 값을 보면 쉽게 알 수 있습니다.

 

그럼 Comparable과 Comparator의 차이는 무엇일까요?

 

바로 본인과 다른 객체를 비교하는가? 아니면 다른 두 개의 객체를 비교하는가?입니다.

 

compareTo는 매개변수가 2개인데 compara은 매개변수가 1개입니다. 이것이 가장 큰 차이입니다. comparaTo는 매개변수로 넘어온 객체 2개를 비교하는데 compara은 매개변수로 넘어온 객체 1개와 자기 자신을 비교하기 때문입니다.

 

 

그럼 각자 Comparable과 Comparator에 대해 더 깊이 알아보도록 하겠습니다.

 

 


 

- Comparable

 

 

Comparable은 기본적으로 compareTo 메서드를 구현해주어야 하며 매개변수를 하나로 받아 자기 자신과 비교하는 인터페이스입니다.

 

해당 인터페이스는 java.lang에 라이브러리로 들어가 있어 import를 해주지 않아도 사용할 수 있는 내장 라이브러리입니다.

 

Comparable은 다음과 같이 선언되어 있습니다. 

 

<T>로 만들어져 있는데 이는 제네릭으로 어떤 타입의 객체가 와도 받을 수 있다는 뜻입니다. 제네릭을 쓰는 대표적인 클래스는 ArrayList가 있습니다.

 

기본적으로 Comparable를 사용하기 위해서는 클래스에 구현하는 방식으로 사용하는데 기본 형태는 이렀습니다.

class ClassName implements Comparable<Type>{
    /*
    클래스 내용..
     */

    @Override
    public int compareTo(Type o) {
        /*
        비교 내용..
         */
    }
}

 

 

compareTo에서 매개변수로 받아오는 o는 비교할 객체고 나 자신은 this로 비교합니다.

중요한 것은 compareTo를 구현하는 것입니다.

만약 위에 Computer를 기준으로 한다면 먼저 어떤 것을 기준으로 비교를 할지 결정해야 합니다.

모니터, 스피커 혹은 입출력 장치를 모두 합친 개수 등 기준을 명확히 정한 후에

return값을 본인 객체에서 비교하는 객체를 빼는 형식으로 리턴해줍니다.

왜 빼는 식으로 값을 리턴해주는 이해가 되지 않을 수도 있습니다.

 

사실은 이런 형태입니다.

public class Main {
    public static void main(String[] args) {
        Computer com1 = new Computer(1,1,1,0);
        Computer com2 = new Computer(2,1,1,1);

        if(com1.compareTo(com2) > 0){
            System.out.println("com1의 입출력 장치가 com2의 입출력 장치보다 많습니다.");
        } else if (com1.compareTo(com2) < 0) {
            System.out.println("com1의 입출력 장치가 com2의 입출력 장치보다 적습니다.");
        } else{
            System.out.println("com1의 입출력 장치와 com2의 입출력 장치가 같습니다.");
        }
    }
}

class Computer implements Comparable<Computer>{
    public int monitor;
    public int keyboard;
    public int mouse;
    public int speaker;

    public Computer(int monitor, int keyboard, int mouse, int speaker) {
        this.monitor = monitor;
        this.keyboard = keyboard;
        this.mouse = mouse;
        this.speaker = speaker;
    }

    public void pri_interface(){
        System.out.println("모니터가 " + monitor + "개 연결되어 있습니다.");
        System.out.println("키보드가 " + keyboard + "개 연결되어 있습니다.");
        System.out.println("마우스가 " + mouse + "개 연결되어 있습니다.");
        System.out.println("스피커가 " + speaker + "개 연결되어 있습니다.");
    }

    public void calculation(int a, int b){
        int sum = a+b;
        System.out.println("a+b=" + sum);
    }

    @Override
    public int compareTo(Computer o) {
        if(this.monitor > o.monitor){
            return 1;
        }else if(this.monitor < o.monitor){
            return -1;
        }else{
            return 0;
        }
    }
}

 

 

compareTo를 보게 되면 해당 모니터의 개수를 직접 비교하고 1,-1,0를 반환해 줍니다. 사실 이게 정석적인 방법입니다. 하지만 이렇게 하면 가독성이 굉장히 떨어지게 됩니다. 대소비교를 하는데 굳이 조건문을 사용할 필요가 없기 때문입니다. 하지만 위에 코드처럼 해야만 하는 경우가 있습니다. 정상적인 코드를 먼저 보고 정상적인 코드가 왜 위험한지 말씀드리겠습니다.

public class Main {
    public static void main(String[] args) {
        Computer com1 = new Computer(1,1,1,0);
        Computer com2 = new Computer(2,1,1,1);

        if(com1.compareTo(com2) > 0){
            System.out.println("com1의 입출력 장치가 com2의 입출력 장치보다 많습니다.");
        } else if (com1.compareTo(com2) < 0) {
            System.out.println("com1의 입출력 장치가 com2의 입출력 장치보다 적습니다.");
        } else{
            System.out.println("com1의 입출력 장치와 com2의 입출력 장치가 같습니다.");
        }
    }
}

class Computer implements Comparable<Computer>{
    public int monitor;
    public int keyboard;
    public int mouse;
    public int speaker;

    public Computer(int monitor, int keyboard, int mouse, int speaker) {
        this.monitor = monitor;
        this.keyboard = keyboard;
        this.mouse = mouse;
        this.speaker = speaker;
    }

    public void pri_interface(){
        System.out.println("모니터가 " + monitor + "개 연결되어 있습니다.");
        System.out.println("키보드가 " + keyboard + "개 연결되어 있습니다.");
        System.out.println("마우스가 " + mouse + "개 연결되어 있습니다.");
        System.out.println("스피커가 " + speaker + "개 연결되어 있습니다.");
    }

    public void calculation(int a, int b){
        int sum = a+b;
        System.out.println("a+b=" + sum);
    }

    @Override
    public int compareTo(Computer o) {
        return this.monitor - o.monitor;
    }
}

 

해당 comparTo를 보면 한 줄로 매우 깔끔하고 양수, 음수, 0으로 비교할 수 있음으로 크게 문제 될 것이 없어 보입니다. 하지만 실수형(int)에서 개수가 벗어나게 되면 해당 함수를 사용할 수 없게 됩니다.

무슨 말이냐면 실수형(int)이 감당할 수 있는 값은  -2,147,483,648 ~ 2,147,483,647입니다. 해당 값을 벗어나는 경우는 거의 없지만 해당 값을 벗어나게 되면 overflow나 underflow를 발생시키므로 주의해서 사용해야 합니다.

 

 


 

 

- Comparator

 

comparator은 2개의 객체를 받아 서로 비교하는 인터페이스입니다.

위에서 봤던 코드를 한번 보겠습니다.

import java.util.Comparator;

public class Main {
    public static void main(String[] args) {
        Computer com1 = new Computer(1,1,1,0);
        Computer com2 = new Computer(2,1,1,1);

        if(com1.compare(com1,com2) > 0){
            System.out.println("com1의 입출력 장치가 com2의 입출력 장치보다 많습니다.");
        } else if (com1.compare(com1,com2) < 0) {
            System.out.println("com1의 입출력 장치가 com2의 입출력 장치보다 적습니다.");
        } else{
            System.out.println("com1의 입출력 장치와 com2의 입출력 장치가 같습니다.");
        }
    }
}

class Computer implements Comparator<Computer> {
    public int monitor;
    public int keyboard;
    public int mouse;
    public int speaker;

    public Computer(int monitor, int keyboard, int mouse, int speaker) {
        this.monitor = monitor;
        this.keyboard = keyboard;
        this.mouse = mouse;
        this.speaker = speaker;
    }

    public void pri_interface(){
        System.out.println("모니터가 " + monitor + "개 연결되어 있습니다.");
        System.out.println("키보드가 " + keyboard + "개 연결되어 있습니다.");
        System.out.println("마우스가 " + mouse + "개 연결되어 있습니다.");
        System.out.println("스피커가 " + speaker + "개 연결되어 있습니다.");
    }

    public void calculation(int a, int b){
        int sum = a+b;
        System.out.println("a+b=" + sum);
    }

    @Override
    public int compare(Computer o1, Computer o2) {
        return o1.monitor - o2.monitor;
    }
}

 

 

 

약간의 이질감이 느껴지시나요? 쓸데없는 무언가가 있는 느낌입니다.

바로 이 부분이 조금 신경 쓰입니다.

if(com1.compare(com1,com2) > 0){
    System.out.println("com1의 입출력 장치가 com2의 입출력 장치보다 많습니다.");
} else if (com1.compare(com1,com2) < 0) {
    System.out.println("com1의 입출력 장치가 com2의 입출력 장치보다 적습니다.");
} else{
    System.out.println("com1의 입출력 장치와 com2의 입출력 장치가 같습니다.");
}

 

 

이게 왜 신경 쓰이는 알아내는 과정이 즐겁고 신기합니다.

신경 쓰이는 이유는 com1이 두 번 반복되었기 때문입니다. 

어차피 비교하는 건 com1, com2인데 굳이 com1을 통해서 이 기능을 사용해야 할까?라는 의문이 듭니다.

그래서 다음과 같이 변경해 봅니다.

import java.util.Comparator;

public class Main {
    public static void main(String[] args) {
        Computer com1 = new Computer(1,1,1,0);
        Computer com2 = new Computer(2,1,1,1);
        Comparator comp = new Comparator<Computer>() {
            @Override
            public int compare(Computer o1, Computer o2) {
                return o1.monitor - o2.monitor;
            }
        };


        if(comp.compare(com1,com2) > 0){
            System.out.println("com1의 입출력 장치가 com2의 입출력 장치보다 많습니다.");
        } else if (comp.compare(com1,com2) < 0) {
            System.out.println("com1의 입출력 장치가 com2의 입출력 장치보다 적습니다.");
        } else{
            System.out.println("com1의 입출력 장치와 com2의 입출력 장치가 같습니다.");
        }
    }
}

class Computer implements Comparator<Computer> {
    public int monitor;
    public int keyboard;
    public int mouse;
    public int speaker;

    public Computer(int monitor, int keyboard, int mouse, int speaker) {
        this.monitor = monitor;
        this.keyboard = keyboard;
        this.mouse = mouse;
        this.speaker = speaker;
    }

    public void pri_interface(){
        System.out.println("모니터가 " + monitor + "개 연결되어 있습니다.");
        System.out.println("키보드가 " + keyboard + "개 연결되어 있습니다.");
        System.out.println("마우스가 " + mouse + "개 연결되어 있습니다.");
        System.out.println("스피커가 " + speaker + "개 연결되어 있습니다.");
    }

    public void calculation(int a, int b){
        int sum = a+b;
        System.out.println("a+b=" + sum);
    }

    @Override
    public int compare(Computer o1, Computer o2) {
        return o1.monitor - o2.monitor;
    }
}

 

 

 

중요한 것은 아래 코드 부분인데

이 부분에서 보면 먼저 Comparator을 객체로 선언한 것을 볼 수 있습니다. 엇? 인터페이스는 객체로 선언할 수 없는 것 아닌가요? 

맞습니다. 하지만 java8부터 일회성의 구현 객체를 만들기 위해 소스 파일을 만들고 클래스를 선언하는 것은 비효율적이라고 판단하여  소스 파일을 만들지 않고도 구현 객체를 만들 수 있는 방법을 제공하는데, 그것이 익명 구현 객체입니다.

익명 구현 객체를 이용하면 인터페이스도 객체 선언을 할 수 있게 됩니다.

java8부터 정말 많은 것들이 편해졌죠.

Computer com1 = new Computer(1,1,1,0);
Computer com2 = new Computer(2,1,1,1);
Comparator comp = new Comparator<Computer>() {
    @Override
    public int compare(Computer o1, Computer o2) {
        return o1.monitor - o2.monitor;
    }
};


if(comp.compare(com1,com2) > 0){
    System.out.println("com1의 입출력 장치가 com2의 입출력 장치보다 많습니다.");
} else if (comp.compare(com1,com2) < 0) {
    System.out.println("com1의 입출력 장치가 com2의 입출력 장치보다 적습니다.");
} else{
    System.out.println("com1의 입출력 장치와 com2의 입출력 장치가 같습니다.");
}

 

익명 구현 객체를 구현하는 방법은 다음과 같습니다.

[ClassName] [변수] = new [ClassName](생성자 매개변수){
    /*
    구현해야 하는 객체 내용..
     */
}

 

 

Comparator을 구현한 것을 보면 Comparator에서 제네릭을 Computer로 선언해 줌으로 해당 override 된 compare함수에서 Computer 객체를 사용할 수 있도록 합니다.

당연히 Comparator는 인터페이스임으로 compare을 구현해주어야 합니다.

Comparator 본코드를 보면 제네릭으로 선언되어서 compare 역시 제네릭으로 매개변수를 받고 있는 걸 알 수 있습니다.

저렇게 선언한 객체를 사용해서 두 개의 객체를 비교할 수 있게 되었습니다. 그렇다면 Computer에 구현한 comparator은 사실상 필요 없는 존재가 됩니다.

import java.util.Comparator;

public class Main {
    public static void main(String[] args) {
        Computer com1 = new Computer(1,1,1,0);
        Computer com2 = new Computer(2,1,1,1);
        Comparator comp = new Comparator<Computer>() {
            @Override
            public int compare(Computer o1, Computer o2) {
                return o1.monitor - o2.monitor;
            }
        };


        if(comp.compare(com1,com2) > 0){
            System.out.println("com1의 입출력 장치가 com2의 입출력 장치보다 많습니다.");
        } else if (comp.compare(com1,com2) < 0) {
            System.out.println("com1의 입출력 장치가 com2의 입출력 장치보다 적습니다.");
        } else{
            System.out.println("com1의 입출력 장치와 com2의 입출력 장치가 같습니다.");
        }
    }
}

class Computer {
    public int monitor;
    public int keyboard;
    public int mouse;
    public int speaker;

    public Computer(int monitor, int keyboard, int mouse, int speaker) {
        this.monitor = monitor;
        this.keyboard = keyboard;
        this.mouse = mouse;
        this.speaker = speaker;
    }

    public void pri_interface(){
        System.out.println("모니터가 " + monitor + "개 연결되어 있습니다.");
        System.out.println("키보드가 " + keyboard + "개 연결되어 있습니다.");
        System.out.println("마우스가 " + mouse + "개 연결되어 있습니다.");
        System.out.println("스피커가 " + speaker + "개 연결되어 있습니다.");
    }

    public void calculation(int a, int b){
        int sum = a+b;
        System.out.println("a+b=" + sum);
    }
    
}

 

 

 

조금만 더 생각해 봅시다. 람다식으로 구현하겠습니다익명 구현 객체는 람다식으로 표현하는 게 좀 더 짧고 가독성이 좋습니다. 클래스 이름을 제거하고 return을 제거해서 바로 리턴하게 만드는 것이 람다식을 만드는 방법입니다.

import java.util.Comparator;

public class Main {
    public static void main(String[] args) {
        Computer com1 = new Computer(1,1,1,0);
        Computer com2 = new Computer(2,1,1,1);
        Comparator<Computer> comp = (Computer o1, Computer o2) -> o1.monitor - o2.monitor;


        if(comp.compare(com1,com2) > 0){
            System.out.println("com1의 입출력 장치가 com2의 입출력 장치보다 많습니다.");
        } else if (comp.compare(com1,com2) < 0) {
            System.out.println("com1의 입출력 장치가 com2의 입출력 장치보다 적습니다.");
        } else{
            System.out.println("com1의 입출력 장치와 com2의 입출력 장치가 같습니다.");
        }
    }
}

class Computer {
    public int monitor;
    public int keyboard;
    public int mouse;
    public int speaker;

    public Computer(int monitor, int keyboard, int mouse, int speaker) {
        this.monitor = monitor;
        this.keyboard = keyboard;
        this.mouse = mouse;
        this.speaker = speaker;
    }

    public void pri_interface(){
        System.out.println("모니터가 " + monitor + "개 연결되어 있습니다.");
        System.out.println("키보드가 " + keyboard + "개 연결되어 있습니다.");
        System.out.println("마우스가 " + mouse + "개 연결되어 있습니다.");
        System.out.println("스피커가 " + speaker + "개 연결되어 있습니다.");
    }

    public void calculation(int a, int b){
        int sum = a+b;
        System.out.println("a+b=" + sum);
    }

}

 

 

 


 

 

이것으로 Comparable과 Comparator의 설명이 끝났습니다.

 

마지막으로 정리하면 Comparable과 Comparator의 진짜 좋은 점은 해당 객체로 <T extends Comparable<? super T>> 형식으로 많은 객체로 선언되어 있기 때문에 sort함수나 여러 함수에서 사용 가능하다는 것입니다. 

저 객체는.. 정말 끝판왕입니다. 이해하느라 며칠을 걸렸는지 모릅니다. 기회가 되면 제네릭을 설명하면서 저것도 한번 다뤄보고 싶네요.

 

지금까지 글을 읽느라 고생 많으셨습니다.

 

궁금한 것이 있다면 댓글 달아주세요 최대한 빨리 답변 달아드리겠습니다.

 

만약에 comparator, comparable을 사용한 정렬을 보고 싶으면 아래 게시물을 참고하기를 바랍니다.

https://kkungchan.tistory.com/299

 

문제 번호 11650번 : 좌표 정렬하기 - JAVA [자바]

https://www.acmicpc.net/problem/1181 1181번: 단어 정렬 첫째 줄에 단어의 개수 N이 주어진다. (1 ≤ N ≤ 20,000) 둘째 줄부터 N개의 줄에 걸쳐 알파벳 소문자로 이루어진 단어가 한 줄에 하나씩 주어진다. 주

kkungchan.tistory.com

https://kkungchan.tistory.com/297

 

문제 번호 11650번 : 좌표 정렬하기 - JAVA [자바]

https://www.acmicpc.net/problem/11650 11650번: 좌표 정렬하기 첫째 줄에 점의 개수 N (1 ≤ N ≤ 100,000)이 주어진다. 둘째 줄부터 N개의 줄에는 i번점의 위치 xi와 yi가 주어진다. (-100,000 ≤ xi, yi ≤ 100,000) 좌표

kkungchan.tistory.com

https://kkungchan.tistory.com/284

 

Java로 구현한 Heap 자료구조(트리 구조)

이번 글에서는 Heap 자료구조에 대해 설명하겠습니다. 백준 알고리즘을 푸는 글이 많은데 알고리즘과 자료구조는 굉장히 깊은 관계가 있기 때문에 자료구조에 대한 공부 한 것을 종종 기록해 공

kkungchan.tistory.com

 

 

 

 

 

 

 


 

 

 

 

 

 

 

해당 블로그를 참고했습니다.

 

https://codechacha.com/ko/java-sorting-array/

 

Java - 배열 정렬(Sorting) (오름차순, 내림차순)

Arrays.sort()을 이용하면 쉽게 배열(Array)을 내림차순, 오름차순으로 정렬(sorting)할 수 있습니다. Integer, String 등 구분없이 모든 객체를 정렬할 수 있습니다. 또한, 클래스에 Comparable을 구현하면 객체

codechacha.com

https://st-lab.tistory.com/243

 

자바 [JAVA] - Comparable 과 Comparator의 이해

아마 이 글을 찾아 오신 분들 대개는 Comparable과 Comparator의 차이가 무엇인지 모르거나 궁금해서 찾아오셨을 것이다. 사실 알고보면 두 개는 그렇게 어렵지 않으나 아무래도 자바를 학습하면서 객

st-lab.tistory.com

https://www.daleseo.com/java-comparable-comparator/

 

[Java] 객체 정렬하기 1부 - Comparable vs Comparator

Engineering Blog by Dale Seo

www.daleseo.com

https://wikidocs.net/207

 

03-07 리스트 (List)

[TOC] 리스트는 배열과 비슷한 자바의 자료형으로 배열보다 편리한 기능을 많이 가지고 있다. 리스트와 배열의 가장 큰 차이는 배열은 크기가 정해져 있지만 리스트는 크기가 …

wikidocs.net

https://koreanfoodie.me/641

 

8-1. 자바 인터페이스의 역할과 선언, 구현 예제 살펴보기

인터페이스의 역할 인터페이스는 객체의 사용 방법을 정의한 타입이다. 개발 코드가 중간에 인터페이스를 두는 이유는, 개발 코드를 수정하지 않고, 사용하는 객체를 변경할 수 있도록 하기 위

koreanfoodie.me

https://brownbears.tistory.com/564

 

[Java] Comparator와 Comparable

Comparator와 Comparable은 둘 다 객체를 정렬할 때 사용할 수 있는 기능입니다. 먼저 아래와 같이 객체가 존재한다고 가정합니다. public class Image { private final int type; private final String url; private final int id

brownbears.tistory.com