Notice
Recent Posts
Recent Comments
Link
«   2024/11   »
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
Tags more
Archives
Today
Total
관리 메뉴

요리사에서 IT개발자로

스파르타 부트캠프 Java 제네릭 정리 (다락방) 본문

Java

스파르타 부트캠프 Java 제네릭 정리 (다락방)

H.S-Backend 2024. 6. 3. 17:28

제네릭 없이 다형성만 활용하여 코드를 작성할 경우

 

다형성을 사용하면 코드의 중복을 제거할 수 있고

기존 코드를 재사용할 수 있으나

다형성만 사용한다면 타입 안전성 문제가 발생한다.

public class IntegerStore {

    private Integer field;

    public void setField(Object object) {
        this.field = field;
    }

    public Integer getField() {
        return field;
    }

}
public class LongStore {

    private Long field;

    public void setField(Object object) {
        this.field = field;
    }

    public Long getField() {
        return field;
    }

}
public class StringStore {

    private String field;

    public void setField(Object object) {
        this.field = field;
    }

    public String getField() {
        return field;
    }

}
public class Main {

    public static void main(String[] args) {

        // Integer
        IntegerStore integerStore = new IntegerStore();
        integerStore.setField(100);
        System.out.println("integerStore.getField() = " + integerStore.getField());
        // Long
        LongStore longStore = new LongStore();
        longStore.setField(100000);
        System.out.println("longStore.getField() = " + longStore.getField());
        // String
        StringStore stringStore = new StringStore();
        stringStore.setField("문자열입니다");
        System.out.println("stringStore.getField() = " + stringStore.getField());

    }

}

다형성을 사용하기 전 코드에서는 

코드 중복이 발생한다.


 

 

public class ObjectStore {

    private Object field;

    public void setField(Object object) {
        this.field = field;
    }

    // return Type이 Obejct 이다.
    public Object getField() {
        return field;
    }

}
public class Main {

    public static void main(String[] args) {

        // Integer
        ObjectStore integerStore = new ObjectStore();
        integerStore.setField(10);

        System.out.println("integerStore.getField() = " + integerStore.getField());

        // Casting : Object -> Integer
        Integer integer = (Integer) integerStore.getField();
        System.out.println(integer);


        // String
        ObjectStore stringStore = new ObjectStore();
        stringStore.setField("sparta");
        System.out.println("stringStore.getField() = " + stringStore.getField());
        // Casting : Object -> String
        String string = (String) stringStore.getField();
        System.out.println(string);


        // Long, Float, 다른 Object 등등..

        // Integer가 아닌 String을 넣는다면?
        integerStore.setField("Spring");
        System.out.println("integerStore.getField = " + integerStore.getField());

        // Integer integerString = (Integer) "Spring"; ?????
        Integer integerString = (Integer) integerStore.getField();
        System.out.println(integerString);

    }

}

// ClassCastException 발생!

 

하위 클래스는 상위 클래스를 참조할 수없다.

 

Object타입이기에

Integer타입, Long타입, String타입을 참조해도

컴파일 에러가 발생하지 않는다.

 

하지만 

해당 값을 출력할 때는 해당값이 저장되지 않는 문제가 발생한다.

 

 

타입 안전성에 대한 문제를 해결하는 방법은

제네릭을 활용함으로 가능할 수 있다.

 


제네릭은

특정 타입에 제한되지 않고 범용적으로 사용할 수 있다.

<> 을 사용한것이 제네릭이며

제네릭 클래스는 Type을 미리 결정하지 않는다.

생성하는 시점에 Type을 결정한다.

public class GenericStore<T> {

    private T field;

    public void setField(T field) {
        this.field = field;
    }

    public T getField() {
        return field;
    }

}
public class Main {

    public static void main(String[] args) {

        // T의 Type이 결정되는 순간
        GenericStore<Integer> integerStore = new GenericStore<Integer>();

        // Compile 오류 발생
        integerStore.setField("Sparta");

        integerStore.setField(1000);
        System.out.println("integerStore.getField() = " + integerStore.getField());

        // integerStore를 new 하는 순간 Integer로 생성되어있다.
        // 즉, 캐스팅이 필요없다.
        Integer integer = integerStore.getField();
        System.out.println(integer);

        // 생성하는 뒷부분의 <> 안의 타입은 생략이 가능하다.
        GenericStore<String> stringStore = new GenericStore<>();
        stringStore.setField("spring");
        System.out.println("stringStore.getField() = " + stringStore.getField());
        
        String string = stringStore.getField();
        System.out.println(string);

        // Long, Double, Obejct 등등..

    }

}

 

