Language/Java

β˜• λžŒλ‹€μ‹ λ¦¬νŒ©ν† λ§ ν•˜κΈ° (Comparator μΆ•μ•½ 원리)

인파_ 2023. 3. 31. 09:28

lambda-refactoring

Comparator λžŒλ‹€μ‹ λ¦¬νŒ©ν† λ§ 해보기

λ‹€μŒμ€ μ‹€μ œλ‘œ μžλ°” ν”„λ‘œκ·Έλž˜λ°μ—μ„œ 배열을 μ •λ ¬(sort) ν• λ•Œ μ‚¬μš©λ˜λŠ” Comparator μΈν„°νŽ˜μ΄μŠ€ μ‚¬μš© μ˜ˆμ œμ΄λ‹€.

Apple ν΄λž˜μŠ€κ°€ 있고 μƒμ„±μž 인자둜 μ‚¬κ³Όμ˜ 무게(weight)값을 λ°›λŠ”λ‹€. 그리고 μ‹€ν–‰λΆ€μ—μ„œ λ°°μ—΄λ‘œ 사과 객체λ₯Ό λ‹΄κ³ , 사과 λ¬΄κ²Œμ— 따라 λ°°μ—΄ μš”μ†Œλ“€μ„ μ •λ ¬ν•˜λ €κ³  ν•œλ‹€. 이λ₯Ό μ½”λ“œλ‘œ κ΅¬ν˜„ν•˜λ©΄ μ•„λž˜μ™€ 같이 κ΅¬ν˜„ν•  수 μžˆλ‹€.

class Apple {
    private final int weight; // 사과 무게

    public Apple(int weight) {
        this.weight = weight;
    }

    public int getWeight() {
        return weight;
    }

    @Override
    public String toString() {
        return "Apple{" + "weight=" + weight + '}';
    }
}
public class Main {
    public static void main(String[] args) {
        Apple[] inventory = new Apple[] {
                new Apple(34),
                new Apple(12),
                new Apple(76),
                new Apple(91),
                new Apple(55)
        };

        Arrays.sort(inventory, new Comparator<Apple>() {
            @Override
            public int compare(Apple o1, Apple o2) {
                return Integer.compare(o1.getWeight(), o2.getWeight());
            }
        });
        
        System.out.println(Arrays.toString(inventory));
    }
}

lambda-refactoring

배열을 좜λ ₯ν•΄λ³΄λ‹ˆ, 사과 객체듀이 λ¬΄κ²Œμ— 따라 μˆœμ„œλŒ€λ‘œ 정렬됨을 λ³Ό 수 μžˆλ‹€.

ν•˜μ§€λ§Œ μΈν…”λ¦¬μ œμ΄μ™€ 같은 IDEλ₯Ό μ“΄λ‹€λ©΄, Comparator 읡λͺ… κ΅¬ν˜„ 클래슀λ₯Ό λžŒλ‹€ν‘œν˜„μ‹μœΌλ‘œ λ¦¬νŒ©ν† λ§ ν•˜λΌκ³  쑰언을 해쀄 것이닀.

lambda-refactoring

그런데 λžŒλ‹€μ‹μœΌλ‘œ λ³€ν™˜ν–ˆλ”λ‹ˆ μ΄λ²ˆμ—” 또 λ³€ν™˜ν•˜λΌκ³  IDEμ—μ„œ μ‘°μ–Έν•΄μ€€λ‹€.

lambda-refactoring

λ³€ν™˜ν•΄λ³΄λ‹ˆ ν™•μ‹€νžˆ κΈ°μ‘΄ λžŒλ‹€μ‹ 보단 μ½”λ“œ 쀄이 ν™•μ—°νžˆ μ€„μ—ˆμŒμ„ λ³Ό 수 μžˆλ‹€. 그런데 κ°‘μžκΈ° 쌩뚱맞게 Comparator의 정적 λ©”μ†Œλ“œλ₯Ό ν˜ΈμΆœν•˜κ³  κ·Έ μ•ˆμ—λŠ” λžŒλ‹€μ˜ λ©”μ„œλ“œ μ°Έμ‘° 문법을 써 λ†“μ•˜λ‹€. λŒ€μ²΄ μ–΄λ–€ μ›λ¦¬λ‘œ λžŒλ‹€ν‘œν˜„μ‹μ„ μ΄λ ‡κ²Œ 좕약을 ν•  수 μžˆλŠ”μ§€ κ°€λŠ μ΄ μ•ˆκ°„λ‹€.

