3주차 Unit 5.2 — 타입 매개변수와 타입 인자

Psj·2026년 5월 19일

F-lab

목록 보기
93/240

Unit 5.2 — 타입 매개변수와 타입 인자

F-LAB JAVA · 3주차 · Phase 5 · 제네릭과 와일드카드


📌 학습 목표

이 Unit을 끝내면 다음을 답할 수 있어야 한다.

  • 타입 매개변수 (Type Parameter)타입 인자 (Type Argument) 의 차이는?
  • T, E, K, V, R, N, S, U 등 관례적 이름의 의미는?
  • 제네릭 클래스제네릭 메서드 의 정의 방법은?
  • 다중 타입 매개변수 (<K, V>, <T, R>) 활용은?
  • bounded type parameter (<T extends Number>) 의 의미와 효과는?
  • 다중 bound (<T extends Number & Comparable<T>>) 는?
  • 재귀적 타입 bound (<T extends Comparable<T>>) 의 의미는?
  • 타입 매개변수의 스코프 와 가시성은?
  • static 멤버에 타입 매개변수 못 쓰는 이유 는?

🎯 핵심 한 문장

타입 매개변수는 "타입을 매개변수처럼" 받는 placeholder 이다.
정의 시점엔 <T> 같은 추상 이름, 사용 시점엔 <String> 같은 구체 타입 (타입 인자).
관례적 이름 (T, E, K, V, R) 이 코드 의도를 명확히 하며,
<T extends Number> 같은 bound 로 타입 범위를 제한해 더 안전하고 유용한 제네릭 작성 가능.
Comparable<T>, Function<T, R>, Map<K, V> 같은 자바 표준이 이 패턴의 모범.

비유 — 함수의 매개변수

일반 함수 매개변수:
  int add(int a, int b)
  
  - a, b: 정의 시 placeholder
  - add(2, 3): 사용 시 실제 값
  
제네릭 타입 매개변수:
  class Box<T>
  
  - T: 정의 시 placeholder (타입의 이름)
  - Box<String>: 사용 시 실제 타입 (타입 인자)

→ 타입 매개변수 = 함수 매개변수의 타입 버전.


🧭 9개 섹션 로드맵

1. 타입 매개변수 vs 타입 인자
2. 관례적 이름 (T, E, K, V, R, N, S, U)
3. 제네릭 클래스 정의
4. 제네릭 메서드 정의
5. 다중 타입 매개변수
6. bounded type parameter
7. 재귀적 타입 bound
8. 타입 매개변수의 스코프와 제약
9. 면접 + 자기 점검

1️⃣ 타입 매개변수 vs 타입 인자

1.1 두 용어의 정확한 의미

타입 매개변수 (Type Parameter):
  - 제네릭 정의 시 placeholder
  - 형식 (formal) 타입 매개변수
  - 예: class Box<T> 의 T
  
타입 인자 (Type Argument):
  - 제네릭 사용 시 실제 타입
  - 실제 (actual) 타입 인자
  - 예: Box<String> 의 String

1.2 함수 매개변수와의 유사성

// 함수의 매개변수
public int add(int a, int b) {   // a, b: 매개변수 (placeholder)
    return a + b;
}

add(2, 3);   // 2, 3: 인자 (실제 값)


// 제네릭의 타입 매개변수
public class Box<T> {   // T: 타입 매개변수 (placeholder)
    private T value;
    public T get() { return value; }
    public void set(T value) { this.value = value; }
}

Box<String> box = new Box<>();   // String: 타입 인자 (실제 타입)

핵심:

  • 매개변수 = 정의 시
  • 인자 = 사용 시

1.3 코드에서의 식별

// 타입 매개변수 (정의 부분)
public class Pair<K, V> {    // ← K, V: 타입 매개변수
    private K key;            // ← K: 타입 매개변수 사용
    private V value;
}

public class HashMap<K, V> implements Map<K, V> {
    // K, V: 타입 매개변수
}

public <T> T process(T input) {   // ← T: 타입 매개변수 (제네릭 메서드)
    return input;
}

// 타입 인자 (사용 부분)
Pair<String, Integer> p = new Pair<>();
// ↑              ↑
// String, Integer: 타입 인자

Map<Long, Shipment> map = new HashMap<>();
// ↑    ↑
// Long, Shipment: 타입 인자

String result = process("hello");
// 컴파일러가 T = String 으로 추론

1.4 컴파일러의 처리

// 사용자 코드
Box<String> box = new Box<>();
box.set("hello");
String s = box.get();

// 컴파일러의 이해:
// 1. Box<String> — String 을 T 자리에
// 2. set(T value) → set(String value)
// 3. get() : T → get() : String
// 4. "hello" → set 의 String 매개변수와 매칭
// 5. box.get() : String — 캐스트 불필요

1.5 placeholder 의 의미

// T 는 placeholder — 어떤 타입이든 올 수 있음
public class Container<T> {
    private List<T> items = new ArrayList<>();
    
    public void add(T item) { items.add(item); }
    public T get(int i) { return items.get(i); }
}

// 다양한 타입으로 사용
Container<String> strings = new Container<>();
strings.add("hello");
String s = strings.get(0);

Container<Integer> ints = new Container<>();
ints.add(42);
Integer i = ints.get(0);

Container<Shipment> shipments = new Container<>();
shipments.add(new Shipment());
Shipment ship = shipments.get(0);

// 한 클래스 → 다양한 타입

1.6 같은 메서드의 다양한 호출

public class Utils {
    public static <T> T first(List<T> list) {
        return list.get(0);
    }
}

// 다양한 타입으로 호출
List<String> names = List.of("Alice", "Bob");
String name = Utils.first(names);   // T = String 추론

List<Integer> nums = List.of(1, 2, 3);
Integer num = Utils.first(nums);   // T = Integer 추론

List<Shipment> shipments = ...;
Shipment first = Utils.first(shipments);   // T = Shipment 추론

// 한 메서드 → 다양한 타입

1.7 자기 점검 답변

타입 매개변수와 타입 인자의 차이는?

:

  • 타입 매개변수 (정의):

    • class Box<T>T
    • placeholder
    • 추상 이름
    • 정의 시 사용
  • 타입 인자 (사용):

    • Box<String>String
    • 실제 타입
    • 구체적 값
    • 사용 시 명시

비유:

  • 함수의 매개변수 vs 인자
  • int add(int a)a vs add(5)5

2️⃣ 관례적 이름 (T, E, K, V, R, N, S, U)

2.1 관례 이름의 의미

T — Type (일반 타입)
  - 가장 일반적
  - "어떤 타입이든"
  - 예: Box<T>, Optional<T>, Class<T>

