제네릭스 심화

황상익·2023년 12월 15일

열혈 자바

목록 보기
19/30
제네릭 클래스와 상속 
제네릭 클래스도 상속이 가능하다. 

class Box{
protected T ob;
public void set(T o){
ob = o;
}

public T get(){
    return ob;
}

}

class SteelBox extends Box{
public SteelBox(T o){
ob = o;
}
}

public class Main1 {
public static void main(String[] args) {
Box iBox = new SteelBox<>(7959);
Box sBox = new SteelBox<>("simple");
System.out.println(iBox.get());
System.out.println(sBox.get());
}
}


제네릭 상속을 설명하기 위해 
class SteelBox<T> extends Box<T>{
    public SteelBox(T o){
        ob = o;
    }
}

두 제네릭스가 상속 관계를 구성하면
Box<Integer> iBox = new SteelBox<>(7959);
Box<String> sBox = new SteelBox<>("simple");
매개변수 타입 또는 제네릭 타입 
Box<Integer> iBox
Box<String> sBox 
타입이란 단어가 포함된 것은 Box<Integer>를 일종의 자료형으로 정확히는 클래스 이름을 간주함. -> 상속 관계가 형성 될 수 있다. 

하지만 
Box<Number> box = new Box<Integer>는 상속 관계 성립 X 
Number를 Integer가 상속하지만, Box<Number> 와 Box<Integer>는 상속관계 성립 하지 않는다.

타겟 타입 

class Box1{
private T ob;
public void set(T o){
ob = o;
}

public T get() {
    return ob;
}

}

class EmptyBoxFactory{
public static Box1 makeBox(){
Box1 box = new Box1<>();
return box;
}
}

public class Main2 {
public static void main(String[] args) {
Box1 ibox = EmptyBoxFactory.makeBox();
ibox.set(25);
System.out.println(ibox.get());
}
}

제네릭 메소드 정의
public static <T> Box1<T> makeBox(){
    Box1<T> box = new Box1<>();
    return box;
}
 
Box1 클래스의 makeBox 메소드와 달리 인자를 전달 받지 않는다. 따라서 compiler로 T 유추 불가능. 따라서 T에 관한 인자를 전달해줘야 한다.
Box1<Integer> ibox = EmptyBoxFactory.<Integer>makeBox();
makeBox 메소드는 Box<Integer> 인스턴스의 참조 값을 반환해야 한다고 판단 할 수 있다, 따라서 makeBox 메소드 호출시, T는 Integer가 되어야 한다는 것을 알 수 있다. T의 유추 정보를 타겟 타입이라고 한다. 

와일드 카드

class Box2{
private T ob;

public void set(T o){
    ob = o;
}

public T get(){
    return ob;
}

@Override
public String toString(){
    return ob.toString();
}

}

class Unboxer{
public static T openBox(Box2 box2){
return box2.get();
}

public static <T> void peekBox(Box2<T> box){
    System.out.println(box);
}

public static void peekBox2(Box2<?> box){
    System.out.println(box);
}

}

public class Main3 {
public static void main(String[] args) {
Box2 box = new Box2<>();
box.set("simple string");
Unboxer.peekBox(box);
}
}


상자의 내용물들을 반환하지 않고, 무엇이 들었는지만 확인 할 정도 
public static <T> void peekBox(Box2<T> box){
    System.out.println(box);
}
Box<Integer> Box<String>의 인스턴스를 인자로 받게 하기 위함 
Box<Object> 와 Box<String>/Box<Integer>는 상속 관계 형성하지 않는다. 




class Box3<T>{
    private T ob;

    public void set(T ob){
        ob = ob;
    }

    public T get(){
        return ob;
    }

    @Override
    public String toString(){
        return ob.toString();
    }
}

class Unboxer2{
    public static void peekBoxt(Box3<? extends Number> box3){
        System.out.println(box3);
    }

    public static void peekBox(Box3<? super Integer> box4){
        System.out.println(box4);
    }
}

