Language/Java

β˜• μžλ°” μ œλ„€λ¦­μ˜ 곡변성 & μ™€μΌλ“œμΉ΄λ“œ μ™„λ²½ 이해

인파_ 2023. 1. 18. 09:08

java-Generics

μžλ°”μ˜ 곡변성 / λ°˜κ³΅λ³€μ„±

μ œλ„€λ¦­μ˜ μ™€μΌλ“œμΉ΄λ“œλ₯Ό 배우기 μ•žμ„œ μ„ μˆ˜ μ§€μ‹μœΌλ‘œ μ•Œκ³  λ„˜μ–΄κ°€μ•Όν•  κ°œλ…μ΄ μžˆλ‹€.

쑰금 λ‚œμ΄λ„ μžˆλŠ” ν”„λ‘œκ·Έλž˜λ° 뢀뢄을 ν•™μŠ΅ ν•˜λ‹€λ³΄λ©΄ ν•œλ²ˆμ―€μ€ λ“€μ–΄λ³Όμˆ˜ μžˆλŠ” κ³΅λ³€μ„±(Covariance) / λ°˜κ³΅λ³€μ„±(Contravariance) ν•©μ³μ„œ 'λ³€μ„±(Variance)' μ΄λΌν•˜λŠ” κ°œλ…μ΄λ‹€.

변성은 νƒ€μž…μ˜ 상속 계측 κ΄€κ³„μ—μ„œ μ„œλ‘œ λ‹€λ₯Έ νƒ€μž… 간에 μ–΄λ–€ 관계가 μžˆλŠ”μ§€λ₯Ό λ‚˜νƒ€νƒœλŠ” μ§€ν‘œμ΄λ‹€. 그리고 곡변성은 μ„œλ‘œ λ‹€λ₯Έ νƒ€μž…κ°„μ— ν•¨κ»˜ λ³€ν• μˆ˜ μžˆλ‹€λŠ” νŠΉμ§•μ„ λ§ν•œλ‹€. 이λ₯Ό 객체 지ν–₯ κ°œλ…μœΌλ‘œ ν‘œν˜„ν•˜μžλ©΄ Liskov μΉ˜ν™˜ 원칙에 ν•΄λ‹Ήλœλ‹€.

java-Generics
곡변성 뜻

예λ₯Όλ“€μ–΄ λ°°μ—΄(Array)κ³Ό 리슀트(List)κ°€ μžˆλ‹€κ³  ν•˜μž. μžλ°”μ—μ„œ 각 λ³€μ„±μ˜ νŠΉμ§•μ€ λ‹€μŒκ³Ό 같이 λœλ‹€.

  • 곡변 : S κ°€ T μ˜ ν•˜μœ„ νƒ€μž…μ΄λ©΄,
    • S[] λŠ” T[] 의 ν•˜μœ„ νƒ€μž…μ΄λ‹€.
    • List<S> λŠ” List<T> 의 ν•˜μœ„ νƒ€μž…μ΄λ‹€.
  • λ°˜κ³΅λ³€ : S κ°€ T의 ν•˜μœ„ νƒ€μž…μ΄λ©΄,
    • T[] λŠ” S[] 의 ν•˜μœ„ νƒ€μž…μ΄λ‹€. (κ³΅λ³€μ˜ λ°˜λŒ€) 
    • List<T> λŠ” List<S> 의 ν•˜μœ„ νƒ€μž…μ΄λ‹€. (κ³΅λ³€μ˜ λ°˜λŒ€)
  • 무곡변 / λΆˆκ³΅λ³€ : S μ™€ T λŠ” μ„œλ‘œ 관계가 μ—†λ‹€.
    • List<S> 와 List<T> λŠ” μ„œλ‘œ λ‹€λ₯Έ νƒ€μž…μ΄λ‹€.

μ–Έλœ» 보면 λ‹€ν˜•μ„±μ˜ μ—…μΊμŠ€νŒ…, λ‹€μš΄μΊμŠ€νŒ…μ„ λ§ν•˜λŠ” 것과 λΉ„μŠ·ν•΄λ³΄μΈλ‹€. μ§€κΈˆ λ…μžλΆ„λ“€μ΄ 느끼고 μžˆλŠ” 것이 λ§žλ‹€. 

이λ₯Ό μžλ°” μ½”λ“œλ‘œ λ‚˜νƒ€λ‚΄λ³΄μžλ©΄ λ‹€μŒκ³Ό κ°™λ‹€.

// 곡변성
Object[] Covariance = new Integer[10];

// λ°˜κ³΅λ³€μ„±
Integer[] Contravariance = (Integer[]) Covariance;
// 곡변성
ArrayList<Object> Covariance = new ArrayList<Integer>();

// λ°˜κ³΅λ³€μ„±
ArrayList<Integer> Contravariance = new ArrayList<Object>();

java-Generics

κ·ΈλŸ¬λ‚˜ λ°°μ—΄κ³Ό 달리 μ œλ„€λ¦­ 예제 μ½”λ“œλŠ” μžλ°”μ—μ„  λŒμ•„κ°€μ§€ μ•ŠλŠ”λ‹€. μ™œλƒν•˜λ©΄ μžλ°”λŠ” 일반적으둜 μ œλ„€λ¦­ νƒ€μž…μ— λŒ€ν•΄μ„œ 곡변성 / λ°˜κ³΅λ³€μ„±μ„ μ§€μ›ν•˜μ§€ μ•ŠκΈ° λ•Œλ¬Έμ΄λ‹€. 즉, μžλ°”μ˜ μ œλ„€λ¦­μ€ λ¬΄κ³΅λ³€μ˜ μ„±μ§ˆμ„ μ§€λ‹Œλ‹€λΌκ³  μ •μ˜ 내릴 수 μžˆλ‹€.


μ œλ„€λ¦­μ€ 곡변성이 μ—†λ‹€

 

객체 νƒ€μž…μ€ μƒν•˜ 관계가 μžˆλ‹€

이뢀뢄은 μš°λ¦¬κ°€ λ„ˆλ¬΄λ‚˜λ„ 잘 μ•Œκ³  μžˆλŠ” λ‹€ν˜•μ„±(polymorphism)의 μ„±μ§ˆμ˜ μ˜ˆμ΄λ‹€.

Object νƒ€μž…μœΌλ‘œ μ„ μ–Έν•œ parent λ³€μˆ˜μ™€ Integer νƒ€μž…μœΌλ‘œ μ„ μ–Έν•œ child λ³€μˆ˜κ°€ μžˆλŠ”λ° 객체 지ν–₯ ν”„λ‘œκ·Έλž˜λ°μ—μ„  이듀 끼리 μ„œλ‘œ 간에 μΊμŠ€νŒ…μ΄ κ°€λŠ₯ν•˜λ‹€.

java-generic-Covariance
Integer - Number - Object 관계

Object parent = new Object();
Integer child = new Integer(1);

parent = child; // λ‹€ν˜•μ„± (μ—…μΊμŠ€νŒ…)
Object parent = new Integer(1);
Integer child;

child = (Integer) parent; // λ‹€ν˜•μ„± (λ‹€μš΄μΊμŠ€νŒ…)

 

일반 ν΄λž˜μŠ€κ°€ μ•„λ‹Œ μ œλ„€λ¦­ ν΄λž˜μŠ€μ—¬λ„ λ˜‘κ°™μ΄ λ‹€ν˜•μ„±μ΄ μ μš©λ˜λŠ” 건 λ§ˆμ°¬κ°€μ§€ 이닀.

λŒ€ν‘œμ μœΌλ‘œ μ»¬λ ‰μ…˜ ν”„λ ˆμž„μ›Œν¬μ˜ Collection κ³Ό 그의 ν•˜μœ„μΈ ArrayList λŠ” μ„œλ‘œ 쑰상-μžμ† 상속 관계에 있기 λ•Œλ¬Έμ— μΊμŠ€νŒ…μ΄ κ°€λŠ₯ν•˜λ‹€.

java-generic-Covariance
μ»¬λ ‰μ…˜ ν”„λ ˆμž„μ›Œν¬ 상속 관계

Collection<Integer> parent = new ArrayList<>();
ArrayList<Integer> child = new ArrayList<>();

parent = child; // λ‹€ν˜•μ„± (μ—…μΊμŠ€νŒ…)

 

μ œλ„€λ¦­ νƒ€μž…μ€ μƒν•˜κ΄€κ³„κ°€ μ—†λ‹€

λ°˜λ©΄μ— μ œλ„€λ¦­μ˜ νƒ€μž… νŒŒλΌλ―Έν„°(κΊΎμ‡  κ΄„ν˜Έ) λΌλ¦¬λŠ” νƒ€μž…μ΄ 아무리 상속 관계에 놓인닀 ν•œλ“€ μΊμŠ€νŒ…μ΄ λΆˆκ°€λŠ₯ν•˜λ‹€. μ™œλƒν•˜λ©΄ μ œλ„€λ¦­μ€ 무곡변 이기 λ•Œλ¬Έμ΄λ‹€. μ œλ„€λ¦­μ€ μ „달받은 λ”± κ·Έ νƒ€μž…μœΌλ‘œλ§Œ μ„œλ‘œ μΊμŠ€νŒ…μ΄ κ°€λŠ₯ν•˜λ‹€.