GenericStore<T>는 명시된 타입이 없기때문에

 

Main클래스에서 생성시점에

Integer타입으로 결정되어 

 

integerStore.setField("Sparta")는

컴파일 에러가 발생한다(문자열은 저장될 수 없다)

 

 

GenericStore를 생성하고 <Integer>으로 선언하게 됨으로

GenericStore<Integer>로 변환하는것이고

 

GenericStore를 생성하고 <String>으로 선언하게 됨으로

GenericStore<String>으로 변환하여 사용되는것

 

컴파일에 T에 대한 정보를 작성한 코드에 맞추어 적용한다.

(타입이 불일치하면 위처럼 컴파일 에러가 발생한다)

 

생성 부분에 <>타입은 생략할 수있다.
GenericStore<Integer> integerStore = new GenericStore<>();

 


 

제네릭은 여러개의 타입을 선언할 수 있다.

public class KeyValueType<K, V> {
    ...
}

class MultipleType<T, U> {
    ...
}

 

제네릭 키워드 대신에 다른것들이 들어갈 수는 있으나

규칙이 있고

대문자를 사용해야하며

키워드 첫글자를 사용해야한다.

 

E => Element

K => Key

N => Number

T => Type

V => Value

 

S, U, V 

Type이 여러개인 경우라면 각각

Second, Third, Fourth

 


 

타입을 지정하지 않아도된다.

GenericStore stringStore = new GenericStore();

타입을 지정하지 않으면 T가 Object로 지정되지만

현재는 사용하지 않아야된다. 

 


public class Fruit {

    private String name;
    private int size;

    public Fruit(String name, int size) {
        this.name = name;
        this.size = size;
    }

    public String getName() {
        return name;
    }

    public int getSize() {
        return size;
    }

    public void sayColor() {
        System.out.println("나는 무색이야");
    }

}
public class Grape extends Fruit {

    public Grape(String name, int size) {
        super(name, size);
    }

    @Override
    public void sayColor() {
        System.out.println("나는 보라색이야");
    }

}
public class Grape extends Fruit {

    public Grape(String name, int size) {
        super(name, size);
    }

    @Override
    public void sayColor() {
        System.out.println("나는 보라색이야");
    }

}
public class FruitShop {

    private Fruit fruit;

    public void setFruit(Fruit fruit) {
        this.fruit = fruit;
    }

    public Fruit getFruit() {
        return fruit;
    }

    public void advertise() {
        System.out.println("맛있는 " + fruit.getName() + " 팝니다.");
        System.out.println("크기는 " + fruit.getSize() + " 입니다.");
        fruit.sayColor();
    }

    // 사이즈가 큰 것은 반환한다.
    public Fruit compareSize(Fruit compareFruit) {

        if (fruit.getSize() > compareFruit.getSize()) {
            return fruit;
        }

        return compareFruit;
    }

}

 

 

Apple과 Grape는 Fruit을 상속받고있다.

FruitShop appleShop = new FruitShop();
FruitShop grapeShop = new FruitShop();
public class FruitShop {

    private Fruit fruit;

    public void setFruit(Fruit fruit) {
        this.fruit = fruit;
    }

    public Fruit getFruit() {
        return fruit;
    }

    public void advertise() {
        System.out.println("맛있는 " + fruit.getName() + " 팝니다.");
        System.out.println("크기는 " + fruit.getSize() + " 입니다.");
        fruit.sayColor();
    }

 

FruitShop클래스안에 setFruit가 있고

Input값으로 fruit를 받으며

 

Fruit(부모클래스)를 상속받는 Apple과 Grape는

fruit에 데이터를 입력하여 출력이 가능하다.

Apple apple = new Apple("사과", 1);
Grape grape = new Grape("포도", 2);

위와 같이 Apple(사과,1 )과 Grape(포도,2)는 

appleShop.setFruit(apple);
appleShop.advertise();

grapeShop.setFruit(grape);
grapeShop.advertise();

위와같이 appleShop에 setFruit를 하게되면

 

appleShop.advertise();

grapeShop.advertise();

 

위 두개의 메소드가 실행되어 위처럼 출력되는 것을 확인할 수 있다.

// 사과 가게인데 포도를 가질 수 있다.
 appleShop.setFruit(grape);
 appleShop.advertise();

또한 apple과 grape의 데이터

String과 int타입이기에 타입이 일치하므로 

 

호환도 가능하다.


public class FruitShop<T extends Fruit> {

