13_제네릭

Tasker_Jang·2024년 3월 25일
0

13_제네릭

1. 제네릭이란 무엇인가

제네릭(Generics)은 Java 프로그래밍 언어의 중요한 기능 중 하나로, 코드의 재사용성과 유연성을 향상시키기 위해 도입되었습니다.

제네릭을 사용하면 클래스나 메서드를 작성할 때, 컴파일 시점에 타입을 지정하지 않고 추상적인 형태로 유지할 수 있습니다. 즉, 제네릭을 사용하여 결정되지 않은 타입을 파라미터로 처리할 수 있습니다. 이렇게 선언된 제네릭은 실제 사용할 때 구체적인 타입으로 대체되어 컴파일됩니다.

예를 들어, 제네릭을 사용하지 않는 경우에는 Object 타입을 사용하여 모든 종류의 객체를 다룰 수 있습니다. 하지만 이 경우에는 객체의 타입 안정성이 보장되지 않으며, 다운캐스팅 등의 추가 작업이 필요합니다. 반면에 제네릭을 사용하면 컴파일 시점에서 타입 안정성이 보장되며, 형변환에 대한 번거로움을 피할 수 있습니다.

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

public class Main {
    public static void main(String[] args) {
        // 제네릭을 사용하여 문자열을 저장하는 리스트 생성
        List<String> stringList = new ArrayList<>();
        
        // 리스트에 요소 추가
        stringList.add("Hello");
        stringList.add("World");
        
        // 리스트에서 요소 가져오기
        String firstElement = stringList.get(0);
        String secondElement = stringList.get(1);
        
        System.out.println(firstElement); // 출력: Hello
        System.out.println(secondElement); // 출력: World
    }
}

이처럼 제네릭을 사용하면 코드의 가독성과 유지보수성을 향상시키며, 타입 안정성을 보장할 수 있습니다.

2. 타입 파라미터 대체하기

제네릭을 사용하여 선언된 클래스나 인터페이스의 타입 파라미터는 구체적인 타입으로 대체될 수 있습니다. 이것은 클래스나 인터페이스를 사용할 때, 제네릭으로 정의된 타입 파라미터에 실제 타입을 지정하여 사용할 수 있다는 의미입니다.

// 제네릭 클래스
class Box<T> {
    private T value;

    public void setValue(T value) {
        this.value = value;
    }

    public T getValue() {
        return value;
    }
}

// 제네릭 인터페이스
interface Pair<K, V> {
    K getKey();
    V getValue();
}

위의 코드에서 Box 클래스와 Pair 인터페이스는 각각 하나 이상의 타입 파라미터를 가지고 있습니다. 이제 이 클래스와 인터페이스를 사용할 때 실제 타입을 지정하여 대체할 수 있습니다.

public class Main {
    public static void main(String[] args) {
        // Box 클래스 사용
        Box<Integer> integerBox = new Box<>();
        integerBox.setValue(10);
        System.out.println("Integer Value: " + integerBox.getValue());

        // Pair 인터페이스 사용
        Pair<String, Integer> pair = new Pair<String, Integer>() {
            @Override
            public String getKey() {
                return "Key";
            }

            @Override
            public Integer getValue() {
                return 100;
            }
        };

        System.out.println("Key: " + pair.getKey());
        System.out.println("Value: " + pair.getValue());
    }
}

3. 제네릭 메서드

제네릭 메서드(Generic Method)는 메서드에 타입 파라미터를 사용하여 여러 타입의 인수를 받을 수 있도록 하는 Java 프로그래밍 언어의 기능입니다. 제네릭 메서드를 사용하면 메서드를 호출할 때마다 다양한 타입의 인수를 전달할 수 있으며, 컴파일러는 해당 타입에 맞는 코드를 생성합니다.

제네릭 메서드를 정의할 때는 메서드의 반환 타입 앞에 타입 파라미터를 선언하고, 메서드의 매개변수나 로컬 변수에서도 이 타입 파라미터를 사용할 수 있습니다.

public class Main {
    // 제네릭 메서드
    public static <T> boolean isEqual(T first, T second) {
        return first.equals(second);
    }

