2023_1_19_TIL

제네릭 없이 여러 객체를 저장하는 클래스 작성하기

// 제네릭 없이 사과와 연필을 저장할 수 있는 클래스 생성
class Apple {}
class Goods1 {
    private Apple apple = new Apple();
    public Apple getApple() {
        return apple;
    }
    public void setApple(Apple apple) {
        this.apple = apple;
    }
}

class Pencil {}
class Goods2 {
    private Pencil pencil = new Pencil();

    public Pencil getPencil() {
        return pencil;
    }

    public void setPencil(Pencil pencil) {
        this.pencil = pencil;
    }
}

public class ProblemBeforeGeneric {
    public static void main(String[] args) {
        Goods1 goods1 = new Goods1();
        goods1.setApple(new Apple());
        Apple apple = goods1.getApple();

        Goods2 goods2 = new Goods2();
        goods2.setPencil(new Pencil());
        Pencil pencil = goods2.getPencil();
    }
}

// Object를 사용해 다양한 객체 저장 -> 다운캐스팅 해야함
// 매우 불편(약한 타입 체크, 잘못된 캐스팅이여도 문법 오류 발생 안됨 Runtime때 발생 함)
class Apple{}
class Pencil{}

class Goods {
    private Object object;
    public Object getObject() {
        return object;
    }
    public void setObject(Object object) {
        this.object = object;
    }
}

public class Solution_UsingObject {
    public static void main(String[] args) {
        Goods goods1 = new Goods();
        goods1.setObject(new Apple());
        Apple apple = (Apple) goods1.getObject();

        Goods goods2 = new Goods();
        goods2.setObject(new Pencil());
        Pencil pencil = (Pencil) goods2.getObject();
    }
}
  • 그래서 필요한 것이 제네릭

제네릭 클래스와 제네릭 인터페이스 정의하기

  • 잘못된 캐스팅? -> 바로 오류 -> 강한 타입 체크

제네릭 클래스의 객체 생성

  • 제네릭 클래스 -> 객체 생성과정은 일반 클래스의 객체 생성과정과 비슷
  • 단 객체를 생성할 때, 제네릭 타입 변수에 실제 타입을 대입하는 것
  • 즉, 제네릭 클래스는 클래스를 정의하는 시점에 타입을 지정하는 것이 아니라 객체를 생성하는 시점에 타입을 지정 -> 하나의 제네릭 클래스로 다양한 타입의 객체를 저장 및 관리 할 수있는 객체를 생성할 수 있는 것
  • Object 처럼 다운 캐스팅 필요없음
// 제네릭 타입 변수 1개
class MyClass<T> {
    private T t;
    public T getT() {
        return t;
    }
    public void setT(T t) {
        this.t = t;
    }
}

public class SingleGenericArgument {
    public static void main(String[] args) {
        MyClass<String> mc1 = new MyClass<>();
        mc1.setT("안녕");
        System.out.println(mc1.getT());

        MyClass<Integer> mc2 = new MyClass<>();
        mc2.setT(123123123);
        System.out.println(mc2.getT());
    }
}

// 제네릭 타입 변수 2개
class KeyValue<K, V> {
    private K key;
    private V value;
    public K getKey() {
        return key;
    }
    public void setKey(K key) {
        this.key = key;
    }
    public V getValue() {
        return value;
    }
    public void setValue(V value) {
        this.value = value;
    }
}

public class TwoGenericArguments {
    public static void main(String[] args) {
        KeyValue<String, Integer> kv1 = new KeyValue<>();
        kv1.setKey("사과");
        kv1.setValue(1000);
        String key1 = kv1.getKey();
        int value1 = kv1.getValue();
        System.out.println("key: " + key1 + " value: " + value1);

        KeyValue<Integer, String> kv2 = new KeyValue<>();
        kv2.setKey(404);
        kv2.setValue("Not Found(요청한 페이지를 찾을 수 없습니다.)");
        int key2 = kv2.getKey();
        String value2 = kv2.getValue();
        System.out.println("key: " + key2 + " value: " + value2);

        KeyValue<String, Void> kv3 = new KeyValue<>();
        kv3.setKey("키 값만 사용");
        String key3 = kv3.getKey();
        System.out.println("key: " + key3);
    }
}

