# μ§€λ„€λ¦­μŠ€ (Generics)

written by sohyeon, hyemin πŸ’‘


# 1. μ§€λ„€λ¦­μ΄λž€?

지넀릭은 λ‹€μ–‘ν•œ νƒ€μž…μ˜ 객체듀을 λ‹€λ£¨λŠ” λ©”μ„œλ“œλ‚˜ μ»¬λ ‰μ…˜ ν΄λž˜μŠ€μ— 컴파일 μ‹œμ˜ νƒ€μž…μ²΄ν¬λ₯Ό ν•΄μ£ΌλŠ” κΈ°λŠ₯이닀.
객체의 νƒ€μž…μ„ 컴파일 μ‹œμ— μ²΄ν¬ν•˜κΈ° λ•Œλ¬Έμ— 잘λͺ»λœ νƒ€μž…μ΄ μ‚¬μš©λ  수 μžˆλŠ” 문제λ₯Ό 컴파일 κ³Όμ •μ—μ„œ μ œκ±°ν•  수 μžˆλ‹€.

# μ§€λ„€λ¦­μ˜ μž₯점

  • νƒ€μž… μ•ˆμ •μ„±μ„ 제곡
  • νƒ€μž…μ²΄ν¬μ™€ ν˜•λ³€ν™˜μ„ μƒλž΅ν•  수 μžˆμœΌλ―€λ‘œ μ½”λ“œκ°€ κ°„κ²°ν•΄ 짐

비지넀릭 μ½”λ“œλŠ” λΆˆν•„μš”ν•˜κ²Œ νƒ€μž… λ³€ν™˜μ„ ν•˜κΈ° λ•Œλ¬Έμ— ν”„λ‘œκ·Έλž¨ μ„±λŠ₯에 μ•…μ˜ν–₯을 λ―ΈμΉœλ‹€.

# ex) 예제

List list = new ArrayList();

list.add("hello");

String str = (String) list.get(0); // νƒ€μž… λ³€ν™˜μ΄ ν•„μš”



List<String> list2 = new ArrayList<>();

list2.add("hello");

String str2 = list.get(0); // λΆˆν•„μš”

# 2. μ œλ„€λ¦­ 클래슀 μ„ μ–Έ

# ex) Box 클래슀

/* Box<T>클래슀 μ„ μ–Έ */

// 지넀릭 νƒ€μž… Tλ₯Ό μ„ μ–Έ
class Box<T> { // class Box {
  // Object item;
  T item;

  void setItem(T item) { // void setItem(Object item) {
    this.item = item;
  }

  T getItem() { // Object getItem() {
    return item;
  }
}

/* Box<T>클래슀 μ‚¬μš© */ 

Box<String> b = new Box<String>(); // T λŒ€μ‹  μ‹€μ œ νƒ€μž… 지정

b.setItem(new Object()); // ERROR: Stringνƒ€μž… μ΄μ™Έμ˜ νƒ€μž…μ€ μ§€μ •λΆˆκ°€
b.setItem("ABC"); // OK: Stringνƒ€μž…λ§Œ κ°€λŠ₯

// String item = (String)b.getItem(); 
String item = b.getItem(); // ν˜•λ³€ν™˜ ν•„μš”μ—†μŒ

/* ν˜Έν™˜μ„± */

Box b2 = new Box(); // OK: TλŠ” Object둜 κ°„μ£Όλœλ‹€.
b2.setItem("ABC"); // WARN: unchecked or unsafe operation.
b2.setItem(new Object()); // WARN: unchecked or unsafe operation.
  • T: νƒ€μž… λ³€μˆ˜(type variable)
    Tκ°€ μ•„λ‹Œ λ‹€λ₯Έ 것도 μ‚¬μš©κ°€λŠ₯ν•˜λ‹€. (ex. ArrayList, Map<K,V>)
    상황에 맞게 의미 μžˆλŠ” 문자λ₯Ό μ„ νƒν•΄μ„œ μ‚¬μš©ν•˜λŠ” 것이 μ’‹λ‹€. (T:type, E:element, K:key, V:value, ...)
    기호만 λ‹€λ₯Ό 뿐 'μž„μ˜μ˜ μ°Έμ‘°ν˜• νƒ€μž…'을 μ˜λ―Έν•œλ‹€.

# ex) Box클래슀

  • Box클래슀: μ–΄λ–€ νƒ€μž…μ΄λ“  ν•œκ°€μ§€ νƒ€μž…μ„ μ •ν•΄μ„œ 넣을 수 μžˆλ‹€.

  • Box클래슀: Stringνƒ€μž…λ§Œ 담을 수 μžˆλ‹€.

class Box<String> { // 지넀릭 νƒ€μž…μ„ String으둜 지정
  String item;
  void setItem(String item) {
    this.item = item;
  }
  String getItem() {
    return item;
  }
}