E — Element (요소)
  - 컬렉션의 요소
  - 예: List<E>, Set<E>, Queue<E>

K — Key (키)
  - Map 의 키
  - 예: Map<K, V>

V — Value (값)
  - Map 의 값
  - 예: Map<K, V>

R — Return / Result (반환 타입)
  - 함수의 반환
  - 예: Function<T, R>

N — Number
  - 숫자 타입 (드물게)

S, U, V — 추가 타입 (T 다음)
  - 예: BiFunction<T, U, R>
  - T, U, V 순으로 추가

2.2 자바 표준의 사용

// 컬렉션 — E (Element)
public interface List<E> { ... }
public interface Set<E> { ... }
public interface Queue<E> { ... }
public interface Iterable<E> { ... }

// Map — K, V (Key, Value)
public interface Map<K, V> {
    interface Entry<K, V> { ... }
}

// 일반 타입 — T
public class Box<T> { ... }
public final class Class<T> { ... }
public final class Optional<T> { ... }
public final class ThreadLocal<T> { ... }

// 함수형 — T, R
public interface Function<T, R> {
    R apply(T t);
}

public interface Predicate<T> {
    boolean test(T t);
}

// 다중 — T, U, R
public interface BiFunction<T, U, R> {
    R apply(T t, U u);
}

// Comparable — T (자기 타입)
public interface Comparable<T> {
    int compareTo(T other);
}

2.3 이름 선택 가이드

일반 가이드:

단순 컨테이너 → T
  Box<T>, Wrapper<T>

요소 컬렉션 → E
  List<E>, Set<E>

키-값 → K, V
  Map<K, V>, Cache<K, V>

함수 입력/출력 → T, R
  Function<T, R>, Transformer<T, R>

두 개 이상 입력 → T, U, V
  BiFunction<T, U, R>, Triple<A, B, C>

특수 의도:
  N — Number
  S — Source (변환의 소스)
  D — Destination (변환의 목적지)

2.4 명확한 이름 사용 예

// 1. 단순 박스 — T
public class Box<T> {
    private T value;
}

// 2. 컬렉션 — E
public class MyList<E> {
    private E[] elements;
}

// 3. 캐시 — K, V
public class Cache<K, V> {
    private Map<K, V> store = new HashMap<>();
    
    public V get(K key) { return store.get(key); }
    public void put(K key, V value) { store.put(key, value); }
}

// 4. 변환기 — T, R
public class Transformer<T, R> {
    private Function<T, R> fn;
    
    public R transform(T input) {
        return fn.apply(input);
    }
}

// 5. ILIC 도메인
public class ShipmentEvent<T extends Shipment> {
    private T shipment;
    private LocalDateTime occurredAt;
}

public interface ShipmentEventHandler<E extends ShipmentEvent<?>> {
    void handle(E event);
}

2.5 단일 문자 vs 긴 이름

// 단일 문자 (자바 관례)
public class Box<T> {
    private T value;
}

// 긴 이름 (가능하지만 권장 X)
public class Box<TypeOfValue> {
    private TypeOfValue value;
}

권장:
  - 단일 문자 + 관례 이름
  - T, E, K, V, R
  - 코드가 깔끔
  
긴 이름은:
  - 의미가 매우 복잡할 때만
  - 또는 제네릭이 많아 혼란 시

2.6 다중 매개변수의 이름

// 두 개
public class Pair<A, B> {
    private A first;
    private B second;
}

// 세 개
public class Triple<A, B, C> {
    private A first;
    private B second;
    private C third;
}

// 네 개 이상은 권장 X
// 의도 불명확 → 별도 클래스로 분리 권장

// Spring 의 BiFunction 패턴
public interface ThreeFunction<T1, T2, T3, R> {
    R apply(T1 t1, T2 t2, T3 t3);
}
// → 사용 시 의도 불명확, 별도 클래스 권장

2.7 도메인 특화 이름

// 도메인이 명확하면 도메인 이름 가능
public interface Repository<Entity, Id> {
    Entity findById(Id id);
    List<Entity> findAll();
    Entity save(Entity entity);
    void delete(Id id);
}

// 사용
public class ShipmentRepository implements Repository<Shipment, Long> {
    // ...
}

// 또는 관례
public interface Repository<T, ID> {
    T findById(ID id);
    // ...
}

→ 두 방식 모두 가능. 관례 (T, ID) 가 더 일반적.

2.8 자기 점검 답변

자바 제네릭의 관례 이름과 의미는?

:
1. T — Type (일반): Box<T>, Optional<T>
2. E — Element (요소): List<E>, Set<E>
3. K — Key (키): Map<K, V>
4. V — Value (값): Map<K, V>
5. R — Return (반환): Function<T, R>
6. N — Number (숫자): 드물게
7. S, U — 추가 타입: BiFunction<T, U, R>

활용:

  • 단일 문자 + 관례
  • 의도 명확화
  • 자바 표준 일관성

3️⃣ 제네릭 클래스 정의

3.1 기본 문법

// 클래스 이름 뒤에 <T>
public class Box<T> {
    // 필드, 메서드 안에서 T 사용 가능
    private T value;
    
    public Box(T value) {
        this.value = value;
    }
    
    public T getValue() {
        return value;
    }
    
    public void setValue(T value) {
        this.value = value;
    }
}

// 사용
Box<String> stringBox = new Box<>("Hello");
String s = stringBox.getValue();   // String

Box<Integer> intBox = new Box<>(42);
Integer i = intBox.getValue();

3.2 다중 타입 매개변수

public class Pair<A, B> {
    private A first;
    private B second;
    
    public Pair(A first, B second) {
        this.first = first;
        this.second = second;
    }
    
    public A getFirst() { return first; }
    public B getSecond() { return second; }
    
    public <C> Triple<A, B, C> withThird(C third) {   // 메서드 자체의 추가 매개변수
        return new Triple<>(first, second, third);
    }
}

// 사용
Pair<String, Integer> p = new Pair<>("Age", 25);
String key = p.getFirst();
Integer value = p.getSecond();

3.3 제네릭 클래스 사용

public class ShipmentBatch<T extends Shipment> {
    private List<T> shipments = new ArrayList<>();
    
    public void add(T shipment) {
        shipments.add(shipment);
    }
    
    public T get(int i) {
        return shipments.get(i);
    }
    
    public int size() {
        return shipments.size();
    }
    
    public BigDecimal totalWeight() {
        BigDecimal total = BigDecimal.ZERO;
        for (T s : shipments) {
            total = total.add(s.getWeight());
        }
        return total;
    }
}