public class Main4 {
    public static void main(String[] args) {
        Box3<Integer> box = new Box3<>();
        box.set(1234);

        Box3<Number> box3 = new Box3<>();
        box3.set(new Integer(9995));

        Box3<Object> box4 = new Box3<>();
        box4.set("My Simple Instance");

        Box3<Double> box1 = new Box3<>();
        box1.set(1.12);

        Unboxer2.peekBoxt(box);
        Unboxer2.peekBoxt(box1);
        Unboxer2.peekBox(box3);
        Unboxer2.peekBox(box4);
    }
}
와일드 카드 라는 것을 사용하면 상속 관계 성립 시킬 수 있다.
public static void peekBoxt(Box3<? extends Number> box3){
        System.out.println(box3);
    }

    public static void peekBox(Box3<? super Integer> box4){
        System.out.println(box4);
    }
}
Box<T>로 기반으로 생성된 Box<Integer> 인스턴스나 Box<String> 인스턴스들을 인자로 받는다
코드가 조금 더 간결하다는 이유로 와일드카드 기반 메소드를 더 선호 

와일드카드의 상한선 과 하한선 
상한 제한, 하한 제한을 문법적 측면에서 보면, 
public static void peekBox2(Box2<?> box){
    System.out.println(box);
}
Box<T>에서 T가 Number 또는 Number의 하위 클래스인 제네릭 타입의 인스턴스만 전달. 제한할 때 상한 제한된 와일드카드라는 것을 사용 
Box <? Extends Number> Java
	Box<T> 인스턴스를 참조하는 참조변수이다. 
	이때 Box<T> 인스턴스의 T는 Number 또는 이를 상속하는 하위 클래스이어야 한다. 
public static void peekBox(Box3<? super Integer> box4){
    System.out.println(box4);
}
peekBox의 매개 변수에 제한을 걸어 Box<Integer>, Box<Double>과 제네릭 타입의 인스터스만 인자로 전달.
하한 제한된 와일드카드 
Box<? Super Integer> 
	Box<T> 인스턴스 T의 Integer 또는 Integer가 상속하는 클래스 이어야 한다. 

언제 와일드카드에 제한을 걸어야 할까?
Box<T>의 T를 Number 또는 Number를 직접적으로 상속하는 클래스로 제한하기 위한 것 /
Box<T>의 T를 Integer 또는 Integer가 직간접으로 상속하는 클래스로 제한 
Public static<T> void copy(List<? Super T>, dest, List<? Extends T src)
	Collections 클래스의 복사 메소드

상한제한의 목적
class Box5<T>{
    private T ob;
    public void set(T ob){
        ob = ob;
    }

    public T get(){
        return ob;
    }
}

class Toy {
    @Override
    public String toString(){
        return "I'm Toy";
    }
}

class BoxHandler{
    public static void outBox(Box5<? extends Toy> box){
        Toy t = box.get();
        System.out.println(t);
    }

    public static void inBox(Box5<? super Toy> box1, Toy n){
        box1.set(n);
    }
}

public class Main5 {
    public static void main(String[] args) {
        Box5<Toy> box = new Box5<>();
        BoxHandler.inBox(box, new Toy());
        BoxHandler.outBox(box);
    }
}
	필요한 만큼만 기능을 허용하여, 코드의 오류가 컴파일 과정에서 최대한 발견 
	Box를 대상으로 get 또는 set 호출도 가능
	Box.set(new Toy()) 넣는 것도 ok
단 set의 빈번한 이용은 하지 않는게 좋다. 오류 발생 가능성이 있다. = 필요한 만큼만 매개변수 선언. 상자에서 꺼내는 것은 가능,  수정을 불가능.
Set 메소드의 호출이 불가능 한 이유는 Toy 인스턴스를 저장할 수 있는 상자만 인스턴스만 전달된다. 
상속관계를 맺으면 outBox 메소드에 Box<car> 또는 Box<robot> 인스턴스가 인자로 전달 가능. 
public static void outBox(Box5<? extends Toy> box){
    Toy t = box.get();
    System.out.println(t);
}
여기서 box.set(new Toy()) -> Error
따라서 Toy 인스턴스를 저장하는 메소드 호출은 불가능함.

