[Generic (제네릭)]

클래스 내부에서 사용할 데이터 타입을 인스턴스 생성시 확정하는 것을 제네릭이라 한다.

제네릭은 다양한 타입의 객체를 다루는 메소드 및 컬렉션 클래스를 컴파일 시, 타입 체크를 해주는 기능을 한다.

객체 타입을 컴파일 시에 체크하기 때문에 객체의 타입 안정성을 높이고 형변환의 번거로움을 줄인다.

 

※ ArrayList 같은 컬렉션 클래스는 다양한 종류의 객체를 담을 수 있지만, 보통 아래와 같이 한 종류의 객체를 담는 경우가 더 많다. 또한 꺼낼 때 마다 타입체크를 하고 형변환 하는 것은 아무래도 불편할 수 밖에 없는데, 제네릭이 이와 같은 불편함 들을 해소해준다.

List<Person> list = new ArrayList<>();
list.add(new Person("영심이"));
list.add(new Person("홍길동"));
list.get(0);

 

1. 제네릭 타입

타입을 파라미터(<T>)로 가지는 클래스 혹은 인터페이스

타입 파라미터(<T>) 를 클래스 혹은 인터페이스 명 뒤에 두어 선언

public class Sample<T>{}
public interface Sample<T>{}

[Print.class (제네릭 타입)]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package Java.Generic;
 
public class Print<T> {
    
    private T text;
    
    public void setText(T text) {
        this.text = text;
    }
    
    public void printText() {
        System.out.println(text);
    }
}
 
cs

 

[Main.class]

p.text 는 String,

b.text 는 Integer(int) 데이터 타입을 갖게된다.

: 제네릭을 사용하여 클래스를 정의할 때 데이터 타입을 정하지 않고, 인스턴스를 생성할 때 타입을 저장

 

※ jdk 1.7 버전 이상은 실제 타입 생략 가능

Print<Integer> p = new Print<>();  // Print<Integer> p = new Print<Integer>(); 와 같음

※ Raw Type : 타입 매개변수가 없는 제네릭 클래스

generic은 jdk 1.5 버전에 지원됐으며, 1.5 버전 이전의 소스와 문제가 생기는걸 방지하기 위해 Raw Type 을 허용

아래와 같이 데이터타입을 주지 않고 선언 할 경우 데이터 타입(타입 매개변수)은 Object로 간주한다.

List list = new ArrayList<>();   //List<Object> list = new ArrayList<>(); 와 같음

[제네릭의 장점]

1. 타입안정성 제공 :

명시한 타입의 데이터가 아닌 다른 형태의 데이터 타입으로의 형변환을 방지 (위 Main.class 의 8번, 14번 참고)

2. 타입체크와 형변환 생략 가능 :

다룰 객체의 데이터 타입을 미리 명시

 

[다중 타입매개변수]

타입매개변수가 N개인 경우, 아래와 같이 사용 가능

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package Java.Generic;
 
public class Pair<K, V> {
 
    private K key;
    private V value;
    
    public Pair(K key, V value){
        this.key = key;
        this.value = value;
    }
    
    public K getKey() {
        return key;
    }
    
    public V getValue() {
        return value;
    }    
}
 
cs

Client(호출부)

Pair<String, Integer> pair1 = new Pair<>("key1", 1);
System.out.println(pair1.getKey());    //key1
System.out.println(pair1.getValue());  //1

Pair<Integer, Integer> pair2 = new Pair<>(1, 2);
System.out.println(pair2.getKey());     //1
System.out.println(pair2.getValue());   //2

[제네릭과 다형성]

public class Print{}
public class ChilePrint extends Print{}

위처럼 Print 클래스, 이를 상속하는 ChildPrint 클래스가 있을 때

아래와 같이 사용 가능.

1
2
3
4
5
6
7
8
9
10
public class Client {
    public static void main(String[] args) {
        List<Print> list = new ArrayList<>();
        list.add(new Print());
        list.add(new ChildPrint());
        
        Print p = list.get(0);
        ChildPrint cp = (ChildPrint)list.get(0);    
    }
}
cs

Print 를 상속하는 ChildPrint 는 Print 타입파라미터를 갖는 list 인스턴스에 add 할 수 있다.

 

 

2. 제네릭 메소드

리턴 타입 및 매개변수 타입을 타입 매개변수로 갖는 메소드

일반 클래스의 메소드에서도 타입 매개변수를 사용하여 제네릭 메소드 정의 가능

리턴타입 앞에 타입 매개변수(<>)를 추가한 후, 리턴타입과 매개타입을 타입 매개변수로 사용가능

 

[Cart.class]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package Java.Generic;
 
import java.util.ArrayList;
import java.util.List;
 