// 사용
ShipmentBatch<SeaShipment> seaBatch = new ShipmentBatch<>();
seaBatch.add(new SeaShipment());
SeaShipment s = seaBatch.get(0);

ShipmentBatch<AirShipment> airBatch = new ShipmentBatch<>();
airBatch.add(new AirShipment());

3.4 필드, 메서드, 생성자에서의 사용

public class Container<T> {
    
    // 필드
    private T value;
    private List<T> history = new ArrayList<>();
    
    // 생성자
    public Container() {
        this.value = null;
    }
    
    public Container(T initial) {
        this.value = initial;
        this.history.add(initial);
    }
    
    // 메서드 매개변수 + 반환
    public T get() {
        return value;
    }
    
    public void set(T newValue) {
        history.add(value);
        this.value = newValue;
    }
    
    public List<T> getHistory() {
        return new ArrayList<>(history);
    }
    
    // T 와 무관한 메서드
    public int historySize() {
        return history.size();
    }
    
    public boolean isEmpty() {
        return value == null;
    }
}

3.5 제네릭 클래스의 상속

// 1. 제네릭 클래스 상속
public class StringBox extends Box<String> {
    // Box<String> 의 자식
    // T = String 으로 고정
    
    public void printValue() {
        String value = getValue();   // 이미 String
        System.out.println(value);
    }
}

// 2. 제네릭 클래스의 제네릭 자식
public class TypedBox<T> extends Box<T> {
    // T 를 자식도 받음
    
    public void printType() {
        System.out.println(getValue().getClass());
    }
}

// 3. 인터페이스 구현
public class Stack<E> implements Collection<E> {
    // E 를 Collection 으로 전달
}

3.6 제네릭 클래스의 메서드 시그니처

public class Box<T> {
    
    // T 를 사용하는 메서드
    public T get() { return value; }
    public void set(T value) { ... }
    
    // 추가 타입 매개변수 (메서드만의 매개변수)
    public <U> U transform(Function<T, U> fn) {
        return fn.apply(value);
    }
    
    // 호출 예
    // Box<String> box = ...
    // Integer length = box.transform(String::length);
    
    // T 없는 메서드도 가능
    public boolean isEmpty() {
        return value == null;
    }
    
    // toString, equals, hashCode 도 가능
    @Override
    public String toString() {
        return "Box[" + value + "]";
    }
    
    @Override
    public boolean equals(Object o) {
        // ...
    }
}

3.7 자바 표준의 제네릭 클래스

// 컬렉션
public class ArrayList<E> extends AbstractList<E> implements List<E> { ... }
public class HashMap<K, V> implements Map<K, V> { ... }
public class HashSet<E> implements Set<E> { ... }

// Optional
public final class Optional<T> {
    private final T value;
    // ...
}

// Class
public final class Class<T> {
    // T 는 이 Class 객체가 표현하는 클래스의 타입
    // 예: Class<String> = String.class
}

// ThreadLocal
public class ThreadLocal<T> {
    public T get() { ... }
    public void set(T value) { ... }
}

// AtomicReference
public class AtomicReference<V> {
    private volatile V value;
    public V get() { ... }
    public void set(V newValue) { ... }
}

3.8 자기 점검 답변

제네릭 클래스에서 T 의 활용처는?

:
1. 필드 타입: private T value;
2. 메서드 반환 타입: public T get() { ... }
3. 메서드 매개변수: public void set(T value) { ... }
4. 생성자 매개변수: public Box(T initial) { ... }
5. 다른 제네릭의 인자: private List<T> items;

추가:

  • 메서드 자체의 추가 매개변수: <U> U transform(...)
  • 상속/구현 시 전달: class TypedBox<T> extends Box<T>

4️⃣ 제네릭 메서드 정의

4.1 기본 문법

// 메서드 시그니처 앞에 <T>
public <T> T process(T input) {
    return input;
}

// 호출
String s = process("hello");   // T = String
Integer i = process(42);       // T = Integer

핵심:

  • 반환 타입 앞에 <T> 선언
  • 메서드 안에서 T 사용
  • 호출 시 컴파일러가 T 추론

4.2 제네릭 메서드 vs 제네릭 클래스 메서드

// 1. 제네릭 클래스의 메서드 — 클래스의 T 사용
public class Box<T> {
    public T get() { return value; }   // 클래스의 T
}

// 2. 일반 클래스의 제네릭 메서드 — 메서드만의 T
public class Utils {
    public static <T> T first(List<T> list) {   // 메서드만의 T
        return list.get(0);
    }
}

// 3. 제네릭 클래스의 다른 매개변수 제네릭 메서드
public class Box<T> {
    private T value;
    
    public <U> U transform(Function<T, U> fn) {   // T (클래스) + U (메서드)
        return fn.apply(value);
    }
}

4.3 static vs 인스턴스 제네릭 메서드

public class Utils {
    
    // static 제네릭 메서드
    public static <T> T first(List<T> list) {
        return list.get(0);
    }
    
    // 인스턴스 제네릭 메서드 (드물게)
    public <T> T process(T input) {
        return input;
    }
}

// 호출
String s = Utils.first(List.of("a", "b"));   // static
String s2 = new Utils().process("hello");     // 인스턴스

4.4 메서드 시그니처의 다양한 형태

public class Utils {
    
    // 1. 단순 — 입력과 출력 같은 타입
    public static <T> T identity(T value) {
        return value;
    }
    
    // 2. 입력 ≠ 출력
    public static <T, R> R transform(T input, Function<T, R> fn) {
        return fn.apply(input);
    }
    
    // 3. 다중 입력
    public static <A, B> Pair<A, B> pair(A first, B second) {
        return new Pair<>(first, second);
    }
    
    // 4. 가변 인자
    public static <T> List<T> asList(T... values) {
        List<T> list = new ArrayList<>();
        for (T v : values) list.add(v);
        return list;
    }
    
    // 5. 컬렉션 입력
    public static <T> T find(Collection<T> collection, Predicate<T> filter) {
        for (T item : collection) {
            if (filter.test(item)) return item;
        }
        return null;
    }
    
    // 6. 여러 컬렉션
    public static <T> List<T> concat(List<T> a, List<T> b) {
        List<T> result = new ArrayList<>(a);
        result.addAll(b);
        return result;
    }
}

// 사용
String s = Utils.identity("hello");
Integer len = Utils.<String, Integer>transform("hello", String::length);
// 또는 Utils.transform("hello", String::length); — 자동 추론

Pair<String, Integer> p = Utils.pair("age", 25);
List<String> list = Utils.asList("a", "b", "c");