μ‹€μ œλ‘œ μ΄λŠ” μ‹€μ „μ—μ„œ 정말 많이 μ“°μ΄λŠ” 기법인데, λ‹¨μˆœν•˜κ²Œ μ•”κΈ°ν•˜κ³  λ„˜κΈ°μ§€ 말고 μ™œ μ €λŸ°μ‹μœΌλ‘œ 쀄일 수 μžˆλŠ”μ§€ μ§€κΈˆλΆ€ν„° ν•˜λ‚˜ν•˜λ‚˜ νŒŒν—€μ³ 보자.


Comparator의 μΆ•μ•½ 원리 νŒŒν—€μΉ˜κΈ°

μš°μ„  ν•΄λ‹Ή λžŒλ‹€μ‹μ€ λ©”μ†Œλ“œ 참쑰둜 λ¬Έλ²•μ μœΌλ‘œ μƒλž΅μ΄ λΆˆκ°€λŠ₯ ν•˜λ‹€.

Arrays.sort(inventory, (o1, o2) -> Integer.compare(o1.getWeight(), o2.getWeight()));

만일 (a1, a2) -> Integer.compare(a1, a2) 처럼 λ˜μ–΄μžˆμœΌλ©΄ Integer::compare 둜 μƒλž΅ν•  수 μžˆμ—ˆκ² μ§€λ§Œ, λ§€κ°œλ³€μˆ˜κ°€ Integer.compare λ©”μ„œλ“œμ˜ 인자둜 κ·ΈλŒ€λ‘œ λ“€μ–΄κ°„κ²Œ μ•„λ‹ˆλΌ λ³„λ„λ‘œ λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•˜κ³  있기 λ•Œλ¬Έμ΄λ‹€. 

λžŒλ‹€ λ©”μ†Œλ“œ μ°Έμ‘° κ³ κΈ‰ μ‘μš©

그러면 μ˜μ›νžˆ μ½”λ“œ 식을 μ€„μΌμˆ˜ μ—†λŠ”κ²ƒ 인가?

생각을 ν™˜κΈ° μ‹œμΌœλ³΄λ©΄ 해닡은 κ°„λ‹¨ν•˜λ‹€. λ°”λ‘œ λžŒλ‹€ ν•¨μˆ˜ 자체λ₯Ό λ©”μ„œλ“œκ°€ λ°˜ν™˜ν•˜λ„λ‘ λ§Œλ“€λ©΄ λœλ‹€.

λžŒλ‹€ ν•¨μˆ˜μ‹μ€ 일급 객체둜 μ·¨κΈ‰λœλ‹€. 일급 객체의 νŠΉμ§•μœΌλ‘œλŠ” λ³€μˆ˜μ— ν•¨μˆ˜λ₯Ό ν• λ‹Ήν• μˆ˜ 있고 ν•¨μˆ˜λ₯Ό λ¦¬ν„΄κ°’μœΌλ‘œλ„ ν™œμš©ν•  수 μžˆλ‹€. λŒ€ν‘œμ μœΌλ‘œ μžλ°”μŠ€ν¬λ¦½νŠΈμ˜ ν•¨μˆ˜κ°€ λ³€μˆ˜μ— λ„£κ±°λ‚˜ 자체 ν•¨μˆ˜λ₯Ό λ°˜ν™˜ν•˜λŠ” 기법을 μ‚¬μš©ν•œλ‹€.

즉, μ΄λŸ¬ν•œ νŠΉμ„±μ„ μ΄μš©ν•΄ λ³„λ„μ˜ 정적 λ©”μ„œλ“œλ₯Ό λ§Œλ“€κ³ , κ·Έ λ©”μ„œλ“œκ°€ νŠΉμ • λžŒλ‹€ ν•¨μˆ˜μ‹μ„ λ¦¬ν„΄ν•˜λ„λ‘ ν•΄μ£Όλ©΄, κΈΈλ‹€λž€ λžŒλ‹€μ‹μ„ λ‹¨μˆœνžˆ λ©”μ„œλ“œ 호좜 μ½”λ“œλ‘œ μ€„μΌμˆ˜ μžˆλŠ” 것이닀. κ·Έλž˜μ„œ μš°μ„  κ°„λ‹¨ν•˜κ²Œ ν”„λ‘œν† νƒ€μž…μœΌλ‘œ λžŒλ‹€μ‹μ„ λ°˜ν™˜ν•˜λŠ” 정적 λ©”μ„œλ“œλ₯Ό μž‘μ„±ν•΄λ³΄λ©΄ λ‹€μŒκ³Ό 같이 λ˜κ² λ‹€.

