Java 제네릭 Generics

김정훈·2024년 4월 26일

Java

목록 보기
28/48

제네릭

타입 안정성 보장
형변환의 번거로움 해결

참고)

단점
1) 타입 안정성 X // instaceof 연산자사용
2) 형변환의 번거로움
이를 해결하기위해 Object 클래스 사용

컴파일 시점 : 자료형이 명확하게 결정되어 있어야 컴파일 가능

👉 컴파일러가 알수 있는 형태로 자료형을 결정
(모든 클래스는 Object의 하위 클래스임을 알고 있다. 👉 자료형을 Object로 결정)

자료형 결정 시점

객체를 생성하는 시점에 타입 힌트를 통해서 형변환이 발생(Object 👉 Apple)
예) Box<Apple> appleBox = new Box<Apple>();

public class Ex01 {
    public static void main(String[] args) {
        Box appleBox = new Box();
        appleBox.setItem(new Apple());

        Apple apple = (Apple)appleBox.getItem(); //1. 형변환의 번거로움 //
        System.out.println(apple.get());

        Box grapeBox = new Box();
        grapeBox.setItem(new Grape());

        if(grapeBox.getItem() instanceof Apple){ //2. 타입안정성이 떨어짐.
            Apple grape = (Apple)grapeBox.getItem();
            System.out.println(grape.get());
        }
    }
}

2. 제네릭 클래스의 선언

public class Apple {
    public String get() {
        return "사과";
    }
}

public class Grape {
    public String get() {
        return "포도";
    }
}

public class Box<T> {
    private T item;
    public void setItem(T item){
        this.item = item;
    }

    public T getItem(){
        return item;
    }
}

public class Ex01 {
    public static void main(String[] args) {
        Box<Apple> appleBox = new Box<Apple>();
        appleBox.setItem(new Apple());
        Apple apple = appleBox.getItem();
        System.out.println(apple.get());

    }
}

3. 제네릭 용어

Class Box<T> {}

1) 제네릭 클래스 : Box<T>
2) 타입변수 T : T
3) 원시타입 : Box : 원시 타입(raw type)

4. 제네릭의 제한

1) 타입 매개변수와 동일한 자료형을 입력.

Box<Apple> appleBox = new Box<Apple>(); // OK, Apple 객체만 저장가능
Box<Grape> grapeBox = new Box<Grape>(); // OK, Grape 객체만 저장가능

2) static멤버에 타입 변수 T를 사용할 수 없다.

처음부터 공간이 필요한 정적 변수는 사용불가
private static T item( X )

3) 지네릭 타입의 배열을 생성하는 것도 허용하지 않는다.

지네릭 배열을 생성할 수 없는 것은 new 연산자 때문.
이 연산자는 컴파일 시점에 타입 T가 뭔지 정확히 알아야 한다.
private static T[] nums

public class Box<T> { // Box<T> : 제네릭 클래스, T 타입 매개변수, Box : 원시타입
    private T item;

    //private  static  T item2;
    //사용 불가, 처음부터 공간을 할당 받으므로 자료형이 명확해야한다.

    //private static T[] nums = new T[3];
    //사용불가, 배열에서 공간 생성을 위해서는 자료형이 명확해야한다.

    public void setItem(T item){
        this.item = item;

    }
    public T getItem() {
        return item;
    }
}

5. 제네릭 클래스의 객체 생성과 사용

  • 컴파일시에 형식 오류로 제거
  • 타입 매개변수 T는 우선 Object 변경
  • 객체를 만드는 시점에 있는 타입 힌트를 통해서 형변환이 발생
  • ex) Box<Apple> appleBox = new Box<>()👉Object👉 Apple

6. 제한된 제네릭 클래스

타입 문자로 사용할 타입을 명시하면 한 종류의 타입만 저장할 수 있도록 제한할 수 있지만, 여전히 모든 종류의 타입을 지정할 수 있는 것에는 변함이 없다. 타입 매개변수 T에 지정할 수 있는 타입의 종류를 제한할 수 있는 방법이 있다.

  • 메서드 내부에서 접근 가능한 제네릭 타입 인스턴스 자원은 모두 Object형
    • 형변환된 객체의 정의된 인스턴스 자원 접근 X
    • 공통된 틀(abstract)을 정해서 타입 매개변수의 하위 클래스임을 정의
	<T extends 타입> -> T 는 타입의 하위 클래스 
	<T extends 타입1 & 타입2> -> T는 타입1의 하위 클래스 이고 타입2 인터페이스의 구현 클래스 