4.5 명시적 타입 인자 vs 자동 추론

public class Utils {
    public static <T> List<T> empty() {
        return new ArrayList<>();
    }
}

// 자동 추론 (대부분)
List<String> strings = Utils.empty();   // T = String 추론

// 명시적 (가끔 필요)
List<String> strings2 = Utils.<String>empty();   // T = String 명시

// 명시 필요한 경우:
// 1. 반환 타입만 사용 (매개변수에 T 없음)
// 2. 컴파일러 추론 실패
// 3. 다른 매개변수 등으로 인한 모호함

4.6 제네릭 메서드의 일반 활용

// Collections 의 유틸리티들
public class Collections {
    
    public static <T extends Comparable<? super T>> void sort(List<T> list) { ... }
    
    public static <T> int binarySearch(List<? extends Comparable<? super T>> list, T key) { ... }
    
    public static <T> T max(Collection<? extends T> coll, Comparator<? super T> comp) { ... }
    
    public static <T> List<T> unmodifiableList(List<? extends T> list) { ... }
    
    public static <T> List<T> emptyList() { ... }
}

// Stream 의 메서드들
public interface Stream<T> {
    
    <R> Stream<R> map(Function<? super T, ? extends R> mapper);
    
    <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
    
    <R, A> R collect(Collector<? super T, A, R> collector);
}

4.7 제네릭 메서드 활용 패턴

// 패턴 1: 팩토리 메서드
public static <T> List<T> emptyList() {
    return new ArrayList<>();
}

public static <T> Optional<T> empty() {
    return Optional.empty();
}

// 패턴 2: 변환
public static <T, R> List<R> map(List<T> source, Function<T, R> mapper) {
    return source.stream().map(mapper).collect(Collectors.toList());
}

// 패턴 3: 필터
public static <T> List<T> filter(List<T> source, Predicate<T> filter) {
    return source.stream().filter(filter).collect(Collectors.toList());
}

// 패턴 4: 검색
public static <T> Optional<T> findFirst(List<T> source, Predicate<T> filter) {
    return source.stream().filter(filter).findFirst();
}

// 패턴 5: 빌더
public static <T, R> R build(T input, Function<T, R> builder) {
    return builder.apply(input);
}

4.8 ILIC 활용 예

public class ShipmentUtils {
    
    // 검색
    public static <T extends Shipment> Optional<T> findByBlNo(
            List<T> shipments, String blNo) {
        return shipments.stream()
            .filter(s -> blNo.equals(s.getBlNo()))
            .findFirst();
    }
    
    // 그룹핑
    public static <T extends Shipment, K> Map<K, List<T>> groupBy(
            List<T> shipments, Function<T, K> keyExtractor) {
        return shipments.stream()
            .collect(Collectors.groupingBy(keyExtractor));
    }
    
    // 변환
    public static <T extends Shipment, R> List<R> mapTo(
            List<T> shipments, Function<T, R> mapper) {
        return shipments.stream()
            .map(mapper)
            .toList();
    }
    
    // 통계
    public static <T extends Shipment> BigDecimal totalWeight(List<T> shipments) {
        return shipments.stream()
            .map(Shipment::getWeight)
            .reduce(BigDecimal.ZERO, BigDecimal::add);
    }
}

// 사용
List<SeaShipment> seaShipments = ...;
Optional<SeaShipment> found = ShipmentUtils.findByBlNo(seaShipments, "BL-001");

Map<String, List<SeaShipment>> byRoute = 
    ShipmentUtils.groupBy(seaShipments, Shipment::getRoute);

4.9 자기 점검 답변

제네릭 메서드 정의와 호출 방법은?

:
정의:

public static <T> T first(List<T> list) {
    return list.get(0);
}
  • 반환 타입 앞에 <T> 선언
  • 메서드 안에서 T 사용

호출:

// 자동 추론
String s = first(List.of("a", "b"));

// 명시적 (드물게)
String s = Utils.<String>first(List.of("a", "b"));

용도:

  • 유틸리티 메서드 (Collections.sort 등)
  • 팩토리 메서드 (emptyList)
  • 변환 메서드 (map, filter)

5️⃣ 다중 타입 매개변수

5.1 다중 매개변수의 정의

// 두 개
public class Pair<A, B> {
    private A first;
    private B second;
}

// 세 개
public class Triple<A, B, C> {
    private A first;
    private B second;
    private C third;
}

// 네 개 이상 (권장 X)
public class Quad<A, B, C, D> { ... }

5.2 Map<K, V> 의 모범 사례

public interface Map<K, V> {
    
    V get(Object key);
    V put(K key, V value);
    
    Set<K> keySet();
    Collection<V> values();
    Set<Entry<K, V>> entrySet();
    
    interface Entry<K, V> {   // 중첩 인터페이스도 K, V
        K getKey();
        V getValue();
    }
}

// 구현
public class HashMap<K, V> implements Map<K, V> {
    static class Node<K, V> {   // 내부 Node 도 K, V
        K key;
        V value;
        // ...
    }
}

// 사용
Map<String, Integer> ages = new HashMap<>();
ages.put("Alice", 25);
Integer age = ages.get("Alice");

for (Map.Entry<String, Integer> entry : ages.entrySet()) {
    String name = entry.getKey();
    Integer a = entry.getValue();
}

5.3 Function<T, R> 의 모범

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
    
    // 합성
    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        return (V v) -> apply(before.apply(v));
    }
    
    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        return (T t) -> after.apply(apply(t));
    }
    
    static <T> Function<T, T> identity() {
        return t -> t;
    }
}

// 사용
Function<String, Integer> length = String::length;
Integer len = length.apply("hello");   // 5

Function<Integer, Integer> doubleIt = n -> n * 2;
Function<String, Integer> lengthDoubled = length.andThen(doubleIt);
lengthDoubled.apply("hello");   // 10

5.4 BiFunction<T, U, R>

@FunctionalInterface
public interface BiFunction<T, U, R> {
    R apply(T t, U u);
    
    default <V> BiFunction<T, U, V> andThen(Function<? super R, ? extends V> after) {
        return (T t, U u) -> after.apply(apply(t, u));
    }
}

// 사용
BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b;
Integer sum = add.apply(2, 3);   // 5

BiFunction<String, Integer, String> repeat = (s, n) -> s.repeat(n);
String repeated = repeat.apply("ab", 3);   // "ababab"

5.5 사용자 정의 다중 매개변수

// 캐시 — Key, Value
public class Cache<K, V> {
    private Map<K, V> store = new HashMap<>();
    private long expirationMs;
    