public class Main {

    public static Comparator<Apple> comparingInt() {
        // λžŒλ‹€μ‹μ„ Comparator μΈν„°νŽ˜μ΄μŠ€ νƒ€μž…μœΌλ‘œ λ°˜ν™˜
        return (a1, a2) -> Integer.compare(a1.getWeight(), a2.getWeight());
    }

    public static void main(String[] args) {
        Apple[] inventory = new Apple[]{
                new Apple(34),
                new Apple(12),
                new Apple(76),
                new Apple(91),
                new Apple(55)
        };

        Arrays.sort(inventory, comparingInt()); // λ‹¨μˆœν•œ λ©”μ„œλ“œ 호좜둜 μƒλž΅ν•¨ (가독성 ↑)
        
        System.out.println(Arrays.toString(inventory));
    }
}

κ΅¬ν˜„ν•œ λ©”μ„œλ“œλ₯Ό μ’€ 더 μ‚¬μš©μ„± μ’‹κ²Œ λ°”κΏ”λ³΄μž. μ§€κΈˆμ€ Apple 객체의 weight κ°’λ§Œ λ‹€λ£¨λ‹ˆκΉŒ μ €λ ‡κ²Œ μž‘μ„±ν•΄λ„ λ˜μ§€λ§Œ, λ‚˜μ€‘μ— Banana 객체의 prize κ°’μœΌλ‘œ μ •λ ¬ν• μˆ˜λ„ μžˆλŠ” κΈ°λŠ₯을 ν™•μž₯ν•  수 도 μžˆλ‹€. λ”°λΌμ„œ 외뢀에 μ˜ν•΄ λ°”λ€”μˆ˜ μžˆλŠ” 뢀뢄인 a.getWeight() 뢀뢄을 λ§€κ°œλ³€μˆ˜ν™” ν•˜μ—¬ μ²˜λ¦¬ν•˜λ„λ‘ ν•˜λ©΄ λœλ‹€.

그런데 a.getWeight() λŠ” λ‹¨μˆœνžˆ 데이터가 μ•„λ‹Œ ν•¨μˆ˜λ₯Ό ν˜ΈμΆœν•˜λŠ” μ½”λ“œ λ‘œμ§μ΄λ‹€. 이λ₯Ό μ–΄λ–»κ²Œ λ§€κ°œλ³€μˆ˜ν™” ν•˜λ©΄ λ˜λŠ” κ²ƒμΌκΉŒ?

μš°λ¦¬λŠ” μžλ°” λžŒλ‹€μ— λŒ€ν•΄ 배우고 μžˆλ‹€. νŒŒλΌλ―Έν„°λ‘œ λ“€μ–΄μ˜¬ 값은 λ°˜λ“œμ‹œ μ •μˆ˜λ‚˜ λ¬Έμžμ—΄κ³Ό 같은 κ³ μ •λœ λ°μ΄ν„°λ§Œ λ“€μ–΄μ˜€λΌλŠ” 법은 μ—†λ‹€. 즉, a.getWeight() λ₯Ό λ¦¬ν„΄ν•˜λŠ” λžŒλ‹€μ‹μœΌλ‘œ λ§€κ°œλ³€μˆ˜ν™” ν•˜μ—¬ ν•¨μˆ˜ν˜• μΈν„°νŽ˜μ΄μŠ€λ‘œ λ°›μœΌλ©΄ λ˜λŠ” 것이닀.

λžŒλ‹€ λ©”μ†Œλ“œ μ°Έμ‘° κ³ κΈ‰ μ‘μš©