    public static void main(String[] args) {
        Integer firstInt = 10;
        Integer secondInt = 20;
        String firstStr = "Hello";
        String secondStr = "Hello";

        // 제네릭 메서드 호출
        boolean intResult = isEqual(firstInt, secondInt); // 두 정수가 같은지 확인
        boolean strResult = isEqual(firstStr, secondStr); // 두 문자열이 같은지 확인

        System.out.println("Integer Result: " + intResult); // 출력: false
        System.out.println("String Result: " + strResult); // 출력: true
    }
}

제네릭 메서드를 사용하면 코드의 재사용성을 높이고, 타입 안정성을 유지할 수 있습니다. 이는 컴파일 시점에서 타입 체크를 수행하여 잘못된 타입의 인수를 전달하는 오류를 방지할 수 있게 해줍니다.

4. 제한된 타입 파라미터

제한된 타입 파라미터(Bounded Type Parameter)는 특정 타입 또는 그 하위 타입만을 받아들일 수 있는 제네릭 타입 파라미터입니다. 이를 사용하면 제네릭 클래스나 메서드가 특정한 타입이나 그 하위 타입만을 다룰 수 있도록 제한할 수 있습니다.

제한된 타입 파라미터를 사용하는 가장 일반적인 방법은 특정 클래스를 확장한 클래스만을 받아들이도록 지정하는 것입니다.

public class Main {
    // 제한된 타입 파라미터를 사용한 제네릭 메서드
    public static <T extends Comparable<T>> int compare(T first, T second) {
        return first.compareTo(second);
    }

    public static void main(String[] args) {
        int result1 = compare(10, 20); // 정수형 비교
        System.out.println("Result1: " + result1); // 출력: -1

        int result2 = compare("Hello", "World"); // 문자열 비교
        System.out.println("Result2: " + result2); // 출력: -15
    }
}

위의 예시에서 <T extends Comparable<T>>는 Comparable 인터페이스를 구현한 클래스만을 받아들일 수 있는 제한된 타입 파라미터를 선언한 것입니다. 이제 compare 메서드는 Comparable 인터페이스를 구현한 클래스의 객체만을 비교할 수 있습니다.

이러한 제한된 타입 파라미터를 사용하면 프로그램의 안정성을 높일 수 있습니다. 예를 들어, 제한된 타입 파라미터를 사용하면 메서드나 클래스의 사용 방법을 명확히 제한할 수 있으며, 컴파일 시점에서 타입 안전성을 보장할 수 있습니다.

5. 와일드카드 타입 파라미터

와일드 카드 타입 파라미터(Wildcard Type Parameter)는 ? 기호를 사용하여 표시되며, 이를 통해 범위에 있는 모든 타입으로 대체할 수 있음을 나타냅니다. 와일드 카드 타입 파라미터는 제한된 타입 파라미터와는 달리 특정한 타입이나 그 하위 타입에 대한 제한 없이 다양한 타입을 다룰 수 있도록 해줍니다.

와일드 카드는 주로 제네릭 메서드에서 다양한 타입의 컬렉션을 다룰 때 사용됩니다. 세 가지 종류의 와일드 카드가 있습니다.

  1. <?>: 비한정적 와일드 카드(Unbounded Wildcard)
  2. <? extends T>: 상한 와일드 카드(Upper Bounded Wildcard)
  3. <? super T>: 하한 와일드 카드(Lower Bounded Wildcard)

비한정적 와일드 카드(<?>)는 모든 타입을 대체할 수 있으며, 상한 와일드 카드(<? extends T>)는 특정 타입 T 또는 T의 하위 타입을 대체할 수 있습니다. 반면에 하한 와일드 카드(<? super T>)는 특정 타입 T 또는 T의 상위 타입을 대체할 수 있습니다.

import java.util.List;

public class Main {
    public static void printList(List<?> list) {
        for (Object obj : list) {
            System.out.print(obj + " ");
        }
        System.out.println();
    }

    public static void main(String[] args) {
        List<Integer> intList = List.of(1, 2, 3, 4, 5);
        List<String> strList = List.of("Hello", "World");

        // 제네릭 메서드 호출
        printList(intList); // 출력: 1 2 3 4 5
        printList(strList); // 출력: Hello World
    }
}

printList 메서드는 비한정적 와일드 카드(<?>)를 사용하여 모든 타입의 컬렉션을 출력합니다. 이를 통해 다양한 타입의 컬렉션을 다룰 수 있으며, 코드의 재사용성과 유연성을 향상시킬 수 있습니다.

profile
터널을 지나고 있을 뿐, 길은 여전히 열려 있다.

0개의 댓글