    public V get(K key) { return store.get(key); }
    public void put(K key, V value) { store.put(key, value); }
}

// 변환기 — Input, Output
public class Converter<I, O> {
    private Function<I, O> fn;
    
    public Converter(Function<I, O> fn) {
        this.fn = fn;
    }
    
    public O convert(I input) {
        return fn.apply(input);
    }
}

// Repository — Entity, Id
public interface Repository<Entity, Id> {
    Entity findById(Id id);
    List<Entity> findAll();
    Entity save(Entity entity);
    void delete(Id id);
}

// 사용
Cache<Long, Shipment> shipmentCache = new Cache<>();
Converter<Shipment, ShipmentResponse> converter = 
    new Converter<>(ShipmentResponse::from);

5.6 메서드의 다중 타입 매개변수

public class Utils {
    
    // T 와 R 변환
    public static <T, R> List<R> mapList(List<T> input, Function<T, R> fn) {
        List<R> result = new ArrayList<>();
        for (T t : input) {
            result.add(fn.apply(t));
        }
        return result;
    }
    
    // 두 컬렉션 합치기
    public static <A, B> List<Pair<A, B>> zip(List<A> a, List<B> b) {
        List<Pair<A, B>> result = new ArrayList<>();
        int size = Math.min(a.size(), b.size());
        for (int i = 0; i < size; i++) {
            result.add(new Pair<>(a.get(i), b.get(i)));
        }
        return result;
    }
    
    // Map 변환
    public static <K, V1, V2> Map<K, V2> mapValues(
            Map<K, V1> input, Function<V1, V2> fn) {
        Map<K, V2> result = new HashMap<>();
        for (Map.Entry<K, V1> entry : input.entrySet()) {
            result.put(entry.getKey(), fn.apply(entry.getValue()));
        }
        return result;
    }
}

// 사용
List<String> names = List.of("Alice", "Bob");
List<Integer> lengths = Utils.mapList(names, String::length);

List<String> keys = List.of("a", "b");
List<Integer> values = List.of(1, 2);
List<Pair<String, Integer>> zipped = Utils.zip(keys, values);

5.7 다중 매개변수의 한계

// 너무 많은 타입 매개변수는 가독성 ↓
public interface ThreeArgFunction<T1, T2, T3, R> {
    R apply(T1 a, T2 b, T3 c);
}

// 더 많아지면 의도 불명확
public interface FiveArgFunction<A, B, C, D, E, R> {
    R apply(A a, B b, C c, D d, E e);
}

// 대안:
// 1. 별도 데이터 클래스
public record Input(String a, Integer b, Double c) { }
public interface Processor<R> {
    R process(Input input);
}

// 2. 빌더 패턴
public class CalculationBuilder {
    public CalculationBuilder with(String key, Object value) { ... }
    public Result execute() { ... }
}

5.8 자기 점검 답변

다중 타입 매개변수를 사용할 때 주의점은?

:
1. 개수 제한:

  • 2-3 개까지 권장
  • 4개 이상은 가독성 ↓
  • 별도 클래스/빌더 고려
  1. 관례 이름:

    • K, V (Map)
    • T, R (Function)
    • A, B (Pair)
  2. 의도 명확화:

    • 단순 데이터 → record
    • 복잡한 처리 → 빌더 또는 별도 클래스
  3. 자바 표준 따르기:

    • Map<K, V>
    • Function<T, R>
    • BiFunction<T, U, R>

6️⃣ bounded type parameter

6.1 bound 의 정의

bounded type parameter:

  타입 매개변수의 범위를 제한하는 방법.
  
문법:
  <T extends UpperBound>
  
의미:
  T 는 UpperBound 또는 그 자식 타입.

6.2 기본 문법

// T 는 Number 또는 자식 타입 (Integer, Long, Double, ...)
public class NumberBox<T extends Number> {
    private T value;
    
    public NumberBox(T value) {
        this.value = value;
    }
    
    // T 의 Number 메서드 사용 가능
    public double asDouble() {
        return value.doubleValue();   // Number 의 메서드
    }
}

// 사용
NumberBox<Integer> intBox = new NumberBox<>(42);
intBox.asDouble();   // 42.0

NumberBox<Double> doubleBox = new NumberBox<>(3.14);
doubleBox.asDouble();   // 3.14

// NumberBox<String> ❌ — String 은 Number 아님

6.3 bound 가 없을 때의 한계

// bound 없으면 Object 의 메서드만 사용 가능
public class Box<T> {
    private T value;
    
    public void process() {
        value.toString();   // ✓ Object 의 메서드
        value.hashCode();   // ✓ Object 의 메서드
        // value.doubleValue();   // ❌ Number 의 메서드 못 씀
    }
}

// bound 있으면 그 타입의 메서드 사용 가능
public class NumberBox<T extends Number> {
    private T value;
    
    public void process() {
        value.doubleValue();   // ✓ Number 의 메서드
        value.intValue();      // ✓
        value.longValue();     // ✓
    }
}

6.4 다중 bound

// 여러 인터페이스/클래스 결합
public class SortableBox<T extends Number & Comparable<T>> {
    private T value;
    
    public boolean isGreaterThan(T other) {
        return value.compareTo(other) > 0;   // Comparable 의 메서드
    }
    
    public double asDouble() {
        return value.doubleValue();   // Number 의 메서드
    }
}

// 규칙:
// - 클래스는 처음에 (있다면) 하나만
// - 인터페이스는 여러 개 가능
// - extends 키워드만 사용 (implements 아님)
// - & 로 연결

// 예
<T extends Number & Comparable<T>>
<T extends Object & Serializable & Comparable<T>>
<T extends MyClass & MyInterface1 & MyInterface2>

6.5 bound 의 활용 예

// 1. Number 의 자식만 — 수학 연산
public class Calculator<T extends Number> {
    public double sum(List<T> numbers) {
        double total = 0;
        for (T n : numbers) {
            total += n.doubleValue();
        }
        return total;
    }
}

Calculator<Integer> intCalc = new Calculator<>();
intCalc.sum(List.of(1, 2, 3));   // 6.0

Calculator<Double> doubleCalc = new Calculator<>();
doubleCalc.sum(List.of(1.5, 2.5));   // 4.0

// 2. Comparable — 정렬 가능
public class SortedBox<T extends Comparable<T>> {
    private List<T> items = new ArrayList<>();
    
    public void add(T item) {
        items.add(item);
        Collections.sort(items);   // T 가 Comparable 이라 가능
    }
}

// 3. 인터페이스 + 클래스 결합
public class Repository<T extends BaseEntity & Auditable> {
    public void save(T entity) {
        entity.setUpdatedAt(LocalDateTime.now());   // Auditable
        // BaseEntity 의 메서드
    }
}