    private T fruit;

    public void setFruit(T fruit) {
        this.fruit = fruit;
    }

    public T getFruit() {
        return fruit;
    }

    public void advertise() {
        System.out.println("맛있는 " + fruit.getName() + " 팝니다.");
        System.out.println("크기는 " + fruit.getSize() + " 입니다.");
        fruit.sayColor();
    }

    // 사이즈가 큰 것은 반환한다.
    public T compareSize(T compareFruit) {

        if (fruit.getSize() > compareFruit.getSize()) {
            return fruit;
        }

        return compareFruit;
    }

}

 

 

제네릭을 사용하여 T를 선언하고 extends Fruit을 하게되면 

 

다형성으로 인해서 Fruit과 그 하위 class만 받을 수 있고

Fruit의 메서드들을 사용할 수 있게되며

타입 안전성으로 appleShop에 grape를 넣을 수 없게된다.

제네릭타입은 생성시점에 타입이 지정되며 다운 캐스팅이 필요없다(형변환)

 

FruitShop<Apple> appleShop = new FruitShop();
FruitShop<Grape> grapeShop = new FruitShop();

Apple apple = new Apple("사과", 1);
Grape grape = new Grape("포도", 2);

appleShop.setFruit(apple);
appleShop.advertise();

grapeShop.setFruit(grape);
grapeShop.advertise();

 

위처럼 다른 타입을 넣게되면 컴파일 오류가 발생한다 

그 이유는 

Grape grape = new Grape("포도", 2);

grape는 Grape클래스로 명시가 되어있는데.

FruitShop<Apple> appleShop = new FruitShop();

위처럼 정의되있는 appleShop에 

// 다른 타입을 넣게되면 컴파일 오류 발생
appleShop.setFruit(grape);

Grape로 정의된 grape를 넣으려고 하면 오류가 발생한다.

 

하지만

Apple grape = new Apple("포도", 2);

이렇게하면 오류가 발생하지 않게 할 수는 있다. 

 


제네릭 메서드

접근제어자 + 반환타입 + 메서드 이름 + (매개 변수) { 구현 코드 }

public class GenericMethod {

    public static <T> T genericMethod(T t) {

        System.out.println("T 출력 : " + t);

        return t;
    }

    public static <T extends Number> T numberGenericMethod(T t) {

        System.out.println("number T 출력 : " + t);

        return t;
    }

}
public class Main {

    public static void main(String[] args) {

        Long value = 1L;

        // 타입을 명시한다.
        Long longValue = GenericMethod.<Long>genericMethod(value);
        Integer integerValue = GenericMethod.<Integer>numberGenericMethod(10);

    }

}

 

위처럼

longValue는

GenericMethod.<Long>genericMethod(value)를 해주면

1이 출력이되고

 

Integer integerValue는

GenericMethod.<Integer>numberGenericMethod(10)을 하면 

10이 출력되는것을 확인할 수 있다.

위처럼 제네릭 메소드를 사용할 수 있다.

 


genericMethod는 String 타입도 가능하다

하지만 numberGenericMethod 같은 경우

Number를 상속받고있어

Number의 하위 클래스만 사용할 수 있다.

 

public static void main(String[] args) {

    Long value = 1L;

    // 타입을 생략한다.
    Long longValue = GenericMethod.genericMethod(value);
    Integer integerValue = GenericMethod.numberGenericMethod(10);

}

위처럼 타입을 생략해도 사용할 수 있다.

 

컴파일러가 자동으로 넣어준다 생각하면 된다.

 


우선순위 

static 메서드는 제네릭 메서드만 적용할 수 있다.

 

new로 생성하여 사용하는

일반 메서드는 제네릭타입과 제네릭 메서드 모두 적용할 수 있다.

// 제네릭 타입 설정
public class FruitBox<T extends Fruit> {

    private T fruit;