public class Cart<T> {
    
    private List<T> items;
    
    public Cart() {
        if(items == null)items = new ArrayList<>();
    }
    
    public void setItem(T item) {
        this.items.add(item);
    }
    
    public List<T> getItems() {
        return items;
    }
    
}
 
cs

[Calculator.class (제네릭 메소드)]

1
2
3
4
5
6
7
8
9
10
11
12
package Java.Generic;
 
public class Calculator {
    public static <T> int add(Cart<T> cart1, Cart<T> cart2) {
        int result = 0;
        
        result = cart1.getItems().size() + cart2.getItems().size();
        
        return result;
    }
}
 
cs

[Client.class (호출부)]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package Java.Generic;
 
public class Client {
    public static void main(String[] args) {
        
        Cart<String> cart1 = new Cart<>();
        cart1.setItem("초콜렛");
        cart1.setItem("슬리퍼");
        
        Cart<String> cart2 = new Cart<>();
        cart2.setItem("생수");
        
        int totalSize = Calculator.add(cart1, cart2);
        System.out.println(totalSize);  // 3
        
    }
}
cs

 

 

3. 타입 파라미터의 제한

상속 및 구현 관계를 이용하여 타입 제한

※ 상위 타입이 interface인 경우에도 extends 키워드 사용.

public <T extends 상위타입> 리턴타입 메소드(매개변수, ...) {...}

[Product.class]

최상위 클래스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package Java.Generic;
 
public class Product {
    
    private String product;
    
    public void setProduct(String product) {
        this.product = product;
    }
    
    public String getProduct() {
        return this.product;
    }
}
 
cs

[Food.class]

Product를 상속받는 하위 클래스

1
2
3
4
package Java.Generic;
 
public class Food extends Product{}
 
cs

[Fruit.class]

Food 를 상속받는 하위 클래스

1
2
3
4
package Java.Generic;
 
public class Fruit extends Food{}
 
cs

[Cart.class]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package Java.Generic;
 
import java.util.ArrayList;
import java.util.List;
 
public class Cart<T> {
    
    private List<T> items;
    
    public Cart() {
        if(items == null)items = new ArrayList<>();
    }
    
    public void setItem(T item) {
        this.items.add(item);
    }
    
    public List<T> getItems() {
        return items;
    }
    
}
 
cs

[Calculator.class (제네릭 메소드)]

타입 파라미터를 Food 로 제한한다. (Food 혹은 Foor 하위 클래스만 타입 파라미터로 받을 수 있다)

1
2
3
4
5
6
7
8
9
10
11
12
package Java.Generic;
 
public class Calculator {
    public static <extends Food> int add(Cart<T> cart1, Cart<T> cart2) {
        int result = 0;
        
        result = cart1.getItems().size() + cart2.getItems().size();
        
        return result;
    }
}
 
cs

[Client.class (호출부)]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package Java.Generic;
 
public class Client {
    public static void main(String[] args) {
        
        Cart<Food> cart1 = new Cart<>();
        Product p = new Food();
        p.setProduct("초콜릿");
        cart1.setItem((Food)p);
        
        Cart<Food> cart2 = new Cart<>();
        Product p2 = new Food();
        p2.setProduct("과자");
       cart2.setItem((Food)p2);
        
        int totalSize = Calculator.add(cart1, cart2);
        System.out.println(totalSize);
    }
}
cs

※ 해당 메소드 호출 시, 타입 파라미터를 섞어서 사용 할 수 없다.

: 16번 라인의 .add(...) 호출 시, 넘겨주는 매개변수 cart1 의 타입 파라미터(6번 라인의 Cart<Food>)에 의해 .add(...) 메소드의 타입파라미터(Calculator.class의 4번 라인의 <T>)가 정해진다.

 

예를 들어, 아래와 같은 경우

14번 라인에서 빨간줄이 그어진다. 

cart3 의 Fruit 으로 .add(...) 메소드의 타입파라미터가 정해지지만, Food 를 담고있는 cart4 를 매개변수로 넣으려고 하고 있으므로 not applicable for the arguments 에러가 발생한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package Java.Generic;
 
public class Client {
    public static void main(String[] args) {
        
        Cart<Food> cart1 = new Cart<>();
        Calculator.add(cart1, cart1);
        
        Cart<Fruit> cart2 = new Cart<>();
        Calculator.add(cart2, cart2);
                
        Cart<Fruit> cart3 = new Cart<>();
        Cart<Food> cart4 = new Cart<>();
        Calculator.add(cart3, cart4);    //compile error
    }
}
cs

 

 

4. 와일드카드

1. 제네릭 타입<?>: Unbounded Wildcards(제한 없음)

  타입 파라미터로 모든 클래스나 인터페이스 타입이 올 수 있다.