6.6 자바 표준의 bound 활용

// Collections.sort
public static <T extends Comparable<? super T>> void sort(List<T> list) {
    // T 가 Comparable 이라 비교 가능
    list.sort(null);
}

// Collections.max
public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll) {
    // T 가 Comparable
}

// Stream.max
public Optional<T> max(Comparator<? super T> comparator) {
    // Comparator 의 compare 사용
}

// TreeMap
public class TreeMap<K, V> {
    // K 는 Comparable<K> 또는 Comparator<K> 제공해야
}

6.7 bound 의 효과

1. 타입 안전성 ↑
   - 특정 타입의 메서드 사용 가능
   - Object 의 메서드만 사용했던 한계 해소

2. 의도 명확화
   - "이 클래스는 Number 만 받음" 명시
   - 사용자가 잘못된 타입 회피

3. 컴파일러 검증
   - 잘못된 타입 인자 → 컴파일 에러
   - 런타임 안전

4. 표현력 ↑
   - 제네릭이 더 유용한 동작 가능
   - 단순 컨테이너 이상

6.8 ILIC 활용 예

// 모든 Shipment 자식을 받는 처리기
public class ShipmentProcessor<T extends Shipment> {
    
    public BigDecimal totalWeight(List<T> shipments) {
        return shipments.stream()
            .map(Shipment::getWeight)
            .reduce(BigDecimal.ZERO, BigDecimal::add);
    }
    
    public Map<String, List<T>> groupByRoute(List<T> shipments) {
        return shipments.stream()
            .collect(Collectors.groupingBy(Shipment::getRoute));
    }
}

// 사용
ShipmentProcessor<SeaShipment> seaProcessor = new ShipmentProcessor<>();
List<SeaShipment> seaShipments = ...;
seaProcessor.totalWeight(seaShipments);

// 다중 bound 예
public class TrackableShipmentService<T extends Shipment & Trackable & Auditable> {
    
    public void track(T shipment) {
        String trackingNo = shipment.getTrackingNumber();   // Trackable
        shipment.setUpdatedAt(LocalDateTime.now());          // Auditable
        // ... Shipment 의 메서드들
    }
}

6.9 자기 점검 답변

bounded type parameter 의 효과는?

:
1. 메서드 호출 가능: bound 타입의 메서드 사용 (Object 외)
2. 타입 안전성: 잘못된 타입 인자 컴파일 에러
3. 의도 명확화: "이 클래스는 X 만 받음"
4. 다중 bound: & Comparable & Serializable 조합

문법:

  • <T extends UpperBound>
  • 다중: <T extends Number & Comparable<T>>
  • 단, upper bound 만 (extends)
  • lower bound (super) 는 와일드카드에서만 (다음 Unit)

7️⃣ 재귀적 타입 bound

7.1 재귀적 bound 의 정의

재귀적 타입 bound:

  타입 매개변수의 bound 에 자기 자신을 사용.

문법:
  <T extends Comparable<T>>
            ↑           ↑
            bound       T 자체 사용

7.2 가장 흔한 예 — Comparable

public interface Comparable<T> {
    int compareTo(T other);
}

// 사용 — 자기 자신을 비교
public class MyClass implements Comparable<MyClass> {
    @Override
    public int compareTo(MyClass other) {
        // ...
    }
}

// 제네릭에서
public static <T extends Comparable<T>> T max(List<T> list) {
    T max = list.get(0);
    for (T item : list) {
        if (item.compareTo(max) > 0) {
            max = item;
        }
    }
    return max;
}

// 호출
max(List.of(1, 2, 3));   // Integer 가 Comparable<Integer> 라 OK
max(List.of("a", "b"));   // String 이 Comparable<String> 라 OK

7.3 재귀적 bound 의 의미

<T extends Comparable<T>>

  의미:
    T 는 자기 자신과 비교 가능한 타입.
  
  즉:
    Integer implements Comparable<Integer>   ✓
    String implements Comparable<String>     ✓
    LocalDate implements Comparable<LocalDate>   ✓
    
  의도:
    "이 메서드는 자기들끼리 비교 가능한 타입만 받음"

7.4 Comparable<? super T> 의 정밀 (Phase 5.5 예고)

// 더 정확한 자바 표준
public static <T extends Comparable<? super T>> void sort(List<T> list) { ... }
//                              ↑
//                  ? super T — T 또는 그 부모

// 이유:
// 부모가 Comparable 이면 자식도 비교 가능
// 예: Number 가 Comparable, Integer 도 사용 가능

// 자세한 건 Unit 5.5 (PECS 원칙)

7.5 다른 재귀적 bound 예

// Enum<E>
public abstract class Enum<E extends Enum<E>> {
    // E 는 자기 자신을 가리키는 Enum
}

// 사용
public enum ShipmentStatus extends Enum<ShipmentStatus> {
    DRAFT, ACTIVE, COMPLETED
}
// 실제로는 컴파일러가 자동 처리

// Builder 패턴
public abstract class Builder<T extends Builder<T>> {
    
    public T with(String key, Object value) {
        // ...
        return self();
    }
    
    protected abstract T self();
}

public class ShipmentBuilder extends Builder<ShipmentBuilder> {
    @Override
    protected ShipmentBuilder self() {
        return this;
    }
}

7.6 재귀적 bound 의 활용 — Fluent API

public abstract class AbstractEntityBuilder<E, B extends AbstractEntityBuilder<E, B>> {
    
    protected Long id;
    protected LocalDateTime createdAt;
    
    public B withId(Long id) {
        this.id = id;
        return self();   // 자신의 타입 반환
    }
    
    public B withCreatedAt(LocalDateTime createdAt) {
        this.createdAt = createdAt;
        return self();
    }
    
    protected abstract B self();
    public abstract E build();
}

// 자식 빌더
public class ShipmentBuilder 
        extends AbstractEntityBuilder<Shipment, ShipmentBuilder> {
    
    private String blNo;
    private BigDecimal weight;
    
    public ShipmentBuilder withBlNo(String blNo) {
        this.blNo = blNo;
        return this;
    }
    
    public ShipmentBuilder withWeight(BigDecimal weight) {
        this.weight = weight;
        return this;
    }
    
    @Override
    protected ShipmentBuilder self() {
        return this;
    }
    
    @Override
    public Shipment build() {
        return new Shipment(id, blNo, weight, createdAt);
    }
}

