Notice
Recent Posts
Recent Comments
Link
«   2026/03   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
Tags more
Archives
Today
Total
관리 메뉴

프로그래밍 공부

[자바] #14. Collections 클래스 안의 여러 알고리즘 구현 메서드들 본문

자바

[자바] #14. Collections 클래스 안의 여러 알고리즘 구현 메서드들

하 냥 2025. 2. 19. 14:39

Collections 클래스 안에는, 다양한 알고리즘을 구현한 메서드들이 있다.

 

📢 정렬 (Collections.sort)

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

여기서 ? super T는 제네릭 타입의 와일드카드로, T 타입과 T의 상위 타입(Superclass) 를 모두 허용한다.

즉, T 또는 T의 부모 타입에 대해 비교를 수행할 수 있는 Comparator를 사용할 수 있도록 한다.

 

💡 왜 <? super T>를 사용할까?

  • 유연성(Flexibility): 상위 타입의 비교자를 재사용할 수 있다.
  • 안전성(Type Safety): 하위 클래스의 객체가 상위 클래스의 필드를 비교할 때도 타입 안정성을 보장한다.

 

🧩 예제: ? super T의 필요성 이해

클래스 구조

class Animal implements Comparable<Animal>{
    String name;

    public Animal(String name) {
        this.name = name;
    }
    
    @Override
    public int compareTo(Animal other) {
        return this.name.compareTo(other.name);
    }
}

class Dog extends Animal {
    int age;

    public Dog(String name, int age) {
        super(name);
        this.age = age;
    }
}

 

🚀 예제 코드: Comparator<? super T> 활용

import java.util.List;
import java.util.Collections;

public class ComparatorSuperExample {
    public static void main(String[] args) {
        List<Dog> dogs = Arrays.asList(
            new Dog("Buddy", 5),
            new Dog("Charlie", 3),
            new Dog("Max", 7)
        );

        // ✅ Animal 타입 기준 Comparator (name 기준 비교)
        Comparator<Animal> animalComparator = (a1, a2) -> a1.name.compareTo(a2.name);

        // ✅ Collections.sort에서 Comparator<? super T> 덕분에 사용 가능
        Collections.sort(dogs, animalComparator);

        for (Dog dog : dogs) {
            System.out.println(dog.name + " (" + dog.age + "살)");
        }
    }
}

여기서  T는 dog.

Comparator<? super T>가 아니었다면, Comparator<Animal>은 List<Dog>에 바로 사용할 수 없다.

? super T 덕분에 Dog의 상위 타입인 Animal의 비교자도 사용할 수 있는 것.

 

💎 핵심 요약

  • ? super T: T와 T의 상위 클래스 모두 허용하여 더 일반적인 비교자를 사용할 수 있도록 함.
  • 실제 사용 사례: Collections.sort()에서 부모 타입의 Comparator를 재사용할 때 유용.
  • "PECS" 원칙:
    • Producer Extends, Consumer Super
    • 데이터를 소비하는 경우(Comparator)에는 super 사용.

📢 찾기 (Collections.binarySearch)

public static <T> int binarySearch(List<? extends Comparable<? super T>> list, T key)

💡 매개변수 설명

List<? extends Comparable<? super T>> list

  • 리스트의 요소 타입이 T 또는 T의 하위 타입(? extends T)이어야 한다.
  • 각 요소는 Comparable<? super T>를 구현해야 한다. 즉, 리스트의 요소는 T와 T의 상위 타입과 비교할 수 있어야.

🎯 List<? extends Comparable<? super T>>의 의미

부분 의미
? extends 리스트 요소가 T 또는 T의 하위 클래스일 수 있도록 허용 (읽기 전용으로 안전)
Comparable<? super T> 리스트 요소가 T 및 T의 상위 클래스와 비교할 수 있어야 함 (타입 유연성 증가)
import java.util.List;
import java.util.Collections;

public class SuperTypeComparableExample {
    public static void main(String[] args) {
        List<Dog> dogs = Arrays.asList(
            new Dog("Buddy"),
            new Dog("Charlie"),
            new Dog("Max")
        );

        // 정렬 (Animal의 compareTo 사용)
        Collections.sort(dogs);

        // Dog 객체를 찾음 (Animal의 비교 규칙을 따름)
        Dog target = new Dog("Charlie");
        int index = Collections.binarySearch(dogs, target);

        System.out.println("Charlie의 인덱스: " + index);
    }
}

🌟 왜 <? extends Comparable<? super T>>를 사용했을까?

여기서:

  • T는 Dog.
  • Dog는 Animal의 하위 타입.
  • 리스트(List<Dog>)의 요소는 Animal 타입과 비교할 수 있어야.
  • Animal이 Comparable<Animal>을 구현했기 때문에 Dog도 자동으로 Animal과 비교할 수 있다.
  • List<? extends Comparable<? super T>> 덕분에 Dog 리스트에서도 Animal 비교 방식을 사용할 수 있다.
    👉 유연성과 재사용성을 보장하는 설계!

 

 