java-generic-Covariance

ArrayList<Object> parent = new ArrayList<>();
ArrayList<Integer> child = new ArrayList<>();

parent = child; // ! μ—…μΊμŠ€νŒ… λΆˆκ°€λŠ₯
child = parent; // ! λ‹€μš΄μΊμŠ€νŒ… λΆˆκ°€λŠ₯

즉, κΊΎμ‡  κ΄„ν˜Έ 뢀뢄을 μ œμ™Έν•œ μ›μ‹œ νƒ€μž…(Raw Type) 뢀뢄은 곡변성이 μ μš©λ˜μ§€λ§Œ, κΊΎμ‡  κ΄„ν˜Έ μ•ˆμ˜ μ‹€μ œ νƒ€μž… λ§€κ°œλ³€μˆ˜μ— λŒ€ν•΄μ„œλŠ” 적용이 λ˜μ§€ μ•ŠλŠ”λ‹€κ³  정리할 수 μžˆλŠ” 것이닀.


곡변성이 μ—†μœΌλ©΄ λ‚˜νƒ€λ‚˜λŠ” 문제점

이 νŠΉμ§•μ΄ μ™œ λ¬Έμ œμ‹œκ°€ λ˜λƒλ©΄μ€ λ§€κ°œλ³€μˆ˜λ‘œ μ œλ„€λ¦­μ„ μ‚¬μš©ν• λ•Œ, μ™ΈλΆ€μ—μ„œ λŒ€μž…λ˜λŠ” 인자의 μΊμŠ€νŒ… 문제둜 인해 μ• λ‘œμ‚¬ν•­μ΄ λ°œμƒν•˜κΈ° λ•Œλ¬Έμ΄λ‹€.

예λ₯Ό λ“€μžλ©΄ 리슀트λ₯Ό 인자둜 λ°›μ•„ μˆœνšŒν•΄μ„œ 좜λ ₯ν•΄μ£ΌλŠ” print λ©”μ„œλ“œκ°€ μžˆλ‹€κ³  ν•˜μž. 배열을 μ΄μš©ν•œ μ•„λž˜ μ½”λ“œλŠ” λ¬Έμ œκ°€ μ—†λ‹€.

public static void print(Object[] arr) {
    for (Object e : arr) {
        System.out.println(e);
    }
}

public static void main(String[] args) {
    Integer[] integers = {1, 2, 3};
    print(integers); // [1, 2, 3]
}

μ΄λ²ˆμ—” 배열이 μ•„λ‹Œ 리슀트의 μ œλ„€λ¦­ 객체둜 λ„˜κ²¨λ³΄μž. 그러면 λ©”μ†Œλ“œ 호좜 λΆ€λΆ„μ—μ„œ 컴파일 μ—λŸ¬κ°€ λ°œμƒν•œλ‹€.

public static void print(List<Object> arr) {
    for (Object e : arr) {
        System.out.println(e);
    }
}

public static void main(String[] args) {
    List<Integer> integers = Arrays.asList(1, 2, 3);
    print(integers); // ! Error
}

java-generic-Covariance

λ°°μ—΄ 같은 경우 print λ©”μ„œλ“œμ˜ λ§€κ°œλ³€μˆ˜λ‘œ μ•„κ·œλ¨ΌνŠΈκ°€ λ„˜μ–΄κ°ˆλ•Œ Integer[] λ°°μ—΄ νƒ€μž…μ΄ Object[] λ°°μ—΄ νƒ€μž…μœΌλ‘œ μžμ—°μŠ€λŸ½κ²Œ μ—…μΊμŠ€νŒ…μ΄ μ μš©λ˜μ–΄ λ¬Έμ œκ°€ μ—†μ§€λ§Œ, 리슀트 μ œλ„€λ¦­ 같은 경우 νƒ€μž… νŒŒλΌλ―Έν„°κ°€ μ˜€λ‘œμ§€ λ˜‘κ°™μ€ νƒ€μž…λ§Œ λ°›κΈ° λ•Œλ¬Έμ— μΊμŠ€νŒ…μ΄ λ˜μ§€ μ•Šμ•„ 그런 것이닀.

κ·Έλ ‡λ‹€λ©΄ μ™ΈλΆ€λ‘œλΆ€ν„° 값을 λ°›λŠ” λ§€κ°œλ³€μˆ˜μ˜ μ œλ„€λ¦­ νƒ€μž… νŒŒλΌλ―Έν„°λ₯Ό Integer둜 κ³ μ •λœ νƒ€μž…μœΌλ‘œ μž‘μ„±ν•΄μ£Όμ–΄μ•Ό ν•˜λŠ”λ°, ν”„λ‘œκ·Έλž¨μ˜ μ‹€ν–‰λΆ€μ—μ„œ λ°˜λ“œμ‹œ Integer νƒ€μž…λ§Œ λ“€μ–΄μ˜¨λ‹€λŠ” 보μž₯도 μ—†μœΌλ©°, 만일 Double ν˜•μ΄λ‚˜ μ•„λ‹ˆλ©΄ μƒμœ„ νƒ€μž…μΈ Number ν˜•κ³Ό 같은 λ‹€λ₯Έ νƒ€μž…μ˜ 값도 λ°›κ³  싢은 경우, λ©”μ„œλ“œλ₯Ό μ˜€λ²„λ‘œλ”©ν•˜μ—¬ μ¦λΉ„ν•˜κ²Œ μ½”λ”©ν•΄μ•Ό ν•œλ‹€.

public static void print(List<Integer> arr) {
}

public static void print(List<Double> arr) {
}

public static void print(List<Number> arr) {
}

...

그럼 μ œλ„€λ¦­μ€ μžλ°”μ˜ νŠΉμ§•μ΄λΌκ³  ν• μˆ˜ μžˆλŠ” 객체 지ν–₯을 μ „ν˜€ μ΄μš©ν•˜μ§€ λͺ»ν•˜λŠ” 것이 λ˜λŠ”λ°, κ²°κ΅­ μœ„μ™€ 같이 λΉ„νš¨μœ¨μ μœΌλ‘œ μ½”λ”©ν•΄μ•Ό ν•˜λŠ”κ²ƒμΌκΉŒ? λ°”λ‘œ 이λ₯Ό ν•΄κ²°ν•˜κΈ° μœ„ν•΄ λ‚˜μ˜¨ κΈ°λŠ₯이 μ œλ„€λ¦­ μ™€μΌλ“œμΉ΄λ“œ μΈ 것이닀.


μ œλ„€λ¦­ μ™€μΌλ“œμΉ΄λ“œ

μžλ°” μ œλ„€λ¦­μ„ μ΄μš©ν•΄ ν”„λ‘œκ·Έλž˜λ° ν• λ•Œ κ°„ν˜Ή 클래슀 μ •μ˜λ¬Έμ„ 보닀보면 κΊΎμ‡  κ΄„ν˜Έ ? λ¬ΌμŒν‘œ κΈ°ν˜Έκ°€ μžˆλŠ” 것을 ν•œλ²ˆμ―€ λ³Έ 적이 μžˆμ„ 것이닀. 이 λ¬ΌμŒν‘œκ°€ μ™€μΌλ“œμΉ΄λ“œμ΄λ©°, λ¬ΌμŒν‘œμ˜ 의미 λ‹΅κ²Œ μ–΄λ–€ νƒ€μž…μ΄λ“  될 수 μžˆλ‹€λŠ” λœ»μ„ μ§€λ‹ˆκ³  μžˆλ‹€.

java-generic-wildcard

ν•˜μ§€λ§Œ λ‹¨μˆœνžˆ <?> 둜 μ‚¬μš©ν•˜λ©΄ Object νƒ€μž…κ³Ό 닀름이 μ—†μ–΄μ§€λ―€λ‘œ 보톡 μ œλ„€λ¦­ νƒ€μž… ν•œμ • μ—°μ‚°μžμ™€ ν•¨κ»˜ 쓰인닀.

μ™€μΌλ“œ μΉ΄λ“œμ˜ νƒ€μž… λ²”μœ„λ₯Ό μ œν•œν•˜λŠ” ν‚€μ›Œλ“œλŠ” extends μ™€ λ”λΆˆμ–΄ superκ°€ μžˆλ‹€. 이 extends μ™€ super ν‚€μ›Œλ“œλŠ” 클래슀 상속 κ΄€κ³„μ—μ„œμ˜ νƒ€μž…μ„ ν•˜μœ„ νƒ€μž…μœΌλ‘œλ§Œ μ œν•œν• μ§€, μƒμœ„ νƒ€μž…μœΌλ‘œλ§Œ μ œν•œν• μ§€μ— 따라 μ“°μž„μƒˆκ°€ λ‹€λ₯΄κ²Œ λœλ‹€.