// 사용 — Fluent API
Shipment s = new ShipmentBuilder()
    .withId(1L)              // ShipmentBuilder 반환
    .withCreatedAt(LocalDateTime.now())   // ShipmentBuilder 반환
    .withBlNo("BL-001")      // ShipmentBuilder 의 메서드
    .withWeight(BigDecimal.valueOf(1000))
    .build();

→ 재귀적 bound 가 Fluent API 의 핵심.

7.7 재귀적 bound 의 효과

1. 자기 타입 반환
   - return self() 가 자식의 타입 반환
   - Fluent API 가능

2. 자기 자신과의 비교
   - Comparable<T> 가 T 와 비교

3. 타입 안전성
   - 잘못된 타입 사용 컴파일 에러

4. 코드 재사용
   - 부모 클래스에 공통 메서드
   - 자식이 자기 타입 반환

7.8 자기 점검 답변

재귀적 타입 bound 의 의미와 활용은?

:

  • 의미:

    • <T extends Comparable<T>>
    • T 가 자기 자신을 가리키는 bound
    • "T 는 자기들끼리 비교 가능"
  • 활용:

    1. Comparable: 자기 자신과 비교
    2. Enum: Enum<E extends Enum<E>>
    3. Builder 패턴: 자기 타입 반환 (Fluent API)
    4. 수학적 객체: 자기 자신과의 연산
  • 이점:

    • 타입 안전한 메서드 체이닝
    • 자식 클래스에서 부모 메서드 → 자식 타입 반환

8️⃣ 타입 매개변수의 스코프와 제약

8.1 스코프

// 클래스의 T — 클래스 전체에서 사용
public class Box<T> {
    private T value;        // 필드
    
    public T get() { return value; }    // 메서드
    
    public void set(T value) { this.value = value; }
    
    static class Helper<T> {   // 내부 클래스의 T 는 별도!
        // 여기 T 는 Box<T> 의 T 와 무관
    }
}

// 메서드의 T — 메서드 안에서만 사용
public <T> T process(T input) {
    return input;
    // 메서드 끝나면 T 사용 불가
}

8.2 내부 클래스의 별도 T

public class Outer<T> {
    private T value;
    
    // 내부 클래스 — Outer 의 T 공유
    public class Inner {
        public T get() {
            return value;   // Outer 의 T
        }
    }
    
    // static 내부 클래스 — 별도 T 필요
    public static class StaticInner<T> {
        // 여기 T 는 Outer 의 T 와 다름
        // (이름만 같을 뿐)
        public T get() { ... }
    }
}

8.3 제약 1 — static 멤버에 T 불가

public class Box<T> {
    
    // ❌ static 필드
    private static T value;
    // 컴파일 에러: 'T' 는 static 컨텍스트에서 참조 불가
    
    // ❌ static 메서드의 매개변수
    public static T process(T input) {
        return input;
    }
    // 컴파일 에러
    
    // ✓ static 메서드는 자기만의 T 가능
    public static <T> T identity(T input) {
        return input;
    }
    // 메서드의 T 와 클래스의 T 는 별도
}

이유:

  • T 는 인스턴스마다 다른 타입
  • static 은 클래스 레벨 (인스턴스 무관)
  • 모순

8.4 제약 2 — 제네릭 배열 불가

public class Container<T> {
    
    // ❌ T 의 배열 생성 못 함
    private T[] array = new T[10];
    // 컴파일 에러: T 의 배열 생성 못 함
    
    // ❌ 메서드 안에서도 못 함
    public T[] toArray() {
        return new T[size];
    }
    
    // ✓ 우회 1: Object 배열 + 캐스트
    @SuppressWarnings("unchecked")
    private T[] array = (T[]) new Object[10];
    
    // ✓ 우회 2: Class<T> 전달받기
    public T[] toArray(Class<T> type) {
        T[] array = (T[]) Array.newInstance(type, 10);
        return array;
    }
    
    // ✓ 우회 3: List 사용 (대부분 더 좋음)
    private List<T> list = new ArrayList<>();
}

이유:

  • 런타임에 T 가 무엇인지 모름 (타입 소거)
  • 배열은 런타임에 타입 정보 필요
  • 모순

8.5 제약 3 — instanceof 검사

public class Container<T> {
    
    public boolean isOfType(Object obj) {
        return obj instanceof T;   // ❌ 컴파일 에러
    }
    
    // ✓ 우회: Class<T> 전달받기
    public boolean isOfType(Object obj, Class<T> type) {
        return type.isInstance(obj);
    }
    
    // 또는 raw type
    public boolean isOfTypeRaw(Object obj) {
        return obj instanceof List;   // raw, 경고
    }
    
    // 와일드카드는 가능
    public boolean isList(Object obj) {
        return obj instanceof List<?>;   // ✓
    }
}

8.6 제약 4 — T 의 new 인스턴스 생성

public class Factory<T> {
    
    // ❌ T 인스턴스 직접 생성 못 함
    public T create() {
        return new T();   // 컴파일 에러
    }
    
    // ✓ 우회 1: Class<T> 사용
    public T create(Class<T> type) throws Exception {
        return type.getDeclaredConstructor().newInstance();
    }
    
    // ✓ 우회 2: Supplier<T> 사용
    public T create(Supplier<T> supplier) {
        return supplier.get();
    }
}

// 사용
Factory<Shipment> factory = new Factory<>();
Shipment s1 = factory.create(Shipment.class);
Shipment s2 = factory.create(() -> new Shipment());

이유:

  • 런타임에 T 가 무엇인지 모름
  • 생성자 호출 불가

8.7 제약 종합

타입 매개변수 T 의 제약:

1. static 멤버에 사용 불가
   - 필드, 메서드 둘 다
   - 우회: 메서드 자체의 <T>

2. 배열 생성 불가
   - new T[]
   - 우회: Object 배열, Class<T>, List

3. instanceof 검사 제한
   - obj instanceof T
   - 우회: Class<T>.isInstance()

4. new 인스턴스 직접 생성 불가
   - new T()
   - 우회: Class<T>, Supplier<T>

5. catch 절에 사용 불가
   - } catch (T e) { ... }
   - 예외는 reified type 필요

6. 동일한 타입 매개변수의 오버로딩 제약
   - process(List<String>) + process(List<Integer>)
   - 소거 후 같은 시그니처

8.8 제약의 우회 종합

// Class<T> 활용 — 런타임 타입 정보 보유
public class TypedContainer<T> {
    private final Class<T> type;
    private final List<T> items = new ArrayList<>();
    
    public TypedContainer(Class<T> type) {
        this.type = type;
    }
    
    public boolean isOfType(Object obj) {
        return type.isInstance(obj);
    }
    