class Box5<T>{
    private T ob;
    public void set(T ob){
        ob = ob;
    }

    public T get(){
        return ob;
    }
}

class Toy {
    @Override
    public String toString(){
        return "I'm Toy";
    }
}

class BoxHandler{
    public static void outBox(Box5<? extends Toy> box){
        Toy t = box.get();
        System.out.println(t);
    }

    public static void inBox(Box5<? super Toy> box1, Toy n){
        box1.set(n);
    }
}

public class Main5 {
    public static void main(String[] args) {
        Box5<Toy> box = new Box5<>();
        BoxHandler.inBox(box, new Toy());
        BoxHandler.outBox(box);
    }
}

하한제한의 목적
필요한기능 만큼 허용, 코드 오류가 컴파일 과정에서 최대한 발견 
상자에 인스턴스를 저장하는 것이 목적, get 메소드를 호출하는 코드가 삽입된다면 프로그래머의 실수 
public static void outBox(Box5<? extends Toy> box){
        Toy t = box.get();
        System.out.println(t);
    }

    public static void inBox(Box5<? super Toy> box1, Toy n){
        box1.set(n);
    }
}
	get 매소드 호출문에서 컴파일 오류가 발생. 
	반환형을 Toy로 결정 할 수 없기 떄문. 
따라서 box가 참조하는 인스턴스를 대상으로 꺼내는 기능의 메소드 호출은 불가능하다. 

와일드카드를 언제 제한을 걸어야 하는가?
매개변수 선언
	Box<? Extends Toy> box
Box가 참조하는 인스턴스를 대상으로 꺼내는 작업만 허용하겠다는 의미
	Box<? Super Toy> box
Box가 참조하는 인스턴스를 대상으로 넣는 작업만 허용하겠다는 의미 
class Box6<T>{
    private T ob;

    public void set(T o){
        ob = o;
    }

    public T get(){
        return ob;
    }
}

class Toy1{
    @Override
    public String toString(){
        return "Toy";
    }
}

class BoxContentMover{
    public static void moveBox(Box6<? super Toy1> to, Box6<? extends Toy1> from){
        to.set(from.get());
    }
}

public class Main6 {
    public static void main(String[] args) {
        Box6<Toy1> box1 = new Box6<>();
        box1.set(new Toy1());
        Box6<Toy1> box2 = new Box6<>();

        BoxContentMover.moveBox(box2, box1);
        System.out.println(box2.get());
    }
}


from으로 전달된 상자의 내용물을 to로 전달된 상자로 옮긴다. 
public static void moveBox(Box6<? super Toy1> to, Box6<? extends Toy1> from){
        to.set(from.get());
    }
}
	From.set(to.get()) -> 프로그램 오류
<문제>
class Boxt<T>{
    private T ob;

    public void set(T ob){
        ob = ob;
    }

    public T get(){
        return ob;
    }
}

class BoundedWildcardDemo{
    public static void addBox(Boxt<? super Integer> b1, Boxt<? extends Integer> b2, Boxt<? extends Integer> b3){
        b1.set(b2.get() + b3.get());
    }
}

public class Main7 {
    public static void main(String[] args) {
        Boxt<Integer> box = new Boxt<>();
        box.set(24);

        Boxt<Integer> box1 = new Boxt<>();
        box1.set(37);

        Boxt<Integer> result = new Boxt<>();
        result.set(0);
        BoundedWildcardDemo.addBox(result, box, box1);
        System.out.println(result.get());
    }
}






제한된 와일드카드 선언을 갖는 제네릭 메소드 
Toy 클래스를 담은 상자 기준을 InBox와 outbox 메소드를 정의 

class Box7<T>{
    private T ob;
    public void set(T o){
        ob = o;
    }

    public T get(){
        return ob;
    }
}

class Toy2{
    @Override
    public String toString(){
        return "Toy";
    }
}