# μ§€λ„€λ¦­μŠ€μ˜ μ œν•œ

  • λͺ¨λ“  객체에 λ™μΌν•˜κ²Œ λ™μž‘ν•΄μ•Ό ν•˜λŠ” staticλ©€λ²„μ—λŠ” νƒ€μž…λ³€μˆ˜ Tλ₯Ό μ‚¬μš©ν•  수 μ—†λ‹€.

    • TλŠ” μΈμŠ€ν„΄μŠ€λ³€μˆ˜λ‘œ κ°„μ£Όλ˜κΈ° λ•Œλ¬Έμ΄λ‹€. staticμ—λŠ” μΈμŠ€ν„΄μŠ€λ³€μˆ˜λ₯Ό μ°Έμ‘°ν•  수 μ—†λ‹€.
  • 지넀릭 νƒ€μž…μ˜ 배열을 (선언은 κ°€λŠ₯ν•˜μ§€λ§Œ) μƒμ„±ν•˜λŠ” 것은 λΆˆκ°€λŠ₯ν•˜λ‹€.

    • newμ—°μ‚°μž, instacneofμ—°μ‚°μžλŠ” 컴파일 μ‹œμ μ— νƒ€μž…Tκ°€ 뭔지 μ •ν™•ν•˜κ²Œ μ•Œμ•„μ•Ό ν•˜κΈ° λ•Œλ¬Έμ— Tλ₯Ό ν”Όμ—°μ‚°μžλ‘œ μ‚¬μš©ν•  수 μ—†λ‹€.

    μ°Έκ³ : 지넀릭 배열을 κΌ­ 생성해야 ν•  경우 방법
    newμ—°μ‚°μž λŒ€μ‹  'Reflection API'의 newInstance()와 같이 λ™μ μœΌλ‘œ 객체λ₯Ό μƒμ„±ν•˜λŠ” λ©”μ„œλ“œλ‘œ λ°°μ—΄ 생성
    Object배열을 μƒμ„±ν•΄μ„œ λ³΅μ‚¬ν•œ λ‹€μŒμ— T[]둜 ν˜•λ³€ν™˜

# 3. 지넀릭 클래슀의 객체 생성과 μ‚¬μš©

# ex) Box 지넀릭 클래슀 μ •μ˜

class Box<T> {
  ArrayList<T> list = new ArrayList<T>();

  void add(T item) { list.add(item); }
  T get(int i) { return list.get(i); }
  ArrayList<T> getList() { return list; }
  int size() { return list.size(); }
  public String toString() { return list.toString(); }
}

# ex) Box 객체

Box<Apple> appleBox = new Box<Apple>(); // OK
// ERROR: μ°Έμ‘°λ³€μˆ˜μ™€ μƒμ„±μžμ— λŒ€μž…λœ νƒ€μž…μ΄ μΌμΉ˜ν•΄μ•Ό ν•œλ‹€.
Box<Apple> appleBox = new Box<Grape>(); 

// νƒ€μž… 상속관계: Apple이 Fruit의 μžμ†μΌ 경우
// ERROR: 상속관계여도 일치된 νƒ€μž…μ΄μ—¬μ•Ό ν•œλ‹€.
Box<Fruit> appleBox = new Box<Apple>(); 

// 지넀릭 클래슀 상속관계: FruitBox<T>κ°€ Box<T>의 μžμ†μΈ 경우
Box<Apple> appleBox = new FruitBox<Apple>(); // OK: λ‹€ν˜•μ„±

// μΆ”μ •κ°€λŠ₯ν•œ νƒ€μž… μƒλž΅ κ°€λŠ₯ (JDK1.7λΆ€ν„°)
Box<Apple> appleBox = new Box<Apple>(); // OK
Box<Apple> appleBox = new Box<>(); // JDK1.7λΆ€ν„° OK

# ex) Box.add() μ‚¬μš©

// μƒμ„±μ‹œ λŒ€μž…λœ νƒ€μž…κ³Ό λ‹€λ₯Έ νƒ€μž…μ˜ 객체 μΆ”κ°€ λΆˆκ°€
Box<Apple> appleBox = new Box<Apple>();
appleBox.add(new Apple()); // OK
appleBox.add(new Grape()); // ERROR

// νƒ€μž… 상속관계: Apple이 Fruit의 μžμ†
Box<Fruit> fruitBox = new Box<Fruit>();
fruitBox.add(new Fruit()); // OK
fruitBox.add(new Apple()); // ERROR: Fruit만 κ°€λŠ₯

# 4. μ œν•œλœ 지넀릭 클래슀