μ™€μΌλ“œμΉ΄λ“œ 넀이밍 μ„€λͺ…
<?> Unbounded wildcards
λΉ„ν•œμ •μ  μ™€μΌλ“œ μΉ΄λ“œ
μ œν•œ μ—†μŒ (λͺ¨λ“  νƒ€μž…μ΄ κ°€λŠ₯)
<? extends U> Upper Bounded Wildcards
μƒν•œ 경계 μ™€μΌλ“œμΉ΄λ“œ
μƒμœ„ 클래슀 μ œν•œ (U와 κ·Έ μžμ†λ“€λ§Œ κ°€λŠ₯)
μƒν•œμ΄ U라 μƒν•œ 경계라고 ν•œλ‹€.
<? super U> Lower Bounded Wildcards
ν•˜ν•œ 경계 μ™€μΌλ“œμΉ΄λ“œ
ν•˜μœ„ 클래슀 μ œν•œ (U와 κ·Έ μ‘°μƒλ“€λ§Œ κ°€λŠ₯)
ν•˜ν•œμ΄ U라 ν•˜ν•œ 경계라고 ν•œλ‹€.

μ™€μΌλ“œμΉ΄λ“œμ˜ 곡변성 / λ°˜κ³΅λ³€μ„±

λ‹€μŒμ€ μ‹€μ œ ArrayList의 κΈ°λŠ₯쀑 일뢀 λ©”μ„œλ“œλ§Œ μΆ”λ €μ„œ λ³„λ„λ‘œ μž¬κ΅¬μ„±ν•œ MyArrayList μ œλ„€λ¦­ 클래슀 예제 이닀.

MyArrayList μƒμ„±μž(Constructor)λ₯Ό 보면, μ»¬λ ‰μ…˜(Collection)을 λ°›μ•„ μ»¬λ ‰μ…˜μ„ μˆœνšŒν•˜μ—¬ μ»¬λ ‰μ…˜ 내에 λ“€μ–΄μžˆλŠ” λͺ¨λ“  μš”μ†Œλ₯Ό λ‚΄λΆ€ λ°°μ—΄(Object[])에 λ„£μ–΄ μ œλ„€λ¦­ 객체λ₯Ό μƒμ‚°ν•˜λŠ” 역할을 ν•œλ‹€.

MyArrayList의 clone λ©”μ„œλ“œλ₯Ό 보면, 빈 μ»¬λ ‰μ…˜(Collection)을 λ°›μ•„ λ‚΄λΆ€ 배열을 μˆœνšŒν•˜μ—¬ λ°°μ—΄ 내에 λ“€μ–΄μžˆλŠ” λͺ¨λ“  μš”μ†Œλ₯Ό μ»¬λ ‰μ…˜μ— λ„£μ–΄μ£ΌλŠ”, λ§€κ°œλ³€μˆ˜λ‘œ 받은 μ œλ„€λ¦­ 객체λ₯Ό μ†ŒλΉ„ν•˜λŠ” 역할을 ν•œλ‹€.

class MyArrayList<T> {
    Object[] element = new Object[5];
    int index = 0;

    // μ™ΈλΆ€λ‘œλΆ€ν„° 리슀트λ₯Ό 받아와 λ§€κ°œλ³€μˆ˜μ˜ λͺ¨λ“  μš”μ†Œλ₯Ό λ‚΄λΆ€ 배열에 μΆ”κ°€ν•˜μ—¬ μΈμŠ€ν„΄μŠ€ν™” ν•˜λŠ” μƒμ„±μž
    public MyArrayList(Collection<T> in) {
        for (T elem : in) {
            element[index++] = elem;
        }
    }

    // μ™ΈλΆ€λ‘œλΆ€ν„° 리슀트λ₯Ό 받아와 λ‚΄λΆ€ λ°°μ—΄μ˜ μš”μ†Œλ₯Ό λͺ¨λ‘ λ§€κ°œλ³€μˆ˜μ— μΆ”κ°€ν•΄μ£ΌλŠ” λ©”μ„œλ“œ
    public void clone(Collection<T> out) {
        for (Object elem : element) {
            out.add((T) elem);
        }
    }

    @Override
    public String toString() {
        return Arrays.toString(element); // λ°°μ—΄ μš”μ†Œλ“€ 좜λ ₯
    }
}

눈치λ₯Ό μ±˜λ“―μ΄ 이 μ½”λ“œλŠ” μœ μ—°ν•˜μ§€ μ•Šλ‹€.

MyArrayList에 μ •μˆ˜λ„ λ°›κ³  μ‹€μˆ˜λ„ λ°›κ²Œ ν•˜κΈ°μœ„ν•΄ μ œλ„€λ¦­ νƒ€μž… λ§€κ°œλ³€μˆ˜λ‘œ Number둜 지정해 μ£Όμ—ˆλ‹€. 그리고 Integer λ§€κ°œλ³€μˆ˜ν™” νƒ€μž…μ˜ Collection 객체λ₯Ό MyArrayList μƒμ„±μžλ‘œ λ„£μ–΄μ£Όμ—ˆλ‹€.

public static void main(String[] args) {
    // MyArrayList의 μ œλ„€λ¦­ T νƒ€μž…μ€ Number
    MyArrayList<Number> list;

    // MyArrayList μƒμ„±ν•˜κΈ°
    Collection<Integer> col = Arrays.asList(1, 2, 3, 4, 5);
    list = new MyArrayList<>(col); // ! ERROR
    
    // MyArrayList 좜λ ₯
    System.out.println(list);
}

java-generic-wildcard

ν•˜μ§€λ§Œ κ²°κ³ΌλŠ” μ‹œλ»˜κ±΄ 컴파일 μ—λŸ¬μ΄λ‹€. μ™œλƒν•˜λ©΄ λ§€κ°œλ³€μˆ˜λŠ” Collection<Number> νƒ€μž…μœΌλ‘œ λ°›λŠ”λ° Collection<Integer> 객체λ₯Ό μ „λ‹¬ν•΄μ£Όμ—ˆκΈ° λ•Œλ¬Έμ΄λ‹€. IntegerλŠ” Numberλ₯Ό μƒμ†ν•˜μ—¬ λ‘˜μ€ λΆ€λͺ¨-μžμ‹ κ΄€κ³„μ΄μ§€λ§Œ μ œλ„€λ¦­μ˜ νƒ€μž… νŒŒλΌλ―Έν„°λŠ” 기본적으둜 곡변성이 μ—†μ–΄ μ„±λ¦½λ˜μ§€ μ•Šκ²Œ λ˜λŠ” 것이닀.

java-generic-wildcard

κ·Έλž˜μ„œ ν•˜λŠ” 수 없이 col 객체의 μ œλ„€λ¦­ νƒ€μž…μ„ λ˜‘κ°™μ΄ Collection<Number>둜 맞좰주고 λ‚˜μ„œμ•Ό MyArrayList μΈμŠ€ν„΄μŠ€λ₯Ό 생산할 수 있게 λ˜μ—ˆλ‹€.

μ΄λ²ˆμ—λŠ” MyArrayList의 clone λ©”μ„œλ“œμ— 빈 LinkedListλ₯Ό 인자둜 μ€˜μ„œ MyArrayList에 λ“€μ–΄μžˆλŠ” μ›μ†Œλ“€μ„ λ³΅μ‚¬ν•˜μ—¬ λ„£κΈ°μœ„ν•΄ λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•˜μ˜€λ‹€. μ΄λ•Œ String νƒ€μž…μ΄λ“  Number νƒ€μž…μ΄λ“  λͺ¨λ“  νƒ€μž…μ˜ 데이터λ₯Ό 받을 수 있게 ν•˜κΈ° μœ„ν•΄ Object νƒ€μž… νŒŒλΌλ―Έν„°λ‘œ μ„€μ •ν•˜μ˜€λ‹€.

public static void main(String[] args) {
    // MyArrayList의 μ œλ„€λ¦­ T νƒ€μž…μ€ Number
    MyArrayList<Number> list;

    // MyArrayList μƒμ„±ν•˜κΈ°
    Collection<Number> col = Arrays.asList(1, 2, 3, 4, 5);
    list = new MyArrayList<>(col);

    // LinkedList 에 MyArrayList μš”μ†Œλ“€ λ³΅μ‚¬ν•˜κΈ°
    List<Object> temp = new LinkedList<>();
    temp = list.clone(temp); // ! ERROR

	// LinkedList 좜λ ₯
    System.out.println(temp);
}

java-generic-wildcard

μ—­μ‹œλ‚˜ κ²°κ³ΌλŠ” 꽝이닀. μ œλ„€λ¦­μ€ λ°˜κ³΅λ³€ μ—­μ‹œ μ„±λ¦½λ˜μ§€ μ•ŠκΈ° λ•Œλ¬Έμ— Collection<Number>에 Collection<Object>κ°€ λ“€μ–΄κ°€λŠ” ν–‰μœ„λŠ” μ„±λ¦½λ˜μ§€ μ•ŠλŠ”λ‹€.