🧩 만약 Comparable<T>만 사용했다면?

public static <T> int binarySearch(List<? extends Comparable<T>> list, T key)

 

이 경우 Comparable<Dog>가 필요하다.

하지만 Dog는 Comparable<Dog>를 구현하지 않았고, Animal만 Comparable<Animal>을 구현했기 때문에 컴파일 오류가 발생했을 것이다.

 

정리하자면:

구문 의미 예시
Comparable<T> 정확히 같은 타입(T)끼리만 비교 허용 Comparable<Animal>은 Animal만 비교
Comparable<? super T> T와 T의 상위 타입과도 비교 허용
(더 유연함)
Comparable<? super Dog>는
Dog와 Animal 모두 비교 가능

 


 

📢 복사 (Collections.copy)

public static <T> void copy(List<? super T> dest, List<? extends T> src)

💡 매개변수 설명

1️⃣ List<? super T> dest (복사 대상 리스트)

  • 의미:
    • dest 리스트는 T 타입 또는 T의 상위 타입(Superclass) 을 포함할 수 있다.
  • <? super T>인가?
    • dest는 src의 요소를 받아들여서 소비(=저장)(consumer) 하는 역할을 한다.
    • "PECS 원칙":
      • Producer Extends
      • Consumer Super
    • 즉, dest가 T의 상위 타입을 허용해야 T 타입 요소를 안전하게 저장할 수 있다.
List<Object> dest = new ArrayList<>(Arrays.asList(new Object[3]));
List<String> src = Arrays.asList("A", "B", "C");
Collections.copy(dest, src);  // ✅ 가능 (Object는 String의 상위 타입)

2️⃣ List<? extends T> src (복사할 리스트)

  • 의미:
    • src 리스트는 T 타입 또는 T의 하위 타입(Subclass) 을 포함할 수 있다.
  • <? extends T>인가?
    • src는 dest로 데이터를 제공(produce) 한다.
    • 따라서 T의 하위 타입을 포함할 수 있도록 허용하여 유연성을 증가시킨다.
List<Number> dest = new ArrayList<>(Arrays.asList(0, 0, 0));
List<Integer> src = Arrays.asList(1, 2, 3);
Collections.copy(dest, src);  // ✅ 가능 (Integer는 Number의 하위 타입)

⚡ 왜 이렇게 설계되었을까? (PECS 원칙)

역할 와일드카드 예시
제공자 (Producer) ? extends T List<? extends Number> → Integer, Double 제공 가능
소비자 (Consumer) ? super T List<? super Integer> → Number, Object 허용

 

실제 코드 예제

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

public class CopyExample {
    public static void main(String[] args) {
        List<Object> dest = new ArrayList<>(Arrays.asList(new Object[3]));
        List<String> src = Arrays.asList("Apple", "Banana", "Cherry");

        Collections.copy(dest, src);
        System.out.println(dest);  // 출력: [Apple, Banana, Cherry]
    }
}

❓ 그럼 실제 코드 예제에서 T가 String이야? T가 String인 줄은 어떻게 알아, 컴파일러가?

↓ ↓ ↓   

🎯 컴파일러가 T를 추론하는 방식

 copy 메서드에서, T는 메서드 호출 시 컴파일러가 자동 추론하는 제네릭 타입이다.

🚀 타입 추론 과정

  1. src의 타입 분석:
    • src는 List<String>.
    • src가 List<? extends T>이어야 하므로, T는 String이거나 String의 상위 타입이어야 한다.
  2. dest의 타입 분석:
    • dest는 List<Object>.
    • dest가 List<? super T>이어야 하므로, T는 Object이거나 Object의 하위 타입이어야 한다.
  3. 조건 통합:
    • src: T ≤ String (즉, T가 String의 상위 타입이어야 함)
    • dest: T ≥ Object (즉, T가 Object의 하위 타입이어야 함)

결론:

  • T가 동시에 String의 상위 타입이면서 Object의 하위 타입이어야 하므로, 가장 구체적인 타입은 "String"이다.
  • 따라서 컴파일러는 T를 String으로 추론한다.

 

예제 변경 : T가 Integer인 경우

List<Number> dest = new ArrayList<>(Arrays.asList(0, 0, 0));
List<Integer> src = Arrays.asList(1, 2, 3);

Collections.copy(dest, src);  // ✅ 성공

분석:

  • src: List<Integer> ⇒ T ≥ Integer
  • dest: List<Number> ⇒ T ≤ Number
  • 따라서 Integer ≤ T ≤ Number ⇒ T = Integer

💎 핵심 요약

  • 컴파일러는 src와 dest 리스트의 타입을 비교하여 가장 구체적인 T를 자동 추론한다.
  • ? super T: dest가 T 타입을 받아들일 수 있도록 보장.
  • ? extends T: src가 T 타입의 데이터를 제공할 수 있도록 보장.
  • "PECS" 원칙을 통해 메서드의 타입 안정성과 유연성을 동시에 확보한다.