νƒ€μž… 문자둜 μ‚¬μš©ν•  νƒ€μž…μ„ λͺ…μ‹œν•˜λ©΄ ν•œ μ’…λ₯˜μ˜ νƒ€μž…λ§Œ 지정할 수 μžˆλ„λ‘ μ œν•œν•  수 μžˆμ§€λ§Œ,
κ·Έλž˜λ„ λͺ¨λ“  μ’…λ₯˜μ˜ νƒ€μž…μ„ 지정할 수 μžˆλ‹€λŠ” κ²ƒμ—λŠ” 변함이 μ—†λ‹€.

지넀릭 νƒ€μž…μ— extendsλ₯Ό μ‚¬μš©ν•˜λ©΄, νŠΉμ • νƒ€μž…μ˜ μžμ†λ“€λ§Œ λŒ€μž…ν•  수 있게 μ œν•œν•  수 μžˆλ‹€.

# ex) 예제

class FruitBox<T extends Fruit>{    // Fruit의 μžμ†λ§Œ νƒ€μž…μœΌλ‘œ 지정 κ°€λŠ₯
    ArrayList<T> list = new ArrayList<T>();
}
FruitBox<Apple> appleBox = new FruitBox<Apple>();  //OK. Apple은 Fruit의 μžμ†
FruitBox<Toy> toyBox = new FruitBox<Toy>();  //μ—λŸ¬. ToyλŠ” Fruit의 μžμ†μ΄ μ•„λ‹˜

ν΄λž˜μŠ€κ°€ μ•„λ‹ˆλΌ μΈν„°νŽ˜μ΄μŠ€λ₯Ό κ΅¬ν˜„ν•΄μ•Ό ν•œλ‹€λŠ” μ œμ•½μ΄ ν•„μš”ν•˜λ‹€λ©΄, extendsλ₯Ό μ‚¬μš©ν•œλ‹€.
implementsλ₯Ό μ‚¬μš©ν•˜μ§€ μ•ŠλŠ” λ‹€λŠ” 점을 μ£Όμ˜ν•˜μž.

# 5. μ™€μΌλ“œ μΉ΄λ“œ

# μ™€μΌλ“œ μΉ΄λ“œ λ„μž… 이유

static λ©”μ„œλ“œμ—λŠ” νƒ€μž… λ§€κ°œλ³€μˆ˜ Tλ₯Ό λ§€κ°œλ³€μˆ˜μ— μ‚¬μš©ν•  수 μ—†λ‹€.

# ex) static λ©”μ„œλ“œ μ˜ˆμ‹œ

class Juicer {
  static Juice makeJuice(FruitBox<Fruit> box) {
    String tmp = "";
    for(Fruit f: box.getList()) tmp += f + " ";
    return new Juice(tmp);
  }
}

FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
FruitBox<Apple> appleBox = new FruitBox<Apple>();

Juicer.makeJuice(fruitBox); // OK
Juicer.makeJuice(appleBox); // ERROR: FruitBox<Apple> -> FruitBox<Fruit> ν˜•λ³€ν™˜ μ•ˆλ¨

μ§€λ„€λ¦­μŠ€λ₯Ό μ‚¬μš©ν•˜μ§€ μ•Šκ±°λ‚˜, νŠΉμ • νƒ€μž…μ„ μ§€μ •ν•΄μ„œ 써야 ν•œλ‹€.
ν•˜μ§€λ§Œ, νŠΉμ •νƒ€μž…μ„ μ§€μ •ν•˜λ©΄ μ—¬λŸ¬κ°€μ§€ νƒ€μž…μ„ 받을 μˆ˜κ°€ μ—†λ‹€.

μ΄λ‘œμΈν•΄ 지넀릭 νƒ€μž…μ΄ λ‹€λ₯Έ μ˜€λ²„λ‘œλ”©μ„ ν•˜κ²Œ λ˜λŠ”λ°,
지넀릭 νƒ€μž…μ΄ λ‹€λ₯Έ κ²ƒλ§ŒμœΌλ‘œλŠ” μ˜€λ²„λ‘œλ”©μ΄ μ„±λ¦½λ˜μ§€ μ•ŠμœΌλ©° λ©”μ„œλ“œ 쀑볡 μ •μ˜λ‘œ 였λ₯˜κ°€ λ°œμƒν•œλ‹€.

# ex) 지넀릭 νƒ€μž…μ΄ λ‹€λ₯Έ μ˜€λ²„λ‘œλ”© μ˜ˆμ‹œ

// 컴파일 μ—λŸ¬ λ°œμƒ
class Juicer {
  static Juice makeJuice(FruitBox<Fruit> box) {
    String tmp = "";
    for(Fruit f: box.getList()) tmp += f + " ";
    return new Juice(tmp);
  }

  static Juice makeJuice(FruitBox<Apple> box) {
    String tmp = "";
    for(Fruit f: box.getList()) tmp += f + " ";
    return new Juice(tmp);
  }
}