// 제네릭클래스를 사용한 다양한 객체의 저장
class Apple{}
class Pencil{}
class Good<T> {
    private T t;
    public T getT() {
        return t;
    }
    public void setT(T t) {
        this.t = t;
    }
}

public class Solution2_Generic {
    public static void main(String[] args) {
        Good<Apple> good1 = new Good<>();
        good1.setT(new Apple());
        Apple apple = good1.getT();

        Good<Pencil> good2 = new Good<>();
        good2.setT(new Pencil());
        Pencil pencil = good2.getT();

        Good<Apple> good3 = new Good<>();
        good3.setT(new Apple());
    }
}

제네릭 메소드의 정의와 호출

  • 클래스 전체를 제네릭으로 선언 대신, 일반 클래스 내부에 특정 메소드만 제네릭 선언 가능
  • 제네릭 메소드는 호출되는 시점에 실제 제네릭 타입을 지정
  • 입력매개변수를 보고 제네릭 타입 변수의 실제 타입을 예측 가능
class GenericMethods {
    public <T> T method1(T t) {
        return t;
    }
    public <T> boolean method2(T t1, T t2) {
        return t1.equals(t2);
    }
    public <K, V> void method3(K k, V v) {
        System.out.println(k + " " + v);
    }
}

public class GenericMethod {
    public static void main(String[] args) {
        GenericMethods gm = new GenericMethods();

        String str1 = gm.method1("안녕");
        String str2 = gm.method1("안녕");
        System.out.println(str1);
        System.out.println(str2);

        boolean boo11 = gm.method2(2.5, 2.5);
        boolean bool2 = gm.method2(2.5, 2.5);
        System.out.println(boo11);
        System.out.println(bool2);

        gm.method3("국어", 86);
    }
}

제네릭 메소드 내에서 사용할 수 있는 메소드

  • 제네릭 메소드를 정의하는 시점에서는 아직 어떤 타입이 입력될지 모름
  • 특정 타입에 포함되어 있는 메소드는 메소드를 정의하는 시점에 사용할 수 없다
  • 아직 결정되지 않은 제네릭 타입 T객체 내부에는 어떤 메소드 가능?
    • Object 타입의 메소드만 가능 -> 나중에 어떤 타입이 제네릭 타입 변수로 확정되어도, 항상 사용할 수 있는 메소드만 제네릭 메소드 내부에서 사용가능
    • 즉, 어떤 상황이든 제네릭 타입은 입력매개변수 객체 내부에서는 Object에게 상속받은 메소드만 활용 가능!
class A {
    public <T> void method(T t) {
    	// System.out.println(t.length()); 불가능!!! 
        System.out.println(t.equals("안녕"));
    }
}

public class AvailableMethodInGenericMethod {
    public static void main(String[] args) {
        A a = new A();
        a.method("안녕");
    }
}

제네릭 타입 범위 제한의 필요성

  • 제네릭 타입은 각각 객체를 생성할 때와 메소드를 호출할 때 제네릭 타입을 지정함 -> 다양한 타입 처리 가능
  • 특정 부분만 관리하는 클래스 생성하고 싶다면? -> 제네릭 타입으로 올 수 있는 실제 타입의 종류를 제한해야 함 -> 제네릭 타입의 범위 제한

제네릭 타입 범위 제한의 종류와 범위 제한 방법

  • 제네릭 클래스의 타입 제한
    • 접근지정자 class 클래스명<T extends 최상위클래스/인터페이스>
    • 제네릭 클래스의 객체를 생성할 때 제네릭 타입을 생략? -> 모든 타입의 최상위 클래스가 입력
class A{}
class B extends A {}
class C extends B {}
class D <T extends B> {
    private T t;
    public T get() {
        return t;
    }
    public void set(T t) {
        this.t = t;
    }
}