이처럼 μžλ°”μ˜ μ œλ„€λ¦­μ€ 기본적으둜 곡변, λ°˜κ³΅λ³€μ„ μ§€μ›ν•˜μ§€ μ•Šμ§€λ§Œ, <? extends T> , <? super T> μ™€μΌλ“œμΉ΄λ“œλ₯Ό μ΄μš©ν•˜λ©΄ 컴파일러 νŠΈλ¦­μ„ 톡해 곡변, λ°˜κ³΅λ³€μ΄ μ μš©λ˜λ„λ‘ μ„€μ • ν•  수 μžˆλ‹€. λ‘˜μ„ μ •λ¦¬ν•˜μžλ©΄ λ‹€μŒκ³Ό κ°™λ‹€.

  • μƒν•œ 경계 μ™€μΌλ“œμΉ΄λ“œ <? extends T> : 곡변성 적용
  • ν•˜ν•œ 경계 μ™€μΌλ“œμΉ΄λ“œ <? super T> : λ°˜κ³΅λ³€μ„± 적용

 

μƒν•œ 경계 μ™€μΌλ“œμΉ΄λ“œ (곡변)

MyArrayListλ₯Ό μ„€κ³„ν•œ 개발자의 μ˜λ„λŠ” Collection<Integer> 와 Collection<Double> 객체λ₯Ό μƒμ„±μžμ˜ μΈμˆ˜λ‘œ λͺ¨λ‘ λ°›μ•„ 배열에 λ„£κ³  싢은 것이닀. 이λ₯Ό μœ„ν•΄ μ œλ„€λ¦­μ— μƒν•œ 경계 μ™€μΌλ“œμΉ΄λ“œ(Upper Bounded Wildcards)λ₯Ό μ μš©μ‹œν‚¨λ‹€.

java-generic-wildcard

class MyArrayList<T> {
    Object[] element = new Object[5];
    int index = 0;

    // μ™ΈλΆ€λ‘œλΆ€ν„° 리슀트λ₯Ό 받아와 λ§€κ°œλ³€μˆ˜μ˜ λͺ¨λ“  μš”μ†Œλ₯Ό λ‚΄λΆ€ 배열에 μΆ”κ°€ν•˜μ—¬ μΈμŠ€ν„΄μŠ€ν™” ν•˜λŠ” μƒμ„±μž
    public MyArrayList(Collection<? extends T> in) {
        for(T elem : in) {
            element[index++] = elem;
        }
    }
    
    // ...
}
public static void main(String[] args) {
    // MyArrayList의 μ œλ„€λ¦­ T νƒ€μž…μ€ Number
    MyArrayList<Number> list;

    // MyArrayList μƒμ„±ν•˜κΈ°
    Collection<Integer> col = Arrays.asList(1, 2, 3, 4, 5);
    list = new MyArrayList<>(col);
    
    // MyArrayList 좜λ ₯
    System.out.println(list); // [1, 2, 3, 4, 5]
}

그러면 곡변 μ„±μ§ˆμ΄ μ μš©λ˜μ–΄ 컴파일 μ—λŸ¬ 없이 μ •μƒμ μœΌλ‘œ MyArrayList에 μš”μ†Œκ°€ λ“€μ–΄κ°€ μƒμ‚°λ˜μ—ˆμŒμ„ 확인 ν•  수 μžˆλ‹€.

이λ₯Ό 클래슀 μƒμ†κ΄€κ³„λ‘œ ν‘œν˜„ν•˜μžλ©΄ λ‹€μŒκ³Ό 같이 그릴수 μžˆλ‹€.

java-generic-wildcard

즉, Integer κ°€ Object의 ν•˜μœ„ νƒ€μž…μΌ 경우, C<Integer>λŠ” C<? extends Object>의 ν•˜μœ„ νƒ€μž…μ΄ λ˜λŠ” 것이닀.

ArrayList<? extends Object> parent = new ArrayList<>();
ArrayList<? extends Integer> child = new ArrayList<>();

parent = child; // 곡변성 (μ œλ„€λ¦­ νƒ€μž… μ—…μΊμŠ€νŒ…)

 

ν•˜ν•œ 경계 μ™€μΌλ“œμΉ΄λ“œ (λ°˜κ³΅λ³€)

MyArrayList의 clone λ©”μ„œλ“œλ₯Ό μ„€κ³„ν•œ 개발자의 μ˜λ„λŠ” MyArrayList의 μ œλ„€λ¦­ νƒ€μž… νŒŒλΌλ―Έν„°κ°€ 무엇이든 인자둜 받은 μ»¬λ ‰μ…˜ λ§€κ°œλ³€μˆ˜μ— μš”μ†Œλ“€μ„ λͺ¨λ‘ 집어넣고 싢은 것이닀. 이λ₯Ό μœ„ν•΄ μ œλ„€λ¦­μ— ν•˜ν•œ 경계 μ™€μΌλ“œμΉ΄λ“œ(Lower Bounded Wildcards)λ₯Ό μ μš©μ‹œν‚¨λ‹€.

java-generic-wildcard

class MyArrayList<T> {
    Object[] element = new Object[5];
    int index = 0;

    // μ™ΈλΆ€λ‘œλΆ€ν„° 리슀트λ₯Ό 받아와 λ§€κ°œλ³€μˆ˜μ˜ λͺ¨λ“  μš”μ†Œλ₯Ό λ‚΄λΆ€ 배열에 μΆ”κ°€ν•˜μ—¬ μΈμŠ€ν„΄μŠ€ν™” ν•˜λŠ” μƒμ„±μž
    public MyArrayList(Collection<? extends T> in) {
        for (T elem : in) {
            element[index++] = elem;
        }
    }

    // μ™ΈλΆ€λ‘œλΆ€ν„° 리슀트λ₯Ό 받아와 λ‚΄λΆ€ λ°°μ—΄μ˜ μš”μ†Œλ₯Ό λͺ¨λ‘ λ§€κ°œλ³€μˆ˜μ— μΆ”κ°€ν•΄μ£ΌλŠ” λ©”μ„œλ“œ
    public void clone(Collection<? super T> out) {
        for (Object elem : element) {
            out.add((T) elem);
        }
    }

    @Override
    public String toString() {
        return Arrays.toString(element); // λ°°μ—΄ μš”μ†Œλ“€ 좜λ ₯
    }
}
public static void main(String[] args) {
    // MyArrayList의 μ œλ„€λ¦­ T νƒ€μž…μ€ Number
    MyArrayList<Number> list = new MyArrayList<>(Arrays.asList(1, 2, 3, 4, 5));

    // LinkedList 에 MyArrayList μš”μ†Œλ“€ λ³΅μ‚¬ν•˜κΈ°
    List<Object> temp = new LinkedList<>();
    temp = list.clone(temp);

	// LinkedList 좜λ ₯
    System.out.println(temp); // [1, 2, 3, 4, 5]
}

그러면 λ°˜κ³΅λ³€ μ„±μ§ˆμ΄ μ μš©λ˜μ–΄ 컴파일 μ—λŸ¬ 없이 μ •μƒμ μœΌλ‘œ MyArrayList에 μš”μ†Œκ°€ λ“€μ–΄κ°€ μ†ŒλΉ„λ˜μ—ˆμŒμ„ 확인 ν•  수 μžˆλ‹€.

이λ₯Ό 클래슀 μƒμ†κ΄€κ³„λ‘œ ν‘œν˜„ν•˜μžλ©΄ λ‹€μŒκ³Ό 같이 그릴수 μžˆλ‹€.

java-generic-wildcard

즉, Objectκ°€ Integer의 μƒμœ„ νƒ€μž…μΌ 경우, C<Object>λŠ” C<? super Integer>의 ν•˜μœ„ νƒ€μž…μ΄ λ˜λŠ” 것이닀.

ArrayList<? super Object> parent = new ArrayList<>();
ArrayList<? super Integer> child = new ArrayList<>();

child = parent; // λ°˜κ³΅λ³€μ„± (μ œλ„€λ¦­ λ‹€μš΄μΊμŠ€νŒ…)

μ–΄λ””λ‹€ 써야할지도 μ• λ§€ν•œ 이런 κ±°κΎΈλ‘œμ˜ 상속 κ΄€κ³„λŠ” μœ„μ˜ 사둀와 같이 μΈμŠ€ν„΄μŠ€ν™” 된 클래슀의 μ œλ„€λ¦­λ³΄λ‹€ μƒμœ„ νƒ€μž…μ˜ 데이터λ₯Ό μ μž¬ν•΄μ•Ό ν• λ•Œ λ°˜κ³΅λ³€μ„ μ‚¬μš©ν•œλ‹€κ³  μ΄ν•΄ν•˜λ©΄ λœλ‹€. μ΄λŸ¬ν•œ μ œλ„€λ¦­μ˜ 변성을 μ μ ˆν•˜κ²Œ μ‚¬μš©ν•˜λ©΄ μœ μ—°ν•œ μ½”λ“œλ₯Ό μž‘μ„±ν•  수 있게 λœλ‹€.

