국비/Java

2023.03.10 31일차 Java

춘핑이 2023. 3. 10. 16:54

31일차

13제네릭

메소드에도 클래스에도 붙을 수있다.

13.4 제네릭의 특징

뭐가 들어오는지 상관없이 모든객체는 Object를 상속하니 Object의 메소드를 사용할수잇다.
Object의 메소드 안전하게 실행 가능하다는게 중요하다.
왜냐하면 Object는 모든 클래스의 상위클래스이니까

public class C07Bounded {
    public static void main(String[] args) {
        MyClass07<String> o1 = new MyClass07<>();
        o1.setItem("java");
        o1.printItem();

        MyClass07<Scanner> o2 = new MyClass07<>();
        o2.setItem(new Scanner(""));
        o2.printItem();

        MyClass07<Integer> o3 = new MyClass07<>();
        o3.setItem(99);
        o3.printItem();
    }
}

class MyClass07<T> {
    private T item;

    public void printItem() {
        String str = item.toString();
        int hashCode = item.hashCode();
        boolean equals = item.equals(null);
        System.out.println(str + hashCode + equals);
    }

    public T getItem() {
        return item;
    }

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

13.4.1 bounded type parameter

제네릭의 특정 타입만 제한한다면 그타입이 들어온다는 것을 보장할 수 있으니
그 타입의 메소드를 사용할 수 있다.

Nubmer의 메소드를 쓰고싶다고 가정해보자
그냥 T를쓰면 Object는 올거라는 보장을 받는데 Number가 올거라는 보장이없다.
그래서 사용하지 못하니 불필요하게 instance로 비교를 하고 사용해야한다.

if (item instanceof Number) {
    ((Number) item).intValue();
    ((Number) item).doubleValue();
}

대신 T extends Number를 하면 T의 타입을 특정지을 수 있다.
item이 Number또는 그 하위타입이므로 Number의 메소드를 실행할 수 있게 된다.

class MyClass08<T extends Number> {
    private T item;

    public MyClass08(T item) {
        this.item = item;
    }

    public void handleItem() {
        String str = item.toString();
        int hash = item.hashCode();
        boolean eq = item.equals(null);

        int i = item.intValue();
        double d = item.doubleValue();
    }
}

public class C08Bounded {
    public static void main(String[] args) {
        MyClass08<Integer> o1 = new MyClass08<>(99);
        o1.handleItem();

        MyClass08<Double> o2 = new MyClass08<>(3.14);
        o2.handleItem();

        //불가능
        //MyClass08<String> o3 = new MyClass08<>("java");
    }
}

13.5 제네릭 상속

만약 제네릭 타입을 상속받는다면 몇가지 규칙이 있다.

1.자식도 타입파라미터의 수만큼 상속필요

class Super09<T> {}
class Sub09<T> extends Super09<T> {}

class Super10<T, U> {}
class Sub10<T, U> extends Super10<T, U> {}

2.명시하면 자식엔 작성안해줘도됨

class Super11<T> {}
class Sub011 extends Super11<String> {}

class Super12<T, U> {}
class Sub12<T> extends Super12<T, String> {}

3.자식타입에서 필요하면 늘릴수도 있다.

class Super13<T> {}
class Sub13<T, U> extends Super13<T> {}

4.부모보다 상위타입으로 제한 불가능하다.

class Super14<T extends Number> {}
//class Sub14<T extends Object> extends Super14<T> {}
class Sub14<T extends Integer> extends Super14<T> {}

13.5.1 확인문제 4번

UtilExample이 작성되어잇을때 Pair와 그 자식만 들어갈 수있는 getValue메소드를 작성해라
1.받아야 되는 목록을 작성해두기
2.필요한 부분에 넣기
이 순서를 지키면 쉬운 문제인 듯하다.

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;
    }
}

public class ChildPair<K, V> extends Pair<K, V>{

    public ChildPair(K key, V value) {
        super(key, value);
    }
}

public class UtilExample {
    public static void main(String[] args) {
        Pair<String, Integer> pair = new Pair<>("홍길동", 35);
        Integer age = Util.getValue(pair, "홍길동");
        System.out.println(age);

        ChildPair<String, Integer> childPair = new ChildPair<>("홍삼원", 30);
        Integer childAge = Util.getValue(childPair, "홍삼순");
        System.out.println(childAge);

        /*
        OtherPair<String, Integer> otherPair = new OtherPair<>("홍삼원", 20);
        Integer otherAge = Util.getValue(otherPair, "홍삼원");
        System.out.println(childAge);
        */
    }
}