T는 Fruit의 하위 클래스임을 알 수 있음 👉 Object 보다는 Fruit로 변환

1) extends 를 사용하면, 특정 타입의 하위클래스만 대입할 수 있게 제한

class FruitBox<T extends Fruit> { // Fruit의 하위클래스만 타입으로 지정가능
	ArrayList<T> list = new ArrayList<T>();
	...
}
FruitBox<Apple> appleBox = new FruitBox<Apple>(); // OK
FruitBox<Toy> toyBox = new FruitBox<Toy>(); // 에러. Toy는 Fruit의 하위클래스가 아님

2) 인터페이스를 구현해야 한다는 제약이 필요하다면, 이때에도 extends를 사용

interface Eatable {}
class FruitBox<T extends Eatable> { ... }

3)클래스 Fruit의 하위 클래스이면서 Eatable 인터페이스도 구현해야 한다면 아래와 같이 &기호로 연결

class FruitBox<T extends Fruit & Eatable> { ... }

👉 FruitBox에는 Fruit의 하위클래스면서 Eatable을 구현한 클래스만 타입 매개변수 T에 대입될 수 있다.

//T는 Fruit의 하위클래스로 한정.
public class Box<T extends Fruit> { // Box<T> : 제네릭 클래스, T 타입 매개변수, Box : 원시타입
    private T item;

    public void setItem(T item){
        this.item = item;

    }
    public T getItem() {
        return item;
    }

    public String toString(){
        // T == Fruit Fruit를 상속받았으니 Object가 아니라 Fruit
        return item.get();
    }
}

public abstract class Fruit {
    public abstract String get();

}    

7. 와일드 카드

<?> : <? extends Object>
<? extends 타입> -> T는 타입의 하위 클래스 : 타입으로 상한 제한
<? extends 클래스형 $ 인터페이스형> : 사용불가, 제니릭 클래스에서만 가능.
<? super 타입> -> T는 타입의 상위 클래스 : 타입으로 하한 제한

중복 메서드 정의 문제

public class Jucier {
    public static void make(Box<Apple> box){
        ArrayList<Apple> fruits = box.getItems();
        System.out.println(fruits);
    }
    public static void make(Box<Grape> box){ 
    //오류 발생
    //컴파일시 <Grape>제거되기 때문에 메서드가 중복정의되어진다.
    }
}

와일드 카드 ?사용

public class Jucier {
    public static void make(Box<?> box){
        ArrayList<?> fruits = box.getItems();
        System.out.println(fruits);
    }
}

public class Ex02 {
    public static void main(String[] args) {
        Box<Grape> grapeBox = new Box<>();
        grapeBox.add(new Grape());
        grapeBox.add(new Grape());
        Jucier.make(grapeBox);

    }
}

<? extends 타입>

public class Jucier {
    // 상한제한 ? : Fruit, Apple, Grape
    public static void make(Box<? extends Fruit> box){
        ArrayList<?> fruits = box.getItems();
        System.out.println(fruits);
    }
    // 하한제한 ? : Apple,Fruit,Object
    public static void make2(Box<? super Apple> box){
        ArrayList<?> fruits = box.getItems();
        System.out.println(fruits);
    }
}
      
public class Ex03 {
    public static void main(String[] args) {
        Box<Toy> toyBox = new Box<>();
        toyBox.add(new Toy());
        //Jucier.make(toyBox); 오류

    }
}      

8. 제네릭 메서드

  • 타입을 클래스에 정의하면 : 지네릭 클래스
    • 예) clss Box<T>: T의 자료형은 객체가 생성될 때 결정
  • 타입을 메서드의 반환값 타입 앞에 정의하면 : 지네릭 메서드
    • public <T,U> String method(T str1, U str2); : T,U의 자료형은 함수가 호출될때 결정.
public static <T extends Fruit> void make3(Box<T> box){
        
    }      

9. 제네릭 타입의 제거

10. 제네릭 클래스, 제네릭 메서드 차이

제네릭 클래스 : Box가 객체가 될 때 T의 자료형 결정.
제네릭 매서드 : 메서드 호출시 T의 자료형 결정

public class Box<T> {
    private T item;
    public void method1(T str1, T str2){ //제네릭 클래스, Box가 객체가 될 때 T의 자료형 결정.

    }
    public <T> void method2(T str1, T str2){ // 제네릭 매서드, 메서드 호출시 T의 자료형 결정

    }
}
      
profile
안녕하세요!

0개의 댓글