μžλ°”μ˜ μ œλ„€λ¦­μ€ 기본적으둜 변성이 μ—†μ§€λ§Œ, ν•œμ •μ  μ™€μΌλ“œμΉ΄λ“œ νƒ€μž…μ„ 톡해 νƒ€μž…μ˜ 곡변성 λ˜λŠ” λ°˜κ³΅λ³€μ„±μ„ 지정할 수 μžˆλ‹€. μ΄λ ‡κ²Œ νƒ€μž… λ§€κ°œλ³€μˆ˜ 지점에 변성을 μ •ν•˜λŠ” μžλ°”μ˜ 방식을 μ‚¬μš©μ§€μ  λ³€μ„±(use-site variance)이라 ν•œλ‹€.
μžλ°”μ™€ 같이 JVM을 μ΄μš©ν•˜λŠ” μ½”ν‹€λ¦°μ—μ„œλ„ μ‚¬μš©μž 지점 변성을 μ œκ³΅ν•œλ‹€.

 

λΉ„ν•œμ •μ  μ™€μΌλ“œ μΉ΄λ“œ

μ œλ„€λ¦­μ— extends, super λ²”μœ„ 따지지 μ•Šκ³  μ‹¬ν”Œν•˜κ²Œ <?> λΉ„ν•œμ •μ  μ™€μΌλ“œ μΉ΄λ“œλ‘œλ§Œ ꡬ성해주면 μ–΄λ–»κ²Œ 될까?

μ–΄λ– ν•œ νƒ€μž…λ„ 올 수 μžˆλ‹€λŠ” 점은 μΉ˜νŠΈν‚€μ΄μ§€λ§Œ, λ™μ‹œμ— μ–΄λ– ν•œ νƒ€μž…λ„ 올 수 μžˆλŠ” 문제 λ•Œλ¬Έμ— λ§€κ°œλ³€μˆ˜λ₯Ό κΊΌλ‚΄κ±°λ‚˜ μ €μž₯ν•˜λŠ” λ‘œμ§μ€ 논리적 μ—λŸ¬κ°€ λ°œμƒν•˜κ²Œ λœλ‹€.

public MyArrayList(Collection<?> in) {
    for (T elem : in) {
        element[index++] = elem;
    }
}

public void clone(Collection<?> out) {
    for (Object elem : element) {
        out.add((T) elem);
    }
}

java-generic-wildcard

ν•˜μ§€λ§Œ extends, super λ₯Ό 톡해 μ™€μΌλ“œμΉ΄λ“œμ˜ 경계λ₯Ό μ •ν•΄μ£Όλ©΄, νƒ€μž…μ˜ λ²”μœ„ λ‚΄μ—μ„œ 좔둠을 ν•˜κΈ° λ•Œλ¬Έμ— κ²½κ³ λŠ” λ°œμƒν• μ§€λΌλ„ 였λ₯˜λŠ” λ‚˜μ§€μ•Šκ²Œ λ˜λŠ” 것이닀.


μ™€μΌλ“œμΉ΄λ“œ κΊΌλ‚΄κΈ° / λ„£κΈ° μ œμ•½

 

μ™€μΌλ“œμΉ΄λ“œ 경계 λ²”μœ„

λ‹€μŒκ³Ό 같이 클래슀 νƒ€μž…μ˜ 상속 관계가 μžˆλ‹€κ³  κ°€μ •ν•˜μž. 이λ₯Ό μ˜ˆμ‹œλ‘œ νƒ€μž… μ œν•œ λ²”μœ„μ— λŒ€ν•΄ μ•Œμ•„ 보겠닀.

generic-wildcard

// μ œλ„€λ¦­ νƒ€μž… 클래슀
class Box<T> {
	...
}
// νƒ€μž… 계측 관계
class Food {
}

class Fruit extends Food {
}

class Vegetable extends Food {
}

class Apple extends Fruit {
}

class Banana extends Fruit {
}

class Carrot extends Vegetable {
}

 

μƒν•œ 경계 <? extends U>

  • νƒ€μž… λ§€κ°œλ³€μˆ˜μ˜ λ²”μœ„λŠ” U ν΄λž˜μŠ€μ΄κ±°λ‚˜, Uλ₯Ό μƒμ†ν•œ ν•˜μœ„ 클래슀 (U와 U의 μžμ† νƒ€μž…λ§Œ κ°€λŠ₯)
  • μƒν•œμ˜ 뜻 : νƒ€μž…μ˜ 졜고 ν•œλ„λŠ” U λΌλŠ” 의미. (μ΅œλŒ€ U μ΄ν•˜)

generic-wildcard
<? extends Fruit>

Box<? extends Fruit> box1 = new Box<Fruit>();
Box<? extends Fruit> box2 = new Box<Apple>();
Box<? extends Fruit> box3 = new Box<Banana>();

 

ν•˜ν•œ 경계 <? super U>

  • νƒ€μž… λ§€κ°œλ³€μˆ˜μ˜ λ²”μœ„λŠ” U ν΄λž˜μŠ€μ΄κ±°λ‚˜, Uκ°€ μƒμ†ν•œ μƒμœ„ 클래슀 (U와 U의 쑰상 νƒ€μž…λ§Œ κ°€λŠ₯)
  • ν•˜ν•œμ˜ 뜻 : νƒ€μž…μ˜ μ΅œμ € ν•œλ„λŠ” U λΌλŠ” 의미. (μ΅œμ†Œ U 이상)

generic-wildcard
<? super Fruit>

Box<? super Fruit> box1 = new Box<Fruit>();
Box<? super Fruit> box2 = new Box<Food>();
Box<? super Fruit> box3 = new Box<Object>();

 

비경계 <?>

  • νƒ€μž… λ§€κ°œλ³€μˆ˜μ˜ λ²”μœ„λŠ” μ œν•œμ΄ μ—†λ‹€. (λͺ¨λ‘ κ°€λŠ₯)
  • < ? extends Object > μ˜ μ€„μž„ ν‘œν˜„

generic-wildcard

Box<?> box1 = new Box<Vegetable>();
Box<?> box2 = new Box<Fruit>();
Box<?> box3 = new Box<Food>();
Box<?> box3 = new Box<Carrot>();

μ™€μΌλ“œμΉ΄λ“œ 경계 κΊΌλ‚΄κΈ° / λ„£κΈ° κ³ μ°°

이 νŒŒνŠΈλŠ” λ‚œμ΄λ„κ°€ κ°‘μžκΈ° 수직 μƒμŠΉν•˜λŠ” 뢀뢄이닀.

μ™€μΌλ“œμΉ΄λ“œλ₯Ό ν•˜ν•œ κ²½κ³„λ‘œ μ§€μ •ν–ˆλŠ”κ°€ μƒν•œ κ²½κ³„λ‘œ μ§€μ •ν–ˆλŠ”κ°€ 에 따라 μ œλ„€λ¦­ νƒ€μž… 객체에 μ›μ†Œλ₯Ό κΊΌλ‚΄κ±°λ‚˜ 집어넣기 ν–‰μœ„μ— λŒ€ν•΄ λ³΅μž‘ν•œ μ œμ•½μ΄ 걸리기 λ•Œλ¬Έμ΄λ‹€. 싀무에 μž„ν•˜λŠ” ν˜„μ§μžλ“€λ„ 자주 ν–‡κΉ”λ¦¬λŠ” 뢀뢄이며, μ™œ μ œμ•½μ΄ κ±Έλ¦¬λŠ”μ§€ λ…Όλ¦¬μ μœΌλ‘œ νƒ€μž…μ„ μΆ”λ‘ ν•˜μ—¬ μ‹¬λ„μžˆλŠ” 고민이 ν•„μš”ν•˜λ‹€. (머리 풀가동 ν•˜μž!)

μ œλ„€λ¦­ νƒ€μž… ν΄λž˜μŠ€λ‘œλŠ” κ°€μž₯ μ΅μˆ™ν•œ List μžλ£Œν˜•μ„ μ΄μš©ν•΄ 예λ₯Ό λ“€κ² λ‹€.

λ…μžλΆ„λ“€μ—κ²Œ TIP을 λ“œλ¦¬μžλ©΄ '될 것 같은데 μ™œ μ•ˆλ˜μ§€?' 라고 μ˜λ¬Έμ„ ν‘œν•œ μΌ€μ΄μŠ€λŠ” λŒ€λΆ€λΆ„ ν΄λž˜μŠ€κ°€ 
ν˜•μ œ 관계에 μžˆμ„λ•Œ μ„œλ‘œ μΊμŠ€νŒ…ν•˜μ—¬ λ‚˜νƒ€λ‚˜λŠ” 문제점 λ•Œλ¬Έμ— λ§‰ν˜€μžˆλ‹€κ³  보면 λœλ‹€.

 

List<? extends U>

  • GET : μ•ˆμ „ν•˜κ²Œ κΊΌλ‚΄λ €λ©΄ U νƒ€μž…μœΌλ‘œ 받아야함
  • SET : μ–΄λ– ν•œ νƒ€μž…μ˜ μžλ£Œλ„ λ„£μ„μˆ˜ μ—†μŒ (null만 μ‚½μž… κ°€λŠ₯)
  • κΊΌλ‚Έ νƒ€μž…μ€ U / μ €μž₯은 NO