public class Util {
    //P K V 받아야하는것 3개 작성 V리턴타입 P , K는 각 파라미터로 
    public static <P extends Pair<K, V>, K, V> V getValue(P pair, K key) {
        if (key.equals(pair.getKey())) {
            return pair.getValue();
        }
        return null;
    }
}

13.6 와일드카드

API보는법 중 ? extends / ? super등등
String이 Object를 상속받고있지만
MyClass02<Object> o1 = new MyClass02<String>();하는 것은 불가능하다.
String의 상위타입이라는게 확정만 된다면? 넣을 수있다.

프로그램기준으로 값이 나가는게 out 들어오는게 in
값이 나갈때(out) super
<? super 클래스> T가 그 클래스 혹은 그 상위라는게 확실하다.
그래서 그 하위 타입 값을 넣는 것은 가능하다.

값이 들어올때(in) extends
<? extends Number>
NUmber또는 그 하위타입이니 넘버 또는 상위로 읽어오는게 가능하다.

public class C03Wildcard {
    public static void main(String[] args) {
        MyClass03<? super Number> o1 = null;
        o1 = new MyClass03<Number>();
        MyClass03<? super Number> o2 = new MyClass03<Object>();

        //o1.item = new Number(); 되는데 추상이라 안됨
        o1.item = new Integer(5);
        o2.item = new Double(5);

        MyClass03<? extends Number> o3 = new MyClass03<Number>();
        MyClass03<? extends Number> o4 = new MyClass03<Integer>();
        MyClass03<? extends Number> o6 = new MyClass03<Double>();

        Number n1 = o4.item;
        Object o5 = o4.item;
        //Integer i1 = o4.item; 불가능

        MyClass03<? extends Integer> o7 = new MyClass03<Integer>();
        Integer i1 = o7.item;
    }
}

13.7 와일드카드 파라미터

지역변수로 사용하는 경우는 별로 없고 파라미터에서 사용되는 경우가 많다.

public static void printItem(MyClass04<? extends Number> param) {
    //들어오는게 Number 또는 그 하위타입이니
    //Number또는 그 상위로 읽는 게 가능
    Number num = param.getItem();
    System.out.println(num.doubleValue() * num.doubleValue() );
}

public static void main(String[] args) {
    MyClass04<Number> o1 = new MyClass04<>();
    o1.setItem(5);
    printItem(o1);

    MyClass04<Integer> o2 = new MyClass04<>();
    o2.setItem(5);
    printItem(o2);

    MyClass04<Double> o3 = new MyClass04<>();
    o3.setItem(3.14);
    printItem(o3);
}

결론
api를 볼때 extends면 그것으로 값을 꺼내서 쓸수잇다고 이해하면된다.
super 어쩌구면 거기에 값을 넣을거라 생각하면된다.

13.8 와일드 카드 파라미터 예제

public class Course {
    //모든 사람이면 등록가능
    public static void registerCourse1(Applicant<?> applicant) {
        System.out.println(applicant.kind.getClass().getSimpleName() + "이(가) Course1을 등록함");
    }

    public static void registerCourse2(Applicant<? extends Student> applicant) {
        System.out.println(applicant.kind.getClass().getSimpleName() + "이(가) Course2을 등록함");
    }

    public static void registerCourse3(Applicant<? super Worker> applicant) {
        System.out.println(applicant.kind.getClass().getSimpleName() + "이(가) Course3을 등록함");
    }
}