이런 문제λ₯Ό ν•΄κ²°ν•˜κΈ° μœ„ν•΄ κ³ μ•ˆλœ 것이 μ™€μΌλ“œ μΉ΄λ“œμ΄λ‹€.

# μ™€μΌλ“œ μΉ΄λ“œ μ‚¬μš©

# μ™€μΌλ“œμΉ΄λ“œ 기호: ?

  • μ™€μΌλ“œμΉ΄λ“œλŠ” μ–΄λ– ν•œ νƒ€μž…λ„ 될 수 μžˆλ‹€.
    ?λŠ” Object와 λ‹€λ¦„μ—†μœΌλ―€λ‘œ extends, super둜 μƒν•œκ³Ό ν•˜ν•œμ„ μ œν•œ ν•  수 μžˆλ‹€.

  • <? extends T>: μ™€μΌλ“œμΉ΄λ“œμ˜ μƒν•œ μ œν•œ (T와 κ·Έ μžμ†λ“€λ§Œ κ°€λŠ₯)
  • <? super T>: μ™€μΌλ“œμΉ΄λ“œμ˜ ν•˜ν•œ μ œν•œ (T와 κ·Έ μ‘°μƒλ“€λ§Œ κ°€λŠ₯)
  • <?>: μ œν•œμ—†μŒ (λͺ¨λ“  νƒ€μž… κ°€λŠ₯), <? extends Object>와 동일
  • 지넀릭 ν΄λž˜μŠ€μ™€ 달리 μ™€μΌλ“œ μΉ΄λ“œμ—λŠ” &을 μ‚¬μš©ν•  수 μ—†λ‹€. (즉, <? extends T & E>와 같이 μ“Έμˆ˜μ—†μŒ)

# ex) μ‚¬μš© μ˜ˆμ‹œ

class Juicer {
  static Juice makeJuice(FruitBox<? extends Fruit> box) {
    String tmp = "";
    for(Fruit f: box.getList()) tmp += f + " ";
    return new Juice(tmp);
  }
}
  • <? extends Object>: λͺ¨λ“ νƒ€μž…이 κ°€λŠ₯

# 6. 지넀릭 λ©”μ„œλ“œ

지넀릭 λ©”μ„œλ“œλŠ” λ©”μ„œλ“œμ˜ 선언뢀에 지넀릭 νƒ€μž…μ΄ μ„ μ–Έλœ λ©”μ„œλ“œμ΄λ‹€.

# ex) 지넀릭 λ©”μ„œλ“œ μ˜ˆμ‹œ

// Collections.sort()
static <T> void sort(List<T> list, Comparator<? super T> c)
  • 지넀릭 클래슀의 νƒ€μž…λ§€κ°œλ³€μˆ˜ T와 지넀릭 λ©”μ„œλ“œμ˜ νƒ€μž…λ§€κ°œλ³€μˆ˜ TλŠ” μ„œλ‘œ κ°™μ€λ¬Έμžλ₯Ό 쓰더라도 λ³„κ°œμ˜ 것
  • 지넀릭 λ©”μ„œλ“œλŠ” 지넀릭 ν΄λž˜μŠ€κ°€ μ•„λ‹Œ ν΄λž˜μŠ€μ—λ„ μ •μ˜ κ°€λŠ₯
  • static λ©€λ²„μ—λŠ” νƒ€μž… λ§€κ°œλ³€μˆ˜λ₯Ό μ‚¬μš©ν•  수 μ—†μ§€λ§Œ, static λ©”μ„œλ“œμ—λŠ” κ°€λŠ₯
  • λ©”μ„œλ“œμ— μ„ μ–Έλœ νƒ€μž…μ€ μ§€μ—­λ³€μˆ˜μ™€ 같은 λŠλ‚Œ
    (λ©”μ„œλ“œ λ‚΄μ—μ„œλ§Œ μ‚¬μš©λ˜λ―€λ‘œ static이건 μ•„λ‹ˆκ±΄ μƒκ΄€μ—†μŒ)

# 7. 지넀릭 νƒ€μž…μ˜ ν˜•λ³€ν™˜

# - μ§€λ„€λ¦­νƒ€μž… <-> μ›μ‹œνƒ€μž…: OK

# - μ§€λ„€λ¦­νƒ€μž… <-> μ§€λ„€λ¦­νƒ€μž…: ERROR

# - μ™€μΌλ“œμΉ΄λ“œ μ§€λ„€λ¦­νƒ€μž… <-> μ§€λ„€λ¦­νƒ€μž…: OK

# - μ™€μΌλ“œμΉ΄λ“œ 지넀릭 νƒ€μž… <-> μ™€μΌλ“œμΉ΄λ“œ μ§€λ„€λ¦­νƒ€μž…: OK

Last Updated: 12/5/2020, 11:36:44 AM