public class BoundedTypeOfGenericClass {
    public static void main(String[] args) {
        D<B> d2 = new D<>();
        D<C> d3 = new D<>();
        D d4 = new D();

        d2.set(new B());
        d2.set(new C());

        d3.set(new C());

        d4.set(new B());
        d4.set(new C());
    }
}
  • 제네릭 메소드의 타입 제한
    • 접근지정자 <T extends 최상위클래스/인터페이스명> T 메소드명(T t)
    • 타입을 제한하지 않을 때는 모든 탕비의 최상위 클래스 Object만 가능
    • 예시) <T extends String클래스> -> 최상위 타입 String객체 메소드 사용 가능
class A {
    public <T extends Number> void method1(T t) {
        System.out.println(t.intValue());
    }
}

interface MyInterface {
    public abstract void print();
}

class B {
    public <T extends MyInterface> void method1(T t) {
        t.print();
    }
}

public class BoundedTypeOfGenericMethod {
    public static void main(String[] args) {
        A a = new A();
        a.method1(5.8);

        B b = new B();
        b.method1(new MyInterface() {
            @Override
            public void print() {
                System.out.println("print() 구현");
            }
        });
    }
}
  • 메소드 매개변수일 때 제네릭 클래스의 타입 제한
class A{}
class B extends A{}
class C extends B{}
class D extends C{}

class Goods<T> {
    private T t;
    
    public T getT() {
        return t;
    }
    public void setT(T t) {
        this.t = t;
    }
}

class Test {
    void method1(Goods<A> g){}// A인 객체만 가능
    void method2(Goods<?> g){}// 모든 타입인 객체 가능
    void method3(Goods<? extends B> g){}// B or B '자식'의 클래스인 객체만 가능
    void method4(Goods<? super B> g){}// B or B '부모'의 클래스인 객체만 가능
}

public class BoundedTypeOfInputArguments {
    public static void main(String[] args) {
        Test t = new Test();

        t.method1(new Goods<A>());

        t.method2(new Goods<A>());
        t.method2(new Goods<B>());
        t.method2(new Goods<C>());
        t.method2(new Goods<D>());

        t.method3(new Goods<B>());
        t.method3(new Goods<C>());
        t.method3(new Goods<D>());

        t.method4(new Goods<B>());
        t.method4(new Goods<A>());
    }
}

제네릭 클래스의 상속

  • 부모클래스가 제네릭? -> 상속받은 자식클래스도 제네릭
    • 제네릭 타입 변수 전부 물려받음
    • 자식클래스 제네릭 타입 변수의 개수 -> 같거나 많거나
class Parent<T> {
    T t;
    public T getT() {
        return t;
    }
    public void setT(T t) {
        this.t = t;
    }
}

class Child1<T> extends Parent<T> {}
class Child2<T, V> extends Parent<T> {
    V v;
    public V getV() {
        return v;
    }
    public void setV(V v) {
        this.v = v;
    }
}

public class InheritanceGenericClass {
    public static void main(String[] args) {
        Parent<String> p = new Parent<>();
        p.setT("부모 제네릭 클래스");
        System.out.println(p.getT());

        Child1<String> c1 = new Child1<>();
        c1.setT("자식1 제네릭 클래스");
        System.out.println(c1.getT());

        Child2<String, Integer> c2 = new Child2<>();
        c2.setT("자식2 제네릭 클래스");
        c2.setV(100);

        System.out.println(c2.getV() + c2.getT());
    }
}

제네릭 메소드의 상속

  • 제네릭 메소드 또한 제네릭 클래스와 마찬가지(그대로 물려받는다!)
class Parent {
    <T extends Number> void print(T t) {
        System.out.println(t);
    }
}
class Child extends Parent{}

public class InheritanceGenericMethod {
    public static void main(String[] args) {
        Parent p = new Parent();
        p.print(10);

        Child c = new Child();
        c.print(304313);
    }
}
profile
현재 블로그 : https://jasonsong97.tistory.com/

0개의 댓글