2. 제네릭 타입<? extends 상위 타입>: Upper Bounded Wildcards(상위 클래스 제한)

  타입 파라미터로 상위 타입의 하위 타입만 올 수 있다.

3. 제네릭 타입<? super 하위 타입>: Lower Bounded Wildcards(하위 클래스 제한)

  타입 파라미터로 하위 타입의 상위타입만 올 수 있다.

 

[example]

public class Product {}
public class Food extends Product {}
public class Fruit extends Food {}

[Calculator.class : 타입 파라미터 제한과 와일드카드 사용의 차이]

add1(...) : 타입 파라미터를 제한

add3(...) : 와일드카드를 사용하여 매개변수의 타입을 제한

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
package Java.Generic;
 
public class Calculator {
    public static <extends Food> int add1(Cart<T> cart1, Cart<T> cart2, Cart<T> cart3) {
        //제네릭 메소드에서의 타입 파라미터의 제한
        //타입파라미터를 Food 로 제한
        //매개변수는 타입파라미터인 T를 갖는 Cart이며, T는 Food 및 Food 의 subclass 로 제한
        int result = 0;
        result = cart1.getItems().size() + cart2.getItems().size() + cart3.getItems().size();
        return result;
    }
    
//    public static <T super Food> int add2(Cart<T> cart1, Cart<T> cart2, Cart<T> cart3) {    
//        //제네릭 메소드에서 타입 파라미터를 제한할 때 위와 같은 super키워드는 불가 
//        //compile error
//        int result = 0;
//        result = cart1.getItems().size() + cart2.getItems().size() + cart3.getItems().size();
//        return result;
//    }
    
    public static int add3(Cart<super Food> cart1, Cart<?> cart2, Cart<extends Food> cart3) {
        //매개변수의 타입을 와일드카드 (?)를 사용하여 제한
        //cart1 맥대변수(Cart)의 타입파라미터 : Food 및 Food 를 subclass 
        //cart2 매개변수(Cart)의 타입파라미터 : 아무 타입이나 담고있는 Cart
        //cart3 매개변수(Cart)의 타입파라미터 : Food 및 Food 의 subclass
        int result = 0;
        result = cart1.getItems().size() + cart2.getItems().size() + cart3.getItems().size();
        return result;
    }
}
 
cs

[Client.class]

1. add1사용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package Java.Generic;
 
public class Client {
    public static void main(String[] args) {
        
        Cart<Food> cart1 = new Cart<>();
        Calculator.add1(cart1, cart1, cart1);    //타입파라미터를 Food 로 지정
        
        Cart<Fruit> cart2 = new Cart<>();
        Calculator.add1(cart2, cart2, cart2);    //타입파라미터를 Fruit 으로 지정
        
        Cart<Fruit> cart3 = new Cart<>();
        Cart<Food> cart4 = new Cart<>();
        Calculator.add1(cart3, cart3, cart4);    //car3 매개변수에 의해 Fruit 으로 타입파라미터가 정해지지만, cart4 Food를 넣으려하기 때문에 Compile Error
        
    }
}
 
cs

2. add3 사용

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
package Java.Generic;
 
public class Client {
    public static void main(String[] args) {
        
        Cart<Product> cart1 = new Cart<>();
        Product p = new Product();
        p.setProduct("바가지");
        cart1.setItem(p);
        
        Cart<String> cart2 = new Cart<>();
        cart2.setItem("연필");
        
        Cart<Fruit> cart3 = new Cart<>();
        Product p2 = new Fruit();
        p2.setProduct("사과");
        Product p3 = new Fruit();
        p3.setProduct("수박");
        cart3.setItem((Fruit)p2);
        cart3.setItem((Fruit)p3);
        
        int totalSize = Calculator.add3(cart1, cart2, cart3);
        System.out.println(totalSize);
    }
}
cs

 

※ 

E - Element (used extensively by the Java Collections Framework)

K - Key

N - Number

T - Type

V - Value

 

 

참고:

https://devbox.tistory.com/entry/Java-%EC%A0%9C%EB%84%A4%EB%A6%AD

https://movefast.tistory.com/74

https://ict-nroo.tistory.com/42

 

 

 

 

반응형

'back > java' 카테고리의 다른 글

[Java] lambda 람다 2 (메소드참조)  (0) 2020.01.17
[Java] lambda 람다 1 (기본문법)  (0) 2020.01.15
[Java] Compile  (0) 2019.12.22
[Java] Collection Framework  (0) 2019.12.21
Comparator, Comparable + Arrays.sort() 그리고 인터페이스..  (0) 2019.09.02

+ Recent posts