λ©”μ„œλ“œμ˜ λ§€κ°œλ³€μˆ˜μ˜ μ œλ„€λ¦­ νƒ€μž…μ΄ <? extends Fruit> λΌλŠ” 것은 Apple, Banana, Fruit νƒ€μž…μ„ 전달받아 λ‚΄λΆ€μ—μ„œ λ‹€λ£°μˆ˜ μžˆλ‹€λŠ” 말이닀.

class FruitBox {
    public static void method(List<? extends Fruit> item) {
        // μ•ˆμ „ν•˜κ²Œ κΊΌλ‚΄λ €λ©΄ Fruit νƒ€μž…μœΌλ‘œλ§Œ λ°›μ•„μ•Όν•œλ‹€
        Fruit f1 = item.get(0);

        Apple f2 = (Apple) item.get(0); // ! 잠재적 ERROR
        Banana f3 = (Banana) item.get(0); // ! 잠재적 ERROR
    }
}

public class Main {
    public static void main(String[] args) {
        List<Banana> bananas = new ArrayList<>(
                Arrays.asList(new Banana(), new Banana(), new Banana())
        );
        FruitBox.method(bananas);
    }
}

그런데 κΊΌλ‚΄λŠ” 것(GET)이 μ™œ Fruit νƒ€μž…μœΌλ‘œ λ°›μ•„μ•Ό λ˜λƒλ©΄,

  1. 만일 λ§€κ°œλ³€μˆ˜μ— List<Banana> νƒ€μž…μœΌλ‘œ λ“€μ–΄μ˜¬ 경우 Apple f2 에 ν˜•μ œ μΊμŠ€νŒ…μ΄ λΆˆκ°€λŠ₯ν•˜κΈ° λ•Œλ¬Έμ΄λ‹€.
  2. 만일 λ§€κ°œλ³€μˆ˜μ— List<Fruit> νƒ€μž…μœΌλ‘œ λ“€μ–΄μ˜¬ 경우 Apple f2 에 λ‹€μš΄ μΊμŠ€νŒ…μ΄ λΆˆκ°€λŠ₯ν•˜κΈ° λ•Œλ¬Έμ΄λ‹€.
  3. μ΄λŸ¬ν•œ 논리 였λ₯˜λ‘œ μ™€μΌλ“œμΉ΄λ“œ μ΅œμƒμœ„ λ²”μœ„μΈ Fruit νƒ€μž…μœΌλ‘œλ§Œ μ•ˆμ „ν•˜κ²Œ κΊΌλ‚Όμˆ˜ μžˆλŠ” 것이닀.
class FruitBox {
    public static void method(List<? extends Fruit> item) {
        // μ €μž₯은 NO
        item.add(new Fruit()); // ! ERROR
        item.add(new Apple()); // ! ERROR
        item.add(new Banana()); // ! ERROR
        
        item.add(null); // null 만 μ‚½μž… κ°€λŠ₯
    }
}

그리고 μ €μž₯ ν•˜λŠ”λ°(SET) μ–΄λ– ν•œ νƒ€μž…λ„ λΆˆκ°€λŠ₯ν•˜λƒλ©΄,

  1. 만일 λ§€κ°œλ³€μˆ˜μ— List<Banana> νƒ€μž…μœΌλ‘œ λ“€μ–΄μ˜¬ 경우 ν˜•μ œ 객체인 new Apple() μ €μž₯이 λΆˆκ°€λŠ₯ν•˜κΈ° λ•Œλ¬Έμ΄λ‹€.
  2. 만일 λ§€κ°œλ³€μˆ˜μ— List<Fruit> νƒ€μž…μœΌλ‘œ λ“€μ–΄μ˜¬ κ²½μš°λŠ” λ¬Έμ œκ°€ μ—†κ² μ§€λ§Œ μœ„μ˜ 논리 였λ₯˜ λ•Œλ¬Έμ— κ·Έλƒ₯ 컴파일 μ—λŸ¬λ‘œ μ²˜λ¦¬λœλ‹€.
  3. λ”°λΌμ„œ 만일 λ§€κ°œλ³€μˆ˜μ— 값을 λ„£κ³  μ‹Άλ‹€λ©΄ 무쑰건 super μ™€μΌλ“œμΉ΄λ“œλ₯Ό μ‚¬μš©ν•˜μ—¬μ•Ό ν•œλ‹€.

 

List<? super U>

  • GET : μ•ˆμ „ν•˜κ²Œ κΊΌλ‚΄λ €λ©΄ Object νƒ€μž…μœΌλ‘œλ§Œ λ°›μ•„μ•Όν•œλ‹€
  • SET : U와 U의 μžμ† νƒ€μž…λ§Œ 넣을 수 있음 (U의 μƒμœ„νƒ€μž… λΆˆκ°€λŠ₯)
  • κΊΌλ‚Έ νƒ€μž…μ€ Object / μ €μž₯은 U와 그의 μžμ†λ§Œ

λ©”μ„œλ“œμ˜ λ§€κ°œλ³€μˆ˜μ˜ μ œλ„€λ¦­ νƒ€μž…μ΄ <? super Fruit> λΌλŠ” 것은 Fruit, Food, Object νƒ€μž…μ„ 전달받아 λ‚΄λΆ€μ—μ„œ λ‹€λ£°μˆ˜ μžˆλ‹€λŠ” 말이닀.

class FruitBox {
    public static void method(List<? super Fruit> item) {
        // μ•ˆμ „ν•˜κ²Œ κΊΌλ‚΄λ €λ©΄ Object νƒ€μž…μœΌλ‘œλ§Œ λ°›μ•„μ•Όν•œλ‹€
        Object f1 = item.get(0);

        Food f2 = (Food) item.get(0); // ! 잠재적 ERROR
        Fruit f3 = (Fruit) item.get(0); // ! 잠재적 ERROR
        Apple f4 = (Apple) item.get(0); // ! 잠재적 ERROR
        Banana f5 = (Banana) item.get(0); // ! 잠재적 ERROR
    }
}

public class Main {
    public static void main(String[] args) {
        List<Food> foods = new ArrayList<>(
                Arrays.asList(new Food(), new Food(), new Food())
        );
        FruitBox.method(foods);
    }
}

그런데 κΊΌλ‚΄λŠ” 것이 μ™œ Object νƒ€μž…μœΌλ‘œ λ°›μ•„μ•Ό λ˜λƒλ©΄,

  1. 만일 λ§€κ°œλ³€μˆ˜μ— List<Food> νƒ€μž…μœΌλ‘œ λ“€μ–΄μ˜¬ 경우 Fruit f3 μ— μΊμŠ€νŒ…μ΄ λΆˆκ°€λŠ₯ν•˜κΈ° λ•Œλ¬Έμ΄λ‹€.
  2. μ΄λŸ¬ν•œ 논리 였λ₯˜λ‘œ μ™€μΌλ“œμΉ΄λ“œ μ΅œμƒμœ„ λ²”μœ„μΈ Object νƒ€μž…μœΌλ‘œλ§Œ μ•ˆμ „ν•˜κ²Œ κΊΌλ‚Όμˆ˜ μžˆλŠ” 것이닀.
class FruitBox {
    static void method(List<? super Fruit> item) {
        // μ €μž₯은 무쑰건 Fruit와 그의 μžμ† νƒ€μž…λ§Œ λ„£μ„μˆ˜ μžˆλ‹€
    	item.add(new Fruit());
        item.add(new Apple());
        item.add(new Banana());
        
        item.add(new Object()); // ! ERROR
        item.add(new Food()); // ! ERROR
    }
}

그리고 μ €μž₯ν•˜λŠ”λ° κ±°κΎΈλ‘œ Fruitκ³Ό 그의 μžμ† νƒ€μž…λ§Œ 올수 μžˆλƒλ©΄,

  1. 만일 λ§€κ°œλ³€μˆ˜μ— List<Fruit> νƒ€μž…μœΌλ‘œ λ“€μ–΄μ˜¬ 경우 new Food()λ₯Ό μ €μž₯이 λΆˆκ°€λŠ₯ν•˜κΈ° λ•Œλ¬Έμ΄λ‹€.
  2. λ”°λΌμ„œ μ–΄λ– ν•œ νƒ€μž…μ΄ 와도 μ—…μΊμŠ€νŒ… κ°€λŠ₯ μƒν•œμΈ Fruit νƒ€μž…μœΌλ‘œλ§Œ μ œν•œλœλ‹€.

 

List<?>

  • GET : μ•ˆμ „ν•˜κ²Œ κΊΌλ‚΄λ €λ©΄ Object νƒ€μž…μœΌλ‘œλ§Œ λ°›μ•„μ•Όν•œλ‹€ (super 의 νŠΉμ§•)
  • SET : μ–΄λ– ν•œ νƒ€μž…μ˜ μžλ£Œλ„ λ„£μ„μˆ˜ μ—†μŒ (null만 μ‚½μž… κ°€λŠ₯) (extends 의 νŠΉμ§•)
  • κΊΌλ‚Έ νƒ€μž…μ€ Object / μ €μž₯은 NO