    public void setFruit(T fruit) {
        this.fruit = fruit;
    }

    // 제네릭 메서드 설정
    public <T> T getClassName(T t) {
        System.out.println("fruit class" + fruit.getClass().getName());
        System.out.println("t class" + t.getClass().getName());
        return t;
    }

}


public class Main {

    public static void main(String[] args) {

        Apple apple = new Apple("사과", 1);
        Grape grape = new Grape("포도", 2);

        FruitBox<Apple> appleBox = new FruitBox<>();
        appleBox.setFruit(apple);

        Grape grape2 = appleBox.getClassName(grape);

    }

}

제네릭 메서드가 우선순위를 가진다

(구체적으로 선언된것이 우선순위를 가진다)

클래스 -> 메서드 

 

제네릭 클래스의 T는 extends Fruit으로 제한을 두었고

제네릭 메서드의 T는

제한을 두지 않았기 때문에 Object로 취급된다.

.getClassName(grape)는 Object메서드 이기에 사용할 수 있다.

 

위와같이 메소드와 클래스의 T가 겹칠경우 
public <S> S getClassName(S s) {

    System.out.println("fruit class" + fruit.getClass().getName());
    System.out.println("s class" + s.getClass().getName());
    return s;

}

다른이름을 사용해야한다.

 


제네릭에서 사용하는 와일드 카드

<?> 키워드를 사용한다

와일드 카드는 제네릭 타입을 활용할 때 사용한다.

https://inpa.tistory.com/entry/JAVA-%E2%98%95-%EC%A0%9C%EB%84%A4%EB%A6%AD-%EC%99%80%EC%9D%BC%EB%93%9C-%EC%B9%B4%EB%93%9C-extends-super-T-%EC%99%84%EB%B2%BD-%EC%9D%B4%ED%95%B4

 

☕ 자바 제네릭의 공변성 & 와일드카드 완벽 이해

자바의 공변성 / 반공변성 제네릭의 와일드카드를 배우기 앞서 선수 지식으로 알고 넘어가야할 개념이 있다. 조금 난이도 있는 프로그래밍 부분을 학습 하다보면 한번쯤은 들어볼수 있는 공변

inpa.tistory.com

public class Shop<T> {

    private T product;

    public void setProduct(T product) {
        this.product = product;
    }

    public T getProduct() {
        return product;
    }

}

public class WildCard {

    // 제네릭 메서드 사용
    static <T> void genericMethod(Shop<T> shop) {
        System.out.println("T Shop Product = " + shop.getProduct());
    }

    // ? 와일드 카드 사용
    static void wildCardGenericMethod(Shop<?> shop) {
        System.out.println("? : " + shop.getProduct());
    }

}

 

제네릭 메서드는 

타입 추론에 의해서 T는 전달받은 값이 되는것이며

 

<?>와일드 카드를 사용하면

Shop과 관련된 모든 타입을 T처럼 받을 수 있다.

<? extends Object>와 같다.

 

이미 정해진 제네릭 타입을 활용할 때 쓴다.

와일드 카드를 사용하면 제네릭 메서드가 아니다.

일반 메서드이며 모든 타입을 받을 수 있다.

 

대신에 타입 추론 과정은 복잡하다.(가독성)

 

와일드 카드를 사용하면 단순하게 동작하고

보통 제네릭메서드가 아니라 와일드 카드를 사용한다

 

public class WildCard {

    // extends + 제네릭 메서드 사용
    static <T extends Fruit> void genericMethod(Shop<T> shop) {
        System.out.println("T Shop Product = " + shop.getProduct());
    }

    // extends + 와일드 카드 사용
    static void extendsGenericMethod(Shop<? extends Fruit> shop) {
        Fruit fruit = shop.getProduct();
        System.out.println("? extends Fruit : " + fruit.getName());
    }

}

와일드 카드도 위처럼

extends Fruit으로 제한을 설정할 수 있고

 

다른타입이 입력되면 컴파일 오류가 발생한다.

제네릭타입처럼 Fruit class 기능을 사용할 수 있다.

 

public class WildCard {

    // T 를 반환
    static <T extends Fruit> T genericMethod(Shop<T> shop) {
        T t = shop.getProduct();
        System.out.println("T Shop Product = " + shop.getProduct());
        return t;
    }

