Post

[Day13] Generic 심화

[Day13] Generic 심화

typescript 수업 배울 때도 어려웠던 generic에 대해서 오늘은 java로 학습했다.


Generic 심화

  • generic은 재사용성, 타입 안정성, 유지보수성을 높여준다.

성능향상을 위한 제네릭

➡ 제네릭을 사용하면 타입이 일관되도록 강제할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package generic;

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

public class Test {
	public static void main(String[] args) {
		List<A> list=new ArrayList<>(); // A타입의 객체만 저장
		list.add(new A());
		list.add(new B()); // B는 A를 상속

// ❌ 컴파일 에러
    list.add("java"); // 문자열은 A 타입이 아님
		list.add(1); // 정수도 A 타입이 아님
    list.add(new String("java")); // A 타입이 아님
		list.add(new StringBuilder("java")); // StringBuilder도 A와 관계 없음
	}
}

class A{}
class B extends A{}

extends 와일드 카드(<? extends A>) 사용이 필요한 경우

문제점

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Test {
	public static void main(String[] args) {
		List<B> listB=new ArrayList<>();
		listB.add(new B());

		Biz.service(listB); // 에러
	}
}

class A{}
class B extends A{}

class Biz{
	public static void service(List<A> list) { // list<B>를 받을 수 없는 메서드
		for(A o:list) {
			//중요한 일...
		}
	}
}

해결 방법 : <? extends A> 사용

  • List<? extends A>는 A를 포함하여 A의 자식 클래스(B, C, D…)도 받을 수 있음
    • list에 값을 추가하는 것은 불가능
    • A, B, C 등 정확한 타입이 보장되지 않기 때문 ```java public class Test { public static void main(String[] args) { List listB=new ArrayList<>(); listB.add(new B());

      1
      
      Biz.service(listB); } }
      

class A{} class B extends A{}

class Biz{ public static void service(List<? extends A> list) { for(A o:list) { //중요한 일… } } }

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
32
33
34
35
>  ❓❓❓ list에 값을 추가하는 것은 불가능
> why? `<? extends A>` 는 읽기 전용 -> 정확히 어떤 하위 타입인지 모르기 때문
>
> ```java
> List<? extends A> list = listB; // ✅ B의 리스트를 받을 수 있음
> // list.add(new A());  // ERROR
> // list.add(new B());  // ERROR
>
> A item = list.get(0); // 가능 (리스트에서 A 타입으로 값을 읽어올 수 있음)
> System.out.println(item); //generic.Test$B@1b6d3586
> ```

## super 와일드카드 (<? super B>) 사용이 필요한 경우
### 문제점

```java
public class Test {
	public static void main(String[] args) {
		List<A> list=new ArrayList<>();
		list.add(new A());
		list.add(new B());

		Biz.service(list);//컴파일 에러
	}
}

class A{}
class B extends A{}

class Biz{
	public static void service(List<B> list) { // list<A>를 받을 수 없는 메서드
		for(A o:list) { ... }
	}
}

해결 방법 (<? super B> 사용)

  • List<? super B>는 B와 B의 부모(A)를 포함한 리스트를 받을 수 있음
  • 가져올 때는 Object로 가져와야 함
    • 왜냐하면 정확히 어떤 타입인지 보장이 안됨
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Test {
	public static void main(String[] args) {
		List<A> list=new ArrayList<>();
		list.add(new A());
		list.add(new B());

		Biz.service(list);
	}
}

class A{}
class B extends A{}

class Biz{
  public static void service(List<? super B> list) {
		for(Object o:list) { ... } // A만 들어있다고 보장할 수 없기 때문에 가장 상위 타입인 Object로 받는다.
	}
}

➡ 따라서 Object 타입으로 받아야 더 안전함.

Method Generic

  • 메서드 내부에서 타입을 지정할 수 있도록 하는 기능
    💡 제네릭 메서드 선언: 를 사용하여 메서드 내부에서 타입을 결정
1
2
3
4
5
6
7
8
9
10
11
public class GenericMethodExample {
  public static <T> void printItem(T item) {
    System.out.println("Item: " + item);
  }

  public static void main(String[] args) {
    printItem("Hello");   // String 타입
    printItem(100);       // Integer 타입
    printItem(3.14);      // Double 타입
  }
}

문제점 (수업 코드)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Test {
	public static void main(String[] args) {
		List<A> list=new ArrayList<>();
		list.add(new A());
		list.add(new B());
		
		Biz.service(list);
		Biz.service(1);
	}
}

class A{}
class B extends A{}

class Biz{
	public static <T> void service(T list) {
		for(A o:list) { // ❌ 컴파일 에러
			//중요한 일...
		}
	}
}
  • 이렇게 작성하면 list가 어떤 타입이든 받을 수 있음
  • 하지만 for-each 문을 사용하려면 list가 반드시 Iterable여야 함.
    • (ex) List, Set, Queue
  • list가 Integer 같은 반복 불가능한 타입이라면? → 컴파일 오류 발생

해결 방법 (정제된 Method Generic)

public static <T> void service(List<T> list) { ... }

  • List 타입만 받을 수 있도록 제한됨
  • T가 정확한 타입을 유지할 수 있음

END

This post is licensed under CC BY 4.0 by the author.