λ©”μ„œλ“œμ˜ λ§€κ°œλ³€μˆ˜μ˜ μ œλ„€λ¦­ νƒ€μž…μ΄ <?> λΌλŠ” 것은 λͺ¨λ“  νƒ€μž…μ„ 전달받아 λ‚΄λΆ€μ—μ„œ λ‹€λ£°μˆ˜ μžˆλ‹€λŠ” 말이닀.

class FruitBox {
    static void method(List<?> item) { 
        // κΊΌλ‚΄λŠ”κ±΄ Object νƒ€μž…λ§Œ κ°€λŠ₯
        Object f1 = item.get(0);

        Food f2 = (Food) item.get(0); // ! 잠재적 ERROR
        Fruit f3 = (Fruit) item.get(0); // ! 잠재적 ERROR
        Apple f4 = (Apple) item.get(0); // ! 잠재적 ERROR
        Banana f5 = (Banana) item.get(0); // ! 잠재적 ERROR
    }
}
class FruitBox {
    static void method(List<?> item) {
        // μ €μž₯은 NO (null만 μ €μž₯)
        item.add(new Object()); // ! ERROR
        item.add(new Food()); // ! ERROR
        item.add(new Fruit()); // ! ERROR
        item.add(new Apple()); // ! ERROR
        item.add(new Banana()); // ! ERROR

        item.add(null);
    }
}

μ™€μΌλ“œμΉ΄λ“œ extends / super μ‚¬μš©μ‹œκΈ°

μ–Έμ œ μ–΄λ””μ„œ μ–΄λŠλ•Œμ— μ™€μΌλ“œμΉ΄λ“œ <? extends T> λ₯Ό μ‚¬μš©ν•΄μ•Ό 할지, <? super T> λ₯Ό μ‚¬μš©ν•΄μ•Ό 할지 λ”± 머릿속에 μž‘νžˆμ§€ μ•ŠλŠ”λ‹€. μ΄λŠ” ν˜„μ—…μ—μ„œλ„ 자주 κ³ λ―Όλ˜λŠ” 사항이며, κ·Έλž˜μ„œ μžλ°” 개발자라면 ν•œλ²ˆ μ―€ 읽어봐야 ν•˜λŠ” μ‘°μŠˆμ•„ λΈ”λ‘œν(Joshua J. Bloch)의 μ €μ„œ Effective Javaμ—μ„œλ„ 이에 λŒ€ν•΄μ„œ PECS λΌλŠ” 곡식을 μ†Œκ°œν•œλ‹€.

 

PECS 곡식

PECSλž€, Producer-Extends / Consumer-Super λΌλŠ” λ‹¨μ–΄μ˜ μ•½μžμΈλ° λ‹€μŒμ„ μ˜λ―Έν•œλ‹€.

  • μ™ΈλΆ€μ—μ„œ 온 데이터λ₯Ό 생산(Producer) ν•œλ‹€λ©΄ <? extends T> λ₯Ό μ‚¬μš© (ν•˜μœ„νƒ€μž…μœΌλ‘œ μ œν•œ)
  • μ™ΈλΆ€μ—μ„œ 온 데이터λ₯Ό μ†ŒλΉ„(Consumer) ν•œλ‹€λ©΄ <? super T> λ₯Ό μ‚¬μš© (μƒμœ„νƒ€μž…μœΌλ‘œ μ œν•œ).

이 ν¬μŠ€νŒ…μ˜ 초반 파트인 μžλ°”μ˜ 곡변성 / λ°˜κ³΅λ³€μ„±μ—μ„œ μ‚¬μš©ν•œ 예제λ₯Ό λ‹€μ‹œ 가져와 μ‚΄νŽ΄λ³΄μž.

class MyArrayList<T> {
    Object[] element = new Object[5];
    int index = 0;

    // μ™ΈλΆ€λ‘œλΆ€ν„° 리슀트λ₯Ό 받아와 λ§€κ°œλ³€μˆ˜μ˜ λͺ¨λ“  μš”μ†Œλ₯Ό λ‚΄λΆ€ 배열에 μΆ”κ°€ν•˜μ—¬ μΈμŠ€ν„΄μŠ€ν™” ν•˜λŠ” μƒμ„±μž
    public MyArrayList(Collection<? extends T> in) {
        for (T elem : in) {
            element[index++] = elem;
        }
    }

    // μ™ΈλΆ€λ‘œλΆ€ν„° 리슀트λ₯Ό 받아와 λ‚΄λΆ€ λ°°μ—΄μ˜ μš”μ†Œλ₯Ό λͺ¨λ‘ λ§€κ°œλ³€μˆ˜μ— μΆ”κ°€ν•΄μ£ΌλŠ” λ©”μ„œλ“œ
    public void clone(Collection<? super T> out) {
        for (Object elem : element) {
            out.add((T) elem);
        }
    }
}

 

Producers Extend

μœ„μ˜ μ˜ˆμ œμ—μ„œ extends κ°€ μ“°μ΄λŠ” 곳은 μƒμ„±μž λ©”μ„œλ“œμ˜ λ§€κ°œλ³€μˆ˜ 뢀뢄이닀. 즉, μ™ΈλΆ€μ—μ„œ 온 데이터λ₯Ό λ§€κ°œλ³€μˆ˜μ— λ‹΄μ•„ for문으둜 μˆœνšŒν•˜μ—¬ MyArrayListλ₯Ό μΈμŠ€ν„΄μŠ€ν™”(생성)ν•˜λŠ” μƒμ‚°μž(Producer) 역할을 ν•˜κ³  μžˆλ‹€κ³  말할 수 μžˆλ‹€.

class MyArrayList<T> {
    Object[] element = new Object[5];
    int index = 0;

    // μ™ΈλΆ€λ‘œλΆ€ν„° 리슀트λ₯Ό 받아와 λ§€κ°œλ³€μˆ˜μ˜ λͺ¨λ“  μš”μ†Œλ₯Ό λ‚΄λΆ€ 배열에 μΆ”κ°€ν•˜μ—¬ μΈμŠ€ν„΄μŠ€ν™” ν•˜λŠ” μƒμ„±μž
    public MyArrayList(Collection<? extends T> in) {
        for (T elem : in) { 
            element[index++] = elem;
        }
    }

    ...
}

 

Consumers Super

μ™ΈλΆ€μ—μ„œ 리슀트λ₯Ό λ°›μ•„ μš”μ†Œλ₯Ό λ³΅μ‚¬ν•˜μ—¬ μ μž¬ν•˜λŠ” clone λ©”μ„œλ“œμ˜ λ§€κ°œλ³€μˆ˜μ—λŠ” super μ™€μΌλ“œμΉ΄λ“œ ν‚€μ›Œλ“œκ°€ μ“°μ˜€λ‹€. 즉, MyArrayList의 λ‚΄λΆ€ 배열을 μ†ŒλΉ„ν•˜μ—¬ λ§€κ°œλ³€μˆ˜ λ¦¬μŠ€νŠΈμ— μ μž¬ν•˜λŠ” ν–‰μœ„λ₯Ό ν•˜κ³  μžˆλ‹€κ³  λ³Ό 수 μžˆλŠ” 것이닀.

class MyArrayList<T> {
    Object[] element = new Object[5];
    int index = 0;
    
    ...
    
    // μ™ΈλΆ€λ‘œλΆ€ν„° 리슀트λ₯Ό 받아와 λ‚΄λΆ€ λ°°μ—΄μ˜ μš”μ†Œλ₯Ό λͺ¨λ‘ λ§€κ°œλ³€μˆ˜μ— μΆ”κ°€ν•΄μ£ΌλŠ” λ©”μ„œλ“œ
    public void clone(Collection<? super T> out) {
        for (Object elem : element) {
            out.add((T) elem);
        }
    }
}

in / out 곡식

였라클 곡식 λ¬Έμ„œμ—μ„œλŠ” PECS λŒ€μ‹  in κ³Ό out 의 κ°œλ…μœΌλ‘œ μ™€μΌλ“œμΉ΄λ“œ μ‚¬μš©μ²˜λ₯Ό μ„€λͺ…ν•œλ‹€.

μœ„μ˜ μ˜ˆμ œμ—μ„œλ„ in κ³Ό out 방법을 μ‚¬μš©ν–ˆλŠ”λ°, extends 에선 λ§€κ°œλ³€μˆ˜λͺ…이 in 이고, super 에선 λ§€κ°œλ³€μˆ˜λͺ…이 out 인걸 확인 ν•  수 μžˆλ‹€. 이λ₯Ό ν•©μΉ˜λ©΄ μ•„λž˜μ™€ 같이 정리할 수 μžˆλ‹€.

  • in λ³€μˆ˜λŠ” μ½”λ“œμ— 볡사할 데이터λ₯Ό 제곡이 λͺ©μ  → extends
  • out λ³€μˆ˜λŠ” λ‹€λ₯Έ κ³³μ—μ„œ μ‚¬μš©ν•  데이터λ₯Ό 보유 → super
public static <E> void copyList(List<? extends E> in, List<? super E> out) {
    for(E elem : in) {
        out.add(elem);
    }
}