    public T create() throws Exception {
        return type.getDeclaredConstructor().newInstance();
    }
    
    public T[] toArray() {
        @SuppressWarnings("unchecked")
        T[] array = (T[]) Array.newInstance(type, items.size());
        for (int i = 0; i < items.size(); i++) {
            array[i] = items.get(i);
        }
        return array;
    }
}

// 사용
TypedContainer<Shipment> container = new TypedContainer<>(Shipment.class);
boolean isShipment = container.isOfType(someObj);
Shipment newOne = container.create();
Shipment[] arr = container.toArray();

8.9 자기 점검 답변

타입 매개변수의 5가지 제약은?

:
1. static 멤버에 사용 불가 — 인스턴스 vs 클래스 레벨 충돌
2. 배열 생성 불가new T[]
3. instanceof 검사 제한obj instanceof T
4. new 인스턴스 직접 생성 불가new T()
5. catch 절 사용 불가catch (T e)

추가:

  • 같은 시그니처 오버로딩 제약 (타입 소거)
  • 우회: Class, Supplier, List 사용

→ 모두 타입 소거 가 원인.


9️⃣ 면접 + 자기 점검

9.1 면접 단골 질문 매핑

Q핵심 답변
타입 매개변수 vs 타입 인자?정의의 placeholder vs 사용의 실제 타입
T, E, K, V, R 의미?Type, Element, Key, Value, Return
제네릭 클래스 정의?class Name<T> { ... }
제네릭 메서드 정의?public <T> T method(...) { ... }
다중 매개변수 활용?Map<K, V>, Function<T, R>, BiFunction<T, U, R>
bounded type?<T extends Number> — 타입 범위 제한
다중 bound?<T extends Number & Comparable<T>>
재귀적 bound?<T extends Comparable<T>> — Builder, Comparable
static 멤버에 T 못 쓰는 이유?인스턴스 vs 클래스 레벨 충돌
제네릭 배열 못 만드는 이유?런타임 타입 정보 X (타입 소거)
new T() 못 하는 이유?런타임에 T 무엇인지 모름

9.2 자기 점검 체크리스트

기본 이해

  • 타입 매개변수와 타입 인자 구분
  • 관례 이름 (T, E, K, V, R) 알기
  • 제네릭 클래스 정의
  • 제네릭 메서드 정의
  • 자동 타입 추론 이해

다중 매개변수

  • Map<K, V> 사용
  • Function<T, R> 사용
  • BiFunction<T, U, R> 사용
  • 사용자 정의 다중 매개변수
  • 가독성을 위한 제한 (2-3개)

bound

  • 활용
  • 다중 bound 활용
  • 재귀적 bound (Comparable) 이해
  • Builder 패턴의 재귀 bound
  • bound 의 효과 4가지

제약

  • static 멤버 제약
  • 제네릭 배열 제약
  • instanceof 제약
  • new T() 제약
  • 우회 방법 (Class, Supplier)

9.3 추가 심화 질문

Q1: List 와 List<?> 의 차이는?

답:

  • List<T>: 타입 매개변수 (정의 또는 사용)
  • List<?>: 와일드카드 — "어떤 타입의 List 든"
  • 자세한 건 Unit 5.3

Q2: Comparable 와 Comparable<? super T> 차이는?

답:

  • Comparable<T>: T 자신과 비교
  • Comparable<? super T>: T 또는 부모와 비교
  • 부모가 Comparable 이면 자식도 사용 가능
  • Unit 5.5 (PECS) 에서 자세히

Q3: 제네릭 메서드에서 명시적 타입 인자가 필요한 때는?

답:

  • 자동 추론 실패 시
  • 반환 타입만 T 사용 (매개변수에 T 없음)
  • 컴파일러가 의도와 다르게 추론
public static <T> List<T> empty() { ... }

List<String> list = Utils.<String>empty();   // 명시
List<String> list2 = Utils.empty();          // 컨텍스트로 추론

Q4: 제네릭 + Lombok @Builder 결합?

답:

@Builder
public class Pair<A, B> {
    private A first;
    private B second;
}

// Lombok 이 자동 생성
Pair<String, Integer> p = Pair.<String, Integer>builder()
    .first("age")
    .second(25)
    .build();

Q5: 제네릭 메서드 + 다이아몬드?

답:

public static <T> List<T> of(T... values) {
    return new ArrayList<>(Arrays.asList(values));
    //              ↑
    //              다이아몬드 — T 추론
}

List<String> list = Utils.of("a", "b");

🎯 핵심 요약 — 3줄 정리

1. 타입 매개변수의 본질

  • 정의 시점 placeholder (T)
  • 사용 시점 실제 타입 (String)
  • 관례 이름 (T, E, K, V, R)

2. 정의 방법

  • 클래스: class Name<T>
  • 메서드: public <T> T method(...)
  • 다중: Map<K, V>, Function<T, R>

3. bound 와 제약

  • bound: <T extends Number> — 타입 범위 제한
  • 재귀 bound: <T extends Comparable<T>> — 자기 비교
  • 제약: static, 배열, instanceof, new T() — 모두 타입 소거 원인

📚 다음으로...

Unit 5.3 — 와일드카드 ? extends, ? super

이번 Unit에서 타입 매개변수의 기본을 봤다면, 다음은 와일드카드.

  • 와일드카드 ? 의 의미
  • unbounded <?> — "어떤 타입이든"
  • upper bounded <? extends T> — 공변성
  • lower bounded <? super T> — 반공변성
  • 제네릭의 불공변성 (invariance)
  • 와일드카드 vs 타입 매개변수

Phase 5 진행 상황

🚀 Phase 5 — 제네릭과 와일드카드
  ✅ Unit 5.1 제네릭의 등장과 raw type
  ✅ Unit 5.2 타입 매개변수와 타입 인자 ← 여기
  ⏭ Unit 5.3 와일드카드 ? extends, ? super
  ⏭ Unit 5.4 제네릭 메서드 + 제네릭 클래스
  ⏭ Unit 5.5 PECS 원칙 (마스터 깊이)

3주차 누적 진행

✅ Phase 1 — Pass by Value (1.1 ~ 1.3 완주)
✅ Phase 2 — 컬렉션 프레임워크 (2.1 ~ 2.6 완주)
✅ Phase 3 — 해시의 원리 (3.1 ~ 3.4 완주)
✅ Phase 4 — 추상화의 두 도구 (4.1 ~ 4.4 완주)
🚀 Phase 5 — 제네릭과 와일드카드 (2/5 진행)

총: 19/43 Unit 작성 (약 44%)
profile
Software Developer

0개의 댓글