이제 μ–΄λ– ν•œ ν•¨μˆ˜ν˜• μΈν„°νŽ˜μ΄μŠ€ νƒ€μž…μ„ λ§€κ°œλ³€μˆ˜μ˜ νƒ€μž…μœΌλ‘œ μ •ν•΄μ•Ό 할지 κ³ λ―Όν•΄μ•Ό λ˜λŠ”λ°, Apple νƒ€μž…μ˜ 객체λ₯Ό νŒŒλΌλ―Έν„°λ‘œ λ°›μ•„ int 값인 무게λ₯Ό λ°˜ν™˜ν•˜κΈ° λ•Œλ¬Έμ— 여기에 μ μ ˆν•œ ν•¨μˆ˜ν˜• μΈν„°νŽ˜μ΄μŠ€λŠ” ToIntFunction<Apple> 이닀.

public static Comparator<Apple> comparingInt(ToIntFunction<Apple> keyExtractor) {
    // λ§€κ°œλ³€μˆ˜λ‘œ λžŒλ‹€μ‹μ„ λ°›μ•„μ„œ 좔상 λ©”μ„œλ“œ μ‹€ν–‰ : keyExtractor.applyAsInt(a1) == a1.getWeight()
    return (a1, a2) -> Integer.compare(keyExtractor.applyAsInt(a1), keyExtractor.applyAsInt(a2));
}

λžŒλ‹€ λ©”μ†Œλ“œ μ°Έμ‘° κ³ κΈ‰ μ‘μš©

λ§ˆμ§€λ§‰μœΌλ‘œ λ‹€λ₯Έ μ œλ„€λ¦­ νƒ€μž…λ„ λ°›μ„μˆ˜ 있게 νƒ€μž… νŒŒλΌλ―Έν„°λ₯Ό T 기호둜 λ°”κΏ”μ£Όλ©΄ 완성이닀.

그리고 (a) -> a.getWeight() λžŒλ‹€μ‹μ€ λ©”μ„œλ“œ 참쑰둜 Apple::getWeight 둜 좕약이 κ°€λŠ₯ν•˜λ‹ˆ μ΅œμ’… 완성본은 λ‹€μŒκ³Ό κ°™λ‹€.

public class Main {
    public static <T> Comparator<T> comparingInt(ToIntFunction<? super T> keyExtractor) {
        return (a1, a2) -> Integer.compare(keyExtractor.applyAsInt(a1), keyExtractor.applyAsInt(a2));
    }

    public static void main(String[] args) {
        Apple[] inventory = new Apple[]{
                new Apple(34),
                new Apple(12),
                new Apple(76),
                new Apple(91),
                new Apple(55)
        };

        Arrays.sort(inventory, comparingInt(Apple::getWeight)); // (a) -> a.getWeight()
        
        System.out.println(Arrays.toString(inventory));
    }
}

μ‹€μ œ Comparator와 λΉ„κ΅ν•˜κΈ°

μ‹€μ œ μžλ°”μ— μ •μ˜λ˜μ–΄ μžˆλŠ” Compator μΈν„°νŽ˜μ΄μŠ€μ˜ comparingInt() λ©”μ„œλ“œ μ‹œκ·Έλ‹ˆμ²˜λ₯Ό 보면 μ§€κΈˆκΉŒμ§€ μš°λ¦¬κ°€ κ΅¬ν˜„ν•œ 것과 λ³„λ°˜ 차이가 μ—†λŠ” 것을 λ³Ό 수 μžˆλ‹€.

Arrays.sort(inventory, Comparator.comparingInt(Apple::getWeight));

λžŒλ‹€ λ©”μ†Œλ“œ μ°Έμ‘° κ³ κΈ‰ μ‘μš©

μ΄λŸ°μ‹μœΌλ‘œ λžŒλ‹€μ‹μ΄ 였히렀 μ½”λ“œμ˜ 가독성을 ν•΄μΉœλ‹€κ³  μƒκ°ν•˜λ©΄ μž…κΈ€ 객체의 νŠΉμ„±μ„ μ΄μš©ν•˜μ—¬ λ©”μ„œλ“œλ‘œ λžŒλ‹€μ‹μ„ λ°˜ν™˜ν•˜λ„λ‘ κ΅¬μ„±ν•΄μ€ŒμœΌλ‘œμ¨ μ½”λ“œμ‹μ΄ λ‹¨μˆœνžˆ λ©”μ„œλ“œ 호좜둜 ꡬ성해쀀닀면 보닀 ν΄λ¦°ν•œ λ¦¬νŒ©ν† λ§ μ½”λ“œκ°€ 될 것이닀.