public class GenericExample {
    public static void main(String[] args) {
        // 모든 사람이 신청가능
        Course.registerCourse1(new Applicant<Person>(new Person()));
        Course.registerCourse1(new Applicant<Worker>(new Worker()));
        Course.registerCourse1(new Applicant<Student>(new Student()));
        Course.registerCourse1(new Applicant<HighStudent>(new HighStudent()));
        Course.registerCourse1(new Applicant<MiddleStudent>(new MiddleStudent()));

        // extends Student -> 학생과 그하위 클래스(고등학생, 중학생)
        // 학생, 고등학생, 중학생 등록가능
        // Course.registerCourse2(new Applicant<Person>(new Person()));
        // Course.registerCourse2(new Applicant<Worker>(new Worker()));
        Course.registerCourse2(new Applicant<Student>(new Student()));
        Course.registerCourse2(new Applicant<HighStudent>(new HighStudent()));
        Course.registerCourse2(new Applicant<MiddleStudent>(new MiddleStudent()));

        // super Worker -> Worker혹은 그위 클래스(Person)
        // 직장인, 일반인만 등록가능
        Course.registerCourse3(new Applicant<Person>(new Person()));
        Course.registerCourse3(new Applicant<Worker>(new Worker()));
        //Course.registerCourse3(new Applicant<Student>(new Student()));
        //Course.registerCourse3(new Applicant<HighStudent>(new HighStudent()));
        //Course.registerCourse3(new Applicant<MiddleStudent>(new MiddleStudent()));
    }
}

9->16람다->15컬렉션->17스트림 11, 14(일부), 18,19 20장은 아주 나중에 컬렉션 배우려고 제네릭부터 빌드업중이다.

9. 중첩선언과 익명객체

nested class / anonymous class
특별한 클래스의 형태
로컬클래스, 익명객체가 가장 중요하다.

9.1 중첩클래스

인스턴스 멤버클래스 정적멤버클래스 로컬클래스 3가지 종류가 있다.
클래스 안에 필드 생성자 메소드 만드듯이 클래스를 작성할 수 있다.
많은 일은 아니지만 중첩된 클래스를 api에서 볼 수잇다.

9.2 인스턴스 멤버 클래스

클래스안에 필드 등을 사용하려면 그 객체의 인스턴스가 필요했듯이
바깥 클래스를 먼저 만들고 내부 클래스를 사용해야한다.

public class C01NestedClass {
    public static void main(String[] args) {
        MyClass01 o1 = new MyClass01();
        MyClass01.Nested01 o2 = o1.new Nested01();
    }
}

이렇게 중첩되어 있으면 클래스 내부에서 필드이던 메소드이던 생성해서 사용할 수 있다.

class MyClass02 {
    class Nested02 {
    }
    Nested02 item = new Nested02();

    void method1() {
        Nested02 local = new Nested02();
    }
}

9.3 정적 멤버 클래스

static키워드를 붙여 중첩 정적 멤버 클래스로 만들 수 있다.
인스턴스 중첩클래스는 바깥을 만들어야 사용가능했는데
정적중첩클래스는 바로 접근하면된다.

public class C03StaticNestedClass {
    public static void main(String[] args) {
        MyClass03.Nested03 o1 = new MyClass03.Nested03();
    }
}

9.3.1 확인문제 4

Car클래스 안쪽에 Tire와 Engine클래스가 중첩되어잇다. 사용법

public class Car {
    class Tire{}
    static class Engine{}
}

public class CarExample {
    public static void main(String[] args) {
        Car myCar = new Car();
        Car.Tire tire = myCar.new Tire();
        Car.Engine engine = new Car.Engine();
    }
}

외부에서 사용하는 일은 거의 없고 내부에서 사용하는 일은 가끔 있다
한번쓰고 버리는 용도.

9.4 로컬클래스

로컬클래스(local class)는 선언된 메소드 내에서만 사용가능하다.
왜 만드나? 딱 메소드 안에서만 사용하고 다른데서 사용할일이없을때 만든다.
많이보는데 16장람다식 배우고 나서 사용하는 경우가 많다.
이 코드 자체를 사용하는 일은 거의없다.

public class C04LocalClass {
    void method1() {
        class LocalClass1{}
        LocalClass1 o1 = new LocalClass1();
    }

    void method2() {
        //LocalClass1 o1 = new LocalClass1(); 불가능
    }
}

9.5 익명객체

이미 존재하는 클래스가 있다면 상속받아서 다른 클래스를 만들 수 있다.
MyClass01 o1 = new SubClass01();
그런데 이 과정을 생략하는 것이다.
상속과 인스턴스 구현이 한번에 일어나는 것이다.

객체 생성자옆에 {}을 이어서 작성한다.
MyClass02 o1 = new MyClass02() {클래스구현};
상속받은 클래스의 이름이 없는 것이다. 상위클래스의 하위타입이니 상위 타입에 넣을 수 있는것이다.