    // Fruit 타입을 반환, 전달할 타입을 명확하게 할 수 없다.
    static Fruit extendsGenericMethod(Shop<? extends Fruit> shop) {
        Fruit fruit = shop.getProduct();
        System.out.println("? extends Fruit : " + fruit.getName());
        return fruit;
    }

}



public class Main {

    public static void main(String[] arts) {

        Apple apple = new Apple("사과", 1);
        Grape grape = new Grape("포도", 2);

        Shop<Apple> appleShop = new Shop();
        Shop<Grape> grapeShop = new Shop();

        appleShop.setProduct(apple);
        grapeShop.setProduct(apple);

        // T를 반환
        Apple returnApple = Wildcard.genericMethod(appleShop);
        Grape returnGrapeb = Wildcard.genericMethod(grapeShop);

        // ? extends Fruit의 경우 Fruit을 반환한다.
        Fruit apple2 = Wildcard.extendsGenericMethod(appleShop);
        Fruit grape2 = Wildcard.extendsGenericMethod(grapeShop);

        // 즉, 형변환이 필요하다.
        Apple apple2 = (Apple) Wildcard.extendsGenericMethod(appleShop);
        Grape grape2 = (Grape) Wildcard.extendsGenericMethod(grapeShop);

    }
}

메서드의 타입을 실행 시점에 변경하려면

제네릭타입, 제네릭 메서드를 사용한다.

 

컴파일러가 런타임 전에 자동변경하여 적용해준다.

결국에 제네릭은 개발자의 편의에 의해 만들어진것이며
결국은 컴파일러가 자동 변환해서 사용하는것.

 

꼭 필요한 상황에서만 <T>를 사용하고

나머지는 와일드 카드를 사용한다

형변환이 필요한경우에만

 

형변환이 필요한 경우는 <T>

형변환이 필요없는 경우 <?>

와일드 카드를 사용한다.


<? super 하위클래스> 를 하게되면

위처럼 하위클래스를 제외한 상위클래스를 사용할 수 있다.

public class Main {

    public static void main(String[] arts) {

        Shop<Object> objectShop = new Shop();
        Shop<Fruit> fruitShop = new Shop();
        Shop<Apple> appleShop = new Shop();
        Shop<Grape> grapeShop = new Shop();

        // Fruit 이상의 상위 클래스 허용
        init(objectShop);
        init(fruitShop);

        // 불가능
        init(appleShop);
        init(grapeShop);

    }

    static void init(Shop<? super Fruit> shop) {
        shop.setProduct(new Apple("사과", 10));
    }
}

 


Eraser

컴파일 이전코드는 개발자가 직접 작성한 코드이며

컴파일 이후는

컴파일러가 컴파일 직전에 자동으로 추가하여 실행한것이다

// 컴파일 이전
// 상위 클래스 제한
public class FruitBox<T extends Fruit> {

    private T fruit;

    public void setFruit(T fruit) {
        this.fruit = fruit;
    }

    public T getFruit() {
        return fruit;
    }

}


public class Main {

    Apple apple = new Apple("사과", 1);
    Grape grape = new Grape("포도", 2);

    FruitBox<Apple> appleBox new FruitBox<>();
       appleBox.set(apple);

    Apple apple = appleBox.getFruit();

}

------------------------------------------------------

// 컴파일 이후
public class FruitBox<Apple extends Fruit> {

    private Apple fruit;

    public void setFruit(Apple fruit) {
        this.fruit = fruit;
    }

    public Apple getFruit() {
        return fruit;
    }

}

public class Main {

    Apple apple = new Apple("사과", 1);
    Grape grape = new Grape("포도", 2);

    FruitBox appleBox new FruitBox<>();
       appleBox.set(apple);

    Apple fruit = (Apple) appleBox.getFruit();

}

T를 Apple로 변경했다고 보면된다.

Compile이 완료된 후

Runtime때 Generic과 관련된 정보를 삭제하는것이

Eraser이다.

 

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

https://hs-backend.tistory.com/143

 

스파르타 부트캠프 Java 제네릭(Generics)

public class Basket { private List items = new ArrayList(); public void addItem(T item) { items.add(item); } public T getItem(int index) { return items.get(index); } public List getAllItems() { return items; }}public class Main { public static void main(St

hs-backend.tistory.com

반응형