μœ„μ˜ μ™€μΌλ“œμΉ΄λ“œμ˜ μ œμ•½ νŒŒνŠΈμ—μ„œ 배웠듯이, extends λŠ” μ• μ΄ˆμ— μ›μ†Œ set은 λͺ»ν•˜κ³  μ˜€λ‘œμ§€ get 만 κ°€λŠ₯ν•˜λ‹€. λ”°λΌμ„œ μ œλ„€λ¦­ νƒ€μž… λ§€κ°œλ³€μˆ˜μ˜ 데이터λ₯Ό κ°€μ Έμ˜€λŠ” 역할을 ν•˜λ©΄ μƒν•œ 경계 μ™€μΌλ“œμΉ΄λ“œ λ₯Ό μ‚¬μš©ν•œλ‹€κ³  보면 λœλ‹€.

super은 μ• μ΄ˆμ— μ›μ†Œ get은 Object둜 κ°€μ Έμ˜€λ‹ˆ μ˜λ―Έκ°€ μ—†λ‹€κ³  보면되고, 그러면 μ˜€λ‘œμ§€ set 만 κ°€λŠ₯ν•˜λ‹€. λ”°λΌμ„œ μ œλ„€λ¦­ νƒ€μž… λ§€κ°œλ³€μˆ˜μ˜ 데이터에 μ μž¬ν•˜λŠ” 역할을 ν•˜λ©΄ ν•˜ν•œ 경계 μ™€μΌλ“œμΉ΄λ“œ λ₯Ό μ‚¬μš©ν•œλ‹€κ³  보면 λœλ‹€.


ν˜Όλ™ν•  수 μžˆλŠ” μ™€μΌλ“œμΉ΄λ“œ ν‘œν˜„

 

μ™€μΌλ“œμΉ΄λ“œλŠ” 섀계가 μ•„λ‹Œ μ‚¬μš©μ„ μœ„ν•œ 것

μƒˆλ‚΄κΈ° κ°œλ°œμžλΆ„λ“€μ΄ κ°€μž₯ 많이 μ°©κ°ν•˜λŠ” 것이 μ™€μΌλ“œμΉ΄λ“œλ₯Ό μ–΄λ””μ„œλ‚˜ μ‚¬μš©ν• μˆ˜ μžˆλ‹€κ³  μƒκ°ν•˜λŠ” 것이닀. ν•˜μ§€λ§Œ μ™€μΌλ“œμΉ΄λ“œλŠ” μ•„λž˜μ™€ 같이 ν΄λž˜μŠ€λ‚˜ μΈν„°νŽ˜μ΄μŠ€ μ œλ„€λ¦­μ„ μ„€κ³„ν• λ•ŒλŠ” μ•„μ˜ˆ μ‚¬μš©ν• μˆ˜κ°€ μ—†λ‹€.

class Sample<? extends T> { // ! Error
    
}

μ™€μΌλ“œμΉ΄λ“œλŠ” 이미 λ§Œλ“€μ–΄μ§„ μ œλ„€λ¦­ ν΄λž˜μŠ€λ‚˜ λ©”μ„œλ“œλ₯Ό μ‚¬μš©ν• λ•Œ μ΄μš©ν•˜λŠ” κ²ƒμœΌλ‘œ 보면 λœλ‹€. 예λ₯Όλ“€μ–΄ λ‹€μŒκ³Ό 같이 λ³€μˆ˜λ‚˜ λ§€κ°œλ³€μˆ˜μ— μ–΄λ– ν•œ 객체의 νƒ€μž… νŒŒλΌλ―Έν„°λ₯Ό λ°›μ„λ•Œ 그에 λŒ€ν•œ λ²”μœ„ ν•œμ •μ„ μ •ν• λ•Œ μ‚¬μš©λœλ‹€κ³  보면 λœλ‹€.

class Sample<T> {
    public static <E> void run(List<? super E> l) {}
}

public class Main {
    public static void main(String[] args) {
        Sample<?> s2= new Sample<String>();
        
        Sample<? extends Number> s1 = new Sample<Integer>();
        
        Sample.run(new ArrayList<>());
    }
}

<T extends νƒ€μž…> μ™€ <? extends U> μ°¨μ΄μ 

λ°”λ‘œ μœ„μ—μ„œ μ–ΈκΈ‰ν–ˆλ“―μ΄, μ™€μΌλ“œ μΉ΄λ“œλŠ” μ œλ„€λ¦­ 클래슀λ₯Ό λ§Œλ“€λ•Œ μ‚¬μš©ν•˜λŠ” 것이 μ•„λ‹ˆλΌ, 이미 λ§Œλ“€μ–΄μ§„ μ œλ„€λ¦­ 클래슀λ₯Ό μ‚¬μš©ν• λ•Œ νƒ€μž…μ„ μ§€μ •ν• λ•Œ μ΄μš©λ˜λŠ” 것이닀.

즉, <T extends νƒ€μž…> λŠ” μ œλ„€λ¦­ 클래슀λ₯Ό μ„€κ³„ν• λ•Œ μ μ–΄μ£ΌλŠ” 것이고, <? extends νƒ€μž…> λŠ” 이미 λ§Œλ“€μ–΄μ§„ μ œλ„€λ¦­ 클래슀λ₯Ό μΈμŠ€ν„΄μŠ€ν™” ν•˜μ—¬ μ‚¬μš©ν• λ•Œ νƒ€μž… νŒŒλΌλ―Έν„°λ‘œ λ„˜κ²¨μ€„λ•Œ μ μ–΄μ£ΌλŠ” 것이닀.


<T super νƒ€μž…> 은 μ™œ μ—†μ„κΉŒ

μ™€μΌλ“œμΉ΄λ“œμ— <T extends νƒ€μž…> 은 μ‘΄μž¬ν•˜μ§€λ§Œ, <T super νƒ€μž…> 은 μ—†λŠ” κ±Έ λ³Ό 수 μžˆλ‹€. 

<T extends νƒ€μž…> λŠ” μ •μ˜ν•  μ œλ„€λ¦­ νƒ€μž… λ²”μœ„λ₯Ό μƒν•œ μ œν•œν•˜κΈ° μœ„ν•΄ μ‚¬μš©ν•˜λŠ”λ°, <T super νƒ€μž…> 이 λœλ‹€λ©΄ 무수히 λ§Žμ€ μžλ°”μ˜ ν΄λž˜μŠ€μ™€ μΈν„°νŽ˜μ΄μŠ€κ°€ 올 수 μžˆλ‹€λŠ” 뜻이기 λ•Œλ¬Έμ—, Object와 λ‹€λ₯΄μ§€ μ•Šμ•„ κ·Έλƒ₯ μ“Έλͺ¨μ—†λŠ” μ½”λ“œμ΄κΈ° λ•Œλ¬Έμ΄λ‹€. 


<?> 와 <Object> λŠ” λ‹€λ₯΄λ‹€

비경계 μ™€μΌλ“œμΉ΄λ“œκ°€ λͺ¨λ“  νƒ€μž…μ΄ λ“€μ–΄μ˜¬ 수 μžˆμœΌλ‹ˆ Object와 λ‹€λ₯Όλ°” μ—†λ‹€κ³  λ§ν• μˆ˜ μžˆκ² μ§€λ§Œ, μ—„λ°€νžˆ List<?> 와 List<Object> λŠ” λ‹€λ₯Έ λ†ˆμ΄λ‹€. μ™œλƒν•˜λ©΄  List<Object>μ—λŠ” Object의 ν•˜μœ„ νƒ€μž…μ€ λͺ¨λ‘ 넣을 수 μžˆμ§€λ§Œ, List<?> μ—λŠ” 였직 null만 넣을 수 있기 λ•Œλ¬Έμ΄λ‹€. (잘 λͺ¨λ₯΄λ©΄ μœ„λ‘œ μ˜¬λΌκ°€ λ‹€μ‹œ λ³΅μŠ΅ν•˜μž!)

μ΄λŠ” νƒ€μž… μ•ˆμ •μ„±μ„ 지킀기 μœ„ν•œ μ œλ„€λ¦­μ˜ νŠΉμ„±μœΌλ‘œ, λ§Œμ•½ λ‹€μŒκ³Ό 같이 List<?>에 λͺ¨λ“  νƒ€μž…μ„ 넣을 수 있게 ν•œλ‹€λ©΄, List<Integer>에 Doubleν˜•μ„ μΆ”κ°€ν•˜λŠ” λͺ¨μˆœ λ°œμƒν•˜κ²Œ λ˜μ–΄μ„œ κ·Έλ ‡λ‹€.

public static void main(String[] args) {
	List<Integer> ints = new ArrayList<>();
	add(ints);
}

private static void add(List<?> ints){
	ints.add(3.14); // μ™ΈλΆ€μ—μ„œ 받은 List<Integer>에 Double을 μΆ”κ°€ν•˜λŠ” λͺ¨μˆœ λ°œμƒ
}

# 참고 자료

μ΄νŽ™ν‹°λΈŒ μžλ°” Effective Java 3/E

https://www.youtube.com/watch?v=Vv0PGUxOzq0

https://www.youtube.com/watch?v=w5AKXDBW1gQ