만약 본 클래스가 추상클래스이고 추상메소드가 있다면
익명클래스는 추상메소드를 재정의해야한다.

로컬클래스와 마찬가지로 한번만 사용하고 버릴거 같을때 사용하는 것이다.
한번만 사용하고 다시 사용안하기 때문에 따로 만드는게 필요가 없을 것 같아 만든다.

abstract class MyClass03 {
    abstract void method1();
}

public class C03Anonymous {
    public static void main(String[] args) {
        //상속과 인스턴스 생성 동시
        MyClass03 o1 = new MyClass03() {
            @Override
            void method1() {
                System.out.println("재정의한 메소드");
            }
        };
        o1.method1();
    }
}

풀어서 작성한다면 다음과 같다.
중첩으로 로컬클래스를 만들고 인스턴스객체를 만드는 것이다.

public class C04Anonymous {
    public static void main(String[] args) {
        class SubClass04 extends MyClass04{
            @Override
            void method1() {
                System.out.println("재정의 된 메소드");
            }
        }
        MyClass04 o1 = new SubClass04();
        o1.method1();
    }
}

9.5.1 매개변수 익명객체

매개변수에 익명객체를 넣는 과정을 하나씩 풀어보면 다음과 같다.
1.로컬클래스로 자식객체구현
2.익명자식객체구현
3.매개변수에 직접 생성하면서 익명자식객체구현

public class C06Anonymous {
    public static void main(String[] args) {
        class Sub06 extends MyClass06 {
            @Override
            void myMethod() {
                System.out.println("로컬 클래스 재정의1");
            }
        }

        MyClass06 o1 = new Sub06();
        method1(o1);

        MyClass06 o2 = new MyClass06() {
            @Override
            void myMethod() {
                System.out.println("로컬 클래스 재정의2");
            }
        };
        method1(o2);

        method1(new MyClass06() {
            @Override
            void myMethod() {
                System.out.println("로컬 클래스 재정의3");
            }
        });
    }

    public static void method1(MyClass06 param) {
        param.myMethod();
    }
}

9.5.2 확인문제 5번

Action인터페이스가 work()추상메소드를 가지고 잇을때 익명 구현객체를 만들어보기

public interface Action {
    public void work();
}

public class ActionExample {
    public static void main(String[] args) {
        Action action = new Action() {
            @Override
            public void work() {
                System.out.println("복사를 합니다.");
            }
        };
        action.work();
    }
}

9.5.3 확인문제 6번

AnonymousExample 클래스의 실행결과를 보고 Vehicle인터페이스의 익명구현객체를 필드와 로컬변수 초기값 그리고 메소드의 매개값으로 대입하기

public interface Vehicle {
    public void run();
}

public class Anonymous {
    Vehicle field = new Vehicle() {
        @Override
        public void run() {
            System.out.println("자전거가 달립니다.");
        }
    };

    void method1() {
        Vehicle localVar = new Vehicle() {
            @Override
            public void run() {
                System.out.println("승용차가 달립니다.");
            }
        };
        localVar.run();
    }

    void method2(Vehicle v) {
        v.run();
    }
}

public class AnonymousExample {
    public static void main(String[] args) {
        Anonymous anony = new Anonymous();
        anony.field.run(); //자전거

        anony.method1(); //승용차

        anony.method2(new Vehicle() {
            @Override
            public void run() {
                System.out.println("트럭이 달립니다.");
            }
        });
    }
}

2023.03.10 후기

와일드카드 파라미터 이해를 잘 못햇는데 해석을 들으니 쉽다.
값이 나갈때(out) super
<? super 클래스> T가 그 클래스 혹은 그 상위라는게 확실하다.
그래서 그 하위 타입 값을 넣는 것은 가능하다.

값이 들어올때(in) extends
<? extends Number>
NUmber또는 그 하위타입이니 넘버 또는 상위로 읽어오는게 가능하다.

우리가 직접 작성할일은 거의 없고 API를 읽을때 잘아는 느낌이 중요하다
api를 볼때 extends면 그것으로 값을 꺼내서 쓸수잇다고 이해하면된다.
super 어쩌구면 거기에 값을 넣을거라 생각하면된다.