class Robot{
    @Override
    public String toString(){
        return "robot";
    }
}

class BoxHandler2{
    public static <T> void outBox(Box7<? extends T> box7){
        T t = box7.get();
        System.out.println(t);
    }

    public static <T> void inBox(Box7<? super T> box7, T n){
        box7.set(n);
    }
}

public class Main8 {
    public static void main(String[] args) {
        Box7<Toy2> tBox = new Box7<>();
        BoxHandler2.inBox(tBox, new Toy2());
        BoxHandler2.outBox(tBox);

        Box7<Robot> rBox = new Box7<>();
        BoxHandler2.inBox(rBox, new Robot());
        BoxHandler2.outBox(rBox);
    }
}



public static <T> void outBox(Box7<? extends T> box7){
        T t = box7.get();
        System.out.println(t);
    }

    public static <T> void inBox(Box7<? super T> box7, T n){
        box7.set(n);
    }
}

class Robot{
    @Override
    public String toString(){
        return "robot";
    }
}
Box<Robot>의 인스턴스를 대상으로 outBox와 InBox 메소드를 호출.
< 메소드는 오버로딩 인정 안됨>
public static <T> void outBox(Box7<? extends T> box7){
    T t = box7.get();
    System.out.println(t);
}
	기술적 문제가 기인한다. 제네릭 등장 이전에 정의된 클래스들과 상호 호환성 유지를 위해 컴피일 시 제네릭과 와일드카드 관련 정보를 지우는 과정을 거친다. 즉 두 매개변수 선언은 컴파일 과정에서 다음과 같이 수정. 메소드의 오버로딩 성립 불가능 
	위와 같이 컴파일러가 제네릭 정보를 지우는 행위를 가리켜 Type Erasure라고 한ㄷ, 
< 메소드는 두번째 매개변수로 인해 오버로딩 인정 >
public static <T> void inBox(Box7<? super T> box7, T n){
        box7.set(n);
    }
}
	제네릭과 관련 없는 두 번째 매개변수의 자료형이 다르기 때문이다.







<문제>
class Box8<T>{
    private T ob;
    public void set(T o){
        ob = o;
    }

    public T get(){
        return ob;
    }
}

class BoundeWildcardGeneric{
    public static <T> boolean compBox(Box8<? extends T> box,  T com){
        T bc = box.get();;
        return bc.equals(com);
    }
    public static void main(String[] args) {
        Box8<Integer> box = new Box8<>();
        box.set(24);
        Box8<String> box1 = new Box8<>();
        box1.set("Poly");

        if (compBox(box, 25))
            System.out.println("상자 안에 25 적용");
        if (compBox(box1, "Moly"))
            System.out.println("상자안에 Moly 저장");

        System.out.println(box.get());
        System.out.println(box1.get());
    }
}

제네릭 인터페이스의 정의와 구현 
인터페이스 역시 제네릭 클래스와 마찬가지로 제네릭으로 정의 
interface Getable<T>{
    public T get();
}

class Box9<T> implements Getable<String>{//Getable<T>
    private T ob;
    public void set(T ob){
        ob = ob;
    }

    @Override
    public String get(){
        return ob.toString();
    }
}

class Toy3{
    @Override
    public String toString(){
        return "Toy";
    }
}

public class Main10 {
    public static void main(String[] args) {
        Box9<Toy3> box = new Box9<>();
        box.set(new Toy3());

        Box9<Toy3> gt = box;
        System.out.println(gt.get());
    }
}

class Box9<T> implements Getable<String>{
	Box<T> 클래스는 Getable <T> 인터페이스를 구현하는 형태 
Box9<Toy3> box = new Box9<>();
    box.set(new Toy3());

    Box9<Toy3> gt = box;
    System.out.println(gt.get());
}
Getable<T>형 참조변수로 Box<T>의 인스터스를 참조할 수 있다. 단 T를 대신할 자료형이 동일해야 한다. 

profile
개발자를 향해 가는 중입니다~! 항상 겸손

0개의 댓글