스물여덟 번째 수업

정혅·2024년 3월 20일

더 조은 아카데미

목록 보기
33/76
post-thumbnail

오전문제

탐색의 기준 변경 문제

import java.util.Arrays;
class Person{
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return name + ": " + age;
}
}
class ArrayObjSearch {
public static void main(String[] args) {
Person[] ar = new Person[3];
ar[0] = new Person("Lee", 29);
ar[1] = new Person("Goo", 15);
ar[2] = new Person("Soo", 37);
}
}

  1. 위 탐색의 기준은 나이이다. 탐색의 기준이 이름이 되도록 예제를 수정해라
package com.test.memo;

import java.util.Arrays;

class Person implements Comparable<Person> {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return name + ": " + age;
    }

    @Override
    public int compareTo(Person o) {
        return name.compareTo(o.name);
    }

}

public class Practice2 {

    public static void main(String[] args) {
        Person[] ar = new Person[3];

        ar[0] = new Person("Cee", 29);
        ar[1] = new Person("Aoo", 15);
        ar[2] = new Person("Boo", 37);

        Arrays.sort(ar);

        int idx = Arrays.binarySearch(ar, new Person("Cee", 29));
        System.out.println(idx);
        System.out.println(ar[idx]);

    }
}


BigDecimal 활용 문제

  1. 프로그램 사용자로부터 두 개의 실수를 입력 받은 후, 두 실수의 차에 대한 절대값을 계산하여 출력하는 프로그램을 작성하자.
package com.test.memo;

import java.math.BigDecimal;
import java.util.Scanner;

public class Practice2 {

    public static void main(String[] args) {

        Scanner sc = new Scanner(System.in);
        System.out.print("첫번째 실수값: "); // BigDecimal이 문자열로 저장하니까
        String num1 = sc.nextLine();
        System.out.print("두번째 실수값: ");
        String num2 = sc.nextLine();

        BigDecimal b1 = new BigDecimal(num1);
        BigDecimal b2 = new BigDecimal(num2);

        BigDecimal result = (b1.subtract(b2));
        System.out.println(result.abs());
    }
}
  • 사용자에게 입력받는 값을 double로 저장했더니, 자릿수가 엄청 길어졌었다. BigDecimal이나 BigInteger는 문자열로 저장하기 때문에, 사용자의 값 또한 문자열로 저장하는 것이 좋다.
  1. 100000000000000000000 와 -99999999999999999999 를 각각 저장하고 두 수를 서로 더하고 곱한 결과를 출력하시오.
package com.test.memo;

import java.math.BigInteger;

public class Practice2 {

    public static void main(String[] args) {

        BigInteger num1 = new BigInteger("100000000000000000000");
        BigInteger num2 = new BigInteger("-99999999999999999999");

        System.out.println("두 수를 더한 결과: " + num1.add(num2));
        System.out.println("두 수를 곱한 결과" + num1.multiply(num2));
    }
}

  1. 실수 1.6과 0.1을 더한 값과 곱한 값을 각각 구하시오.
package com.test.memo;

import java.math.BigDecimal;

public class Practice2 {

    public static void main(String[] args) {

        BigDecimal num1 = new BigDecimal("1.6");
        BigDecimal num2 = new BigDecimal("0.1");

        System.out.println("더한 값: " + num1.add(num2));
        System.out.println("곱한 값: " + num1.multiply(num2));
    }
}


문자열 분리

  1. 컴퓨터의 현재시간을 기준으로, 1970년 1월1일 자정 이후로 지나온 시간을 밀리 초(1/1000초) 단위로 계산해서 반환하는 메소드는?

    • System.currentTimeMillis

  1. 매개변수 없는 난수 발생의 생성자의 원리는?
  • API 문서에서는 "Random의 생성자가 호출될 때, 이전에 호출될 때와 전혀 다른 씨드 값이 설정된다.

    • Math 클래스에 static으로 선언되어 있는 random 메소드를 이용해서 난수를 생성하는 방법도 존재한다.

    • Math 클래스에 정의되어 있는 random 메소드는 Random 클래스의 nextDouble 메소드와 마찬가지로 0.0 이상 1.0 미만의 double 형 난수를 반환한다.

      • 물론 실행할 때마다 새로운 씨드 값이 설정되기 때문에, 실행할 때마다 다른 유형의 난수가 생성된다.

  1. 다음 토큰 반환할 때 쓰는 메소드
  • nextToken()

  1. 반환할 토큰이 남았는 지 확인하는 메소드
  • hasMoreTokens()

  1. "11:22:33:44:55" 를 ":"를 구분자로 하여 각각의 토큰을 출력하시오.
package com.test.memo;

import java.util.StringTokenizer;

public class Practice2 {

    public static void main(String[] args) {

        String time = "11:22:33:44:55";

        StringTokenizer st = new StringTokenizer(time, ":");
        while (st.hasMoreTokens()) {
            System.out.println(st.nextToken());
        }
    }
}
  • 아무 생각없이 st를 바로 출력했더니 참조값 주소가 나왔다.

    • hasMoreTokens()를 이용해서 반복문 돌려서 토큰을 차례로 출력해줘야 한다.

  1. "TEL 82-02-997-2059"를 " "을 구분자로 하여 각각의 토큰을 출력하시오.

    "TEL 82-02-997-2059"를 " -"을 구분자로 하여 각각의 토큰을 출력하시오.
    "num+=1"를 "+="을 구분자로 하여 각각의 토큰을 출력하시오. 구분자도 토큰으로 인식하도록하여 출력하자.

package com.test.memo;

import java.util.StringTokenizer;

public class Practice2 {

    public static void main(String[] args) {

        String time = "TEL 82-02-997-2059";
        String num = "num+=1";

        StringTokenizer first = new StringTokenizer(time);// 공백을 구분자로
        StringTokenizer twice = new StringTokenizer(time, " -"); // 공백과 하이폰을 구분자로
        StringTokenizer third = new StringTokenizer(num, "+=", true); // 구분자포함 토큰으로 인식

        while (first.hasMoreTokens()) {
            System.out.print(first.nextToken() + " ");
        }
        System.out.println();
        while (twice.hasMoreTokens()) {
            System.out.print(twice.nextToken() + " ");
        }
        System.out.println();
        while (third.hasMoreTokens()) {
            System.out.print(third.nextToken() + " ");
        }
    }
}


Generic 암기할 개념 문제

27일 메모 참조

  1. 제네릭 메소드에 매개변수로 배열을 전달하는 형태로 정의및 호출해 보자.
package com.test.memo;

public class Practice2 {
    static <N> void example(N[] numArr) {
        for (N n : numArr) {
            System.out.print(n + " ");
        }
    }

    public static void main(String[] args) {
        // int[] arr = { 1, 3, 5, 7, 9 }; 기본 데이터 타입 int는 제네릭을 사용할 수 없다.
        Integer[] arr = { 1, 3, 5, 7, 9 };
        example(arr);
    }
}
  • 메인 메서드에서 배열을 int로 선언해서 오류가 났다.

    • 현재 위에 예제에서는 오토박싱, 오토언박싱이 이루어지고있다.

      • Integer클래스를 사용하여 정수를 객체로 포장한 후에 배열에 저장한다.
class IntroGenericArray
{    
    public static <T> void showArrayData(T[] arr)//제네릭 메소드임을 선언하기 위한 <T> 
    {
        for(int i=0; i<arr.length; i++)
            System.out.println(arr[i]);
    }

    public static void main(String[] args)
    {
        String[] stArr=new String[]{
                "Hi!",
                "I'm so happy",
                "Java Generic Programming"
        };

        showArrayData(stArr);
    }
}

  1. 다음의 메소드 정의를 보면서 매개변수로 전달될 수 있는 대상의 범위를 정리해 보자.

public void hiMethod(Apple param) { ... }

  • 자료형이 Apple인 변수밖에 올 수 없다.

  • 매개변수의 자료형이 Apple이거나, Apple 인스턴스 또는 Apple을 상속하는 인스턴스의 참조 값이 매개 변수에 전달될 수 있다.

  1. 다음의 메소드 정의를 보면서 매개변수로 전달될 수 있는 대상의 범위를 정리해 보자.중요

public void onMethod(FruitBox param) { ... }

  • FruitBox 인스턴스의 참조 값이 전달 대상이 될 수 있다.


3-1. 만약 Apple클래스가 Fruit클래스를 상속하는 경우에 FruitBox\ 인스턴스의 참조 값이 위의 onMethod의 매개변수로 올 수 있을까? 중요

  • 문법적으로 아니다. FruitBox\ 과 FruitBox\ 이 상속관계에 놓이는 것은 아니기 때문에 클래스가 정의되는 과정에서 키워드 extends 를 통해 상속됨이 명시되어야 한다.

  • FruitBox\ 인스턴스의 참조 값도 인자로 전달받을 수 있는 매개변수의 선언은 와일드 카드를 이용한 자료형의 명시를 허용한다.(상한 제한 와일드카드)

    • FruitBox<? extends Fruit> box1 = new FruitBox<Fruit>();

    • FruitBox<? extends Fruit> box2 = new FruitBox<Apple>();

    가 의미하는 바는 "Fruit을 상속하는 모든 클래스" 이다. > ?
    • 즉, 자료형을 결정 짓는 제네릭 매개변수 T에 Fruit클래스를 포함하여, Fruit을 상속하는 클래스라면 무엇이든 올 수 있음을 명시하는 것이다.

      • box1과 box2는 new FruitBox<'Fruit클래스 , 또는 Fruit을 상속하는 클래스의 이름'>() 의 형태로 생성되는 인스턴스면 무엇이든 참조 가능하다.

  1. 자료형을 결정짓는 제네릭 매개변수 T에 Fruit클래스를 포함하여, Fruit을 상속하는 클래스면 무엇이든 올 수 있음을 명시할려면? 중요
  • FruitBox<? extends Fruit> box1 = new FruitBox<Fruit>();
  • class FruitBox<? extends Fruit>{...}

와일드카드를 이용해 특정 타입의 서브 클래스들을 나타내는 것이다.

'? extends Fruit'은 Fruit클래스를 상속하는 어떤 타입의 클래스나 인터페이스도 올 수 있음을 의미한다.

객체를 생성할 때 뿐 아니라 메소드에서도 사용 가능하다.


  1. 문제4에 해당하는 예제소스코드를 작성하시오.
class Fruit
{
    public void showYou()
    { 
        System.out.println("난 과일입니다.");
    }
}

class Apple extends Fruit
{
    public void showYou()
    {
        super.showYou();
        System.out.println("난 붉은 과일입니다.");
    }    
}

class FruitBox<T>    //과일 객체를 저장하고, 필요할 때 꺼낼 수 있는 기능이 존재하는 클래스 
{
    T item;
    public void store(T item) { this.item=item; }
    public T pullOut() { return item; }
}

class IntroWildCard
{
    public static void openAndShowFruitBox(FruitBox<? extends Fruit> box)//Fruit클래스나 그 하위 ㅋ르래스의 객체만 받을 수 있
    {
        Fruit fruit=box.pullOut(); //상위클래스의 참조변수로 하위클래스의 객체 주소값을 참조할 수 있으므로 
        fruit.showYou();
    }
    public static void main(String[] args)
    {
        FruitBox<Fruit> box1=new FruitBox<Fruit>(); //new FruitBox<>(); 로 생략가능 
        box1.store(new Fruit());

        FruitBox<Apple> box2=new FruitBox<Apple>();
        box2.store(new Apple());//Apple이 Fruit을 상속받고 있으므로 가능하다. 

        openAndShowFruitBox(box1);
        openAndShowFruitBox(box2);
    }
}
  • 와일드카드를 이용해 제네릭을 사용하면, 반환타입 옆에 제네릭을 명시하지 않는다.(문법)

  1. 전달되는 자료형에 상관없이 FruitBox의 인스턴스를 참조하려면?
  • FruitBox<?> box; > Object에게 상속받는 모든 클래스

  • FruitBox<? extends Object> box; > 위와 같은 의미


  1. .FruitBox가 인스턴스를 참조하되, T가 Apple 클래스 또는 Apple 클래스가 직간접적으로 상속하는 클래스인 경우에만 참조할 수 있게 하려면?
  • FruitBox<? super Apple> boundedBox; > Apple이 상속하는 모든 클래스를 의미

    • extends는 "~을 상속하는 클래스라면 무엇이든지" > 해당 하위객체만 받을 수 o

    • super는 "~이 상속하는 클래스라면 무엇이든지" > 해당 상위 객체만 받을 수 O

class Fruit
{
    public String toString()
    {
        return "과일";
    }
}

class Apple extends Fruit
{
    public String toString()
    {
        return "사과";
    }
}

class RedApple extends Apple
{
    public String toString()
    {
        return "빨간 사과";
    }    
}

class FruitBox<T>
{
    T item;
    public void store(T item)
    {
        this.item = item;
    }
    public T pullOut()
    {
        return item;
    }
}


public class Test {

    public static void show(FruitBox<? super Apple> box)
    {
        System.out.println(box.pullOut());
    }

    public static void main(String[] args) {
        FruitBox<Apple> fbox1 = new FruitBox<Apple>();
        fbox1.store(new Apple());
        show(fbox1);
        FruitBox<Fruit> fbox2 = new FruitBox<Fruit>();
        fbox2.store(new Fruit());
        show(fbox2);
        FruitBox<RedApple> fbox3 = new FruitBox<RedApple>();
        fbox3.store(new RedApple());
//        show(fbox3); // 에러
    }

}
  • show메서드의 인자값으로는 Apple, Apple의 상위클래스 Fruit, Object 자료형이 올 수 있다.

  • 주석처리한 부분(에러): show메서드의 인자는 Apple과 Fruit, Object를 저장하는 상자만 들어갈 수 있고, 외에 하위 클래스는 올 수 없다. >>Apple을 포함한 Apple의 상위클래스만 올 수 있다.


  1. 제네릭 클래스 A를 상속하려면
class AAA<T>
{
    T itemAAA;
}

class BBB<T> extends AAA<T>
{
    T itemBBB;
}

이렇게 상속이 되면, 하나의 자료형 정보로 인해서 AAA의 자료형과 BBB의 자료형이 모두 결정된다.

즉, 다음과 같이 문장을 구성하면, T가 각각 String과 Integer로 대체되어 인스턴스가 생성된다.(Wrapper Class 로 활용 가능)

BBB<String> myString = new BBB<String>();
BBB<Integer> myInteger = new BBB<Integer>();

  1. AAA클래스의 T를 지정해서 상속할려면?
class BBB extends AAA<String>
{
    int itemBBB;
}

위의 BBB 클래스는 제네릭으로 정의될 수도 있다.

  • 위처럼 지정해서 상속하면, 해당 클래스 안 메소드들도, 지정된 자료형으로 명시해야한다.

그러나 제네릭이 아니어도 된다는 것을 강조하기 위해서 일반 클래스로 정의하였다.


  1. 인터페이스를 제네릭으로 정의해보자
interface MyInterface<T>
{
    public T myFunc(T item);
}

interface AAA<T>
{
    void aaa(T item);
}

  1. 인터페이스를 구현하여 클래스를 정의하는 방식 두가지

    1. T를 그대로 유지하는 방식
    class MyImplement<T> implements MyInterface<T>
    {
        public T myFunc(T item)//인터페이스 구현
        {
            return item;
        }
    }
    1. T의 자료형을 결정하는 방식
    class MyImplement implements MyInterface<String>
    {
        public String myFunc(String item)//제네릭을 String 으로 지정해 구현도 가능  
        {
            return item;
        }
    }

주의할점

위의 클래스 정의와 같이 T의 자료형이 String으로 결정되면, MyInterface의 메소드 myFunc를 구현할 때에도 T가 아닌 String으로 명시해야 한다.


3번 문제 와일드 카드 개념

제네릭 타입의 유연성을 높여주는 기능이다.

  • 제네릭 타입을 사용할 때, 타입 매개변수를 명확히 지정하지 않고 일부 불확실한 경우에 사용된다.

  • 와일드 카드는 물음표 기호(?)로 표시되며, 크게 세 가지 유형이 있다.

    1. <?>: 비한정적 와일드카드(Unbounded Wildcards)

      • 어떤 타입이든 모두 허용될 때 사용된다. 즉, 제한 없이 모든 타입에 대해 동작한다.
    2. <? extends T>: 상한 와일드카드(Upper Bounded Wildcards)

      • 상한 와일드카드는 특정 타입의 하위 타입만을 허용한다.
    3. <? super T>: 하한 와일드카드(Lower Bounded Wildcards)

      • 하한 와일드카드는 특정 타입의 상위 타입만을 허용한다.

      • 특정 타입과 그 타입의 상위 타입들만을 처리할 수 있다.

와일드 카드는 제네릭 메서드, 제네릭 클래스, 제네릭 인터페이스에서 사용될 수 있으며, 코드의 유연성을 높이고 재사용성을 향상시킨다.


Generic 예제

예제1

class Box<T> {
    private T ob;

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

class BoxInBox {
    public static void main(String[] args) {
        Box<String> sBox = new Box<>(); //<>안에 생략 가능 
        sBox.set("I am so happy.");

        Box<Box<String>> wBox = new Box<>(); 
        wBox.set(sBox);

        Box<Box<Box<String>>> zBox = new Box<>();
        zBox.set(wBox);

        System.out.println(zBox.get().get().get()); //wBox >> sBox >> 문자열을 꺼내게되는
    }
}

타입 인자의 생략 : 다이아몬드(Diamond) 기호

Box<Apple> aBox = new Box<>(); 형태로 객체 생성 가능

  • 위처럼 중첩해서 객체를 생성했다면 출력해서 꺼낼때, 꺼내고 싶은 객체의 중첩양 만큼 get()메소드도 같은 양으로 꺼내야한다.

  • 위에 객체 생성할때 new Box<>(); 하면 자바 자동완성에서는 그냥 new Box();로 자동완성 하는데, 기본적으로 같은 동작을 수행하지만, 자바가 자동완성하는 new Box();는 제네릭 타입의 매개변수를 생략하는 것이기 때문에 경고를 발생한다.

    • 컴파일러가 제네릭 타입의 매개변수를 명시하는 것을 권장하기 때문이다.

제네릭 타입 인자 Number 클래스로 제한하는 예제

class Box<T extends Number> {
    private T ob;

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

class BoundedBox {
    public static void main(String[] args) {
        Box<Integer> iBox = new Box<>();    // Integer는 Number를 상속 > 객체 형태 
        iBox.set(24);

        Box<Double> dBox = new Box<>();    // Double은 Number를 상속
        dBox.set(5.97); //오토박싱 >> double객체의 주소값이 들어가있는것 

        System.out.println(iBox.get());
        System.out.println(dBox.get());
    }
}
  • 위처럼 Number클래스를 상속하는 클래스의 인스턴스만 담고 싶다면

    • class Box<T extends Number> {...} > 인스턴스 생성 시 타입 인자로 Number또는 이를 상속하는 클래스만 올 수 있게 된다.
class Box<T>{
    private T ob;
    ...
    public int toIntValue() {
        return ob.intValue();    // Error!
    }
}

모든 클래스가 intValue()메소드가 있는지 없는지 모르기 때문에 위와같은 코드는 Object메소드만 호출할 수 있다.

class Box<T extends Number> {
    private T ob;
    ...
    public int toIntValue() {
        return ob.intValue();    // OK!
    }
}
  • 그러나 위와 같이 타입 인자를 제한하면 Number클래스의 intValue메소드를 호출 할 수 있다.

인터페이스를 이용해 타입 인자를 제한하는 예제

interface Eatable {
   public String eat();    
}

class Apple implements Eatable {
    public String toString() {
        return "I am an apple.";
    }

    @Override
    public String eat() {
        return "It tastes so good!";
    }
}

class Box<T extends Eatable> {//Eatable을 구현하는 클래스가 올 수 있게끔 제약을 두었다. 
    private T ob;

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

    // 한 입 먹고 반환하는 행위의 메소드로 수정
    public T get() {
        System.out.println(ob.eat());    // Eatable로 제한하였기에 eat 호출 가능
        return ob;
    }    
}

class BoundedInterfaceBox {
    public static void main(String[] args) {
        Box<Apple> box = new Box<>();
        box.set(new Apple());        // 사과 저장

        Apple ap = box.get();        // 사과 꺼내기
        System.out.println(ap);
    }
}
  • Box클래스의 타입 인자에서 Eatable인터페이스를 구현하는 클래스로 타입 인자를 제한했기 때문에 인터페이스에 선언되어 있는 eat()메소드의 호출이 가능한것이다.

  • 타입 인자를 제한할 때는 하나의 클래스와 하나 이상의 인터페이스에 대해 동시에 제한을 할 수 있다.

    • class Box<T extends Number & Eatable> {...} >> Number를 상속하고 Eatable인터페이스를 구현하는 클래스만이 올 수 있도록 제한한것이다.

public static <T> Box<T> makeBox(T o) {

메소드의 이름: makeBox

  • 반환형: Box

  • 제네릭 타입

static과 Box사이에 위치한 가 T타입 매개변수임을 알리는 표시다.

위와 같은 메소드 정의를 보면 Box가 반환형임을, 그리고 그 앞에 위치한 는 T가 타입 매개변수임을 알리는 표시임을 알 수 있어야 한다.(반환타입 이전이 타입 명시)

<> public static Box<T> makeBox(T o) {...} 이렇게 표시하면 에러남

class Box<T> {
    private T ob;

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

class BoxFactory {
    public static <T> Box<T> makeBox(T o) {    // 제네릭 메소드의 정의
        Box<T> box = new Box<T>();    // 상자를 생성하고,
        box.set(o);    // 전달된 인스턴스를 상자에 담아서,
        return box;    // 이 상자를 반환한다.
    }
}

class GenericMethodBoxMaker {
    public static void main(String[] args) {
        Box<String> sBox = BoxFactory.makeBox("Sweet");
        System.out.println(sBox.get());

        Box<Double> dBox = BoxFactory.makeBox(7.59);
        System.out.println(dBox.get());
    }
}

제네릭 인터페이스를 구현하는 제네릭 클래스

예제1

interface Getable<T> {
    public T get();
}

class Box<T> implements Getable<T> {    // Box<T> 클래스는 다음과 같이 Getable<T> 인터페이스를 구현하는 형태로 정의
    private T ob;     
    public void set(T o) { ob = o; }

    @Override
    public T get() { 
        return ob; 
    }
}

class Toy {
    @Override 
    public String toString() { 
        return "I am a Toy";
    }
}

class GetableGenericInterface {
    public static void main(String[] args) {
        Box<Toy> box = new Box<>();
        box.set(new Toy());

        Getable<Toy> gt = box;
        System.out.println(gt.get());        
    }
}
  • 제네릭 인터페이스를 구현할 때에는 아래와 같이 T를 결정한 상태로 구현할 수 있다.
  • class Box<T> implements Getable<String> {...}
  • 위와같이 제네릭 인터페이스의 T를 String으로 지정하면 Getable의 메소드를 구현할 떄에도 T가 아닌 String으로 명시해 구현해야한다.(get()메소드)

예제 2 - 인터페이스에서 제네릭을 지정했을 때

interface Getable<T> {
    public T get();
}

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

    @Override
    public String get() { //제네릭을 String으로 두었으니 여기서도 String 형으로 
        return ob.toString(); 
    }
}

class Toy {
    @Override 
    public String toString() { 
        return "I am a Toy";
    }
}

class GetableGenericInterface2 {
    public static void main(String[] args) {
        Box<Toy> box = new Box<>();
        box.set(new Toy());

        Getable<String> gt = box;
        System.out.println(gt.get());      

    }
}
  • Getable형 참조변수는 위 메인 메서드에서와 같이 Box인스턴스를 T의 자료형에 상관없이 참조할 수 있다.

    1. 만약 Box 클래스가 Getable 인터페이스를 직접 구현하는 것이 아니라, 다른 클래스가 Getable 인터페이스를 구현한 객체를 반환하는 메서드를 가지고 있을 때

타입인자 정보 생략 가능

Box<String> sBox = BoxFactory.<String>makeBox("Sweet");
Box<Double> dBox = BoxFactory.<Double>makeBox(7.59);    // 7.59에 대해 오토 박싱 진행됨
  • 첫 번째 문장에서는 T를 String으로 결정하여 호출하였고, 두 번째 문장에서는 Double로 결정하여 호출하였다
Box<String> sBox = BoxFactory.makeBox("Sweet");
Box<Double> dBox = BoxFactory.makeBox(7.59);        // 7.59에 대해 오토 박싱 진행됨
  • 컴파일러는 makeBox에 전달되는 인자를 보고 T를 각각 String과 Double로 유추한다. 그리고 이러한 자료형의 유추는 오토 박싱까지 감안하여 이뤄진다.

제네릭 클래스의 상속

class Box<T> {
    protected T ob;

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

class SteelBox<T> extends Box<T> {//제네릭 클래스의 상속 
    public SteelBox(T o) {    // 제네릭 클래스의 생성자
        ob = o;
    }//이 클래스로 인해  메인 메서드에서와 같이 Box<T>의 참조변수로 SteelBox<T>인스턴스를 참조하는 문장을 구성할 수있
}



class GenericInheritance {
    public static void main(String[] args) {
        Box<Integer> iBox = new SteelBox<>(7959);
        Box<String> sBox = new SteelBox<>("Simple");

        System.out.println(iBox.get());
        System.out.println(sBox.get());
    }
}

'SteelBox 클래스는 Box 클래스를 상속한다."
"SteelBox 제네릭 타입은 Box 지네릭 타입을 상속한다." 라고 표현한다.

Box<Number> box = new Box<Integer>(); >> 컴파일 불가능

  • Number를 Integer가 상속하지만 Box와 Box는 상속 관계를 형성하지 않는다. 따라서 컴파일 되지 않는다.

와일드카드 기반 제네릭 메소드

class Box<T> {
    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> T openBox(Box<T> box) {
        return box.get();
    }

    public static void peekBox(Box<?> box) {    // 와일드카드 사용
        System.out.println(box);
    }
}

class WildcardUnboxer2 {
    public static void main(String[] args) {
        Box<String> box = new Box<>();
        box.set("So Simple String");
        Unboxer.peekBox(box);
    }
}
  • public static void peekBox(Box box){

    System.out.println(box);}

    • 제네릭 메소드의 정의 >> 가 두번 등장
  • public static void peekBox(Box<?> box) {

    System.out.println(box);}

    • 와일드 카드 기반 메소드 정의 >> 매개변수 선언에서 <?> 한번만 등장

기능적인 측면에서 보면 위의 두 메소드는 완전히 동일하다. 즉 제네릭 메소드와 와일드카드 기반 메소드는 상호 대체 가능한 측면이 있다.

그러나 코드가 조금 더 간결하다는 이유로 와일드카드 기반 메소드의 정의를 선호한다.


하한 제한된 와일드카드로 선언하는 제네릭 메소드

class Box<T> {
    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 void peekBox(Box<? super Integer> box) {
        System.out.println(box);
    }//Box<Integer>, Box<Number>, Box<Object> 의 타입종류만 올 수 있
}

class LowerBoundedWildcard {
    public static void main(String[] args) {
        Box<Integer> iBox = new Box<Integer>();
        iBox.set(5577);

        Box<Number> nBox = new Box<Number>();
//        nBox.set(new Integer(9955));    // deprecated API.
        nBox.set(Integer.valueOf(9955));    

        Box<Object> oBox = new Box<Object>();
        oBox.set("My Simple Instance");

        Unboxer.peekBox(iBox);
        Unboxer.peekBox(nBox);
        Unboxer.peekBox(oBox);
    }
}
  • 하한 제한이므로, Integer가 제일 낮은 하위클래스가 되므로, Box, Box, Box 의 종류만 올 수 있다.

    와일드 카드 메소드 사용의 이유

    class Box<T> {
        private T ob;     
        public void set(T o) { ob = o; }
        public T get() { return ob; }
    }
    
    class Toy {    //장난감을 나타내는 클래
        @Override 
        public String toString() { 
            return "I am a Toy";
        }
    }
    
    class BoxHandler {
        public static void outBox(Box<Toy> box) {
            Toy t = box.get();    // 박스에서 꺼내기
            System.out.println(t);
        }
    
        public static void inBox(Box<Toy> box, Toy n) {
            box.set(n);    // 박스에 넣기
        } 
    }
    
    
    class BoundedWildcardBase {
        public static void main(String[] args) {
            Box<Toy> box = new Box<>();
            BoxHandler.inBox(box, new Toy());
            BoxHandler.outBox(box); 
        }
    }

    와일드카드를 사용하는 이유는 메서드가 호출될 때 컴파일러가 유형 안전성을 보장하기 위함

    1. outBox() 메서드에 Box<Toy> 인스턴스를 전달하면 Toy 클래스의 하위 클래스가 아닌 다른 타입의 인스턴스를 저장하려고 할 때 컴파일 오류가 발생합니다.

    2. 이를 통해 메서드의 호출자가 오류를 컴파일 시간에 발견할 수 있습니다.


    와일드 카드로 컴파일 오류 발견

    class Box<T> {
        private T ob;     
        public void set(T o) { ob = o; }
        public T get() { return ob; }
    }
    
    class Toy {
        @Override 
        public String toString() { 
            return "I am a Toy";
        }
    }
    
    class BoxContentsMover {
    
        // from에 저장된 내용물을 to로 이동
    
        public static void moveBox(Box<? super Toy> to, Box<? extends Toy> from) {
            to.set(from.get());
        }
    }
    
    class MoveBoxContents {
        public static void main(String[] args) {
            Box<Toy> box1 = new Box<>();
            box1.set(new Toy());
    
        // box1에 저장된 내용물 box2로 이동
            Box<Toy> box2 = new Box<>();
            BoxContentsMover.moveBox(box2, box1);
    
            System.out.println(box2.get());
        }
    }

    이같이 하는 이유는 실수가 컴파일 과정에서 드러나기 위해 제한을 걸어놓는 것이다.

    • 매개변수 선언: Box<? extends Toy> box

      box가 참조하는 인스턴스를 대상으로 꺼내는 작업만 허용

    • 매개변수 선언: Box<? super Toy> box

      box가 참조하는 인스턴스를 대상으로 넣는 작업만 허용


    와일드카드 선언을 갖는 제네릭 메소드의 오버로딩 기준

    class Box<T> {
        private T ob;     
        public void set(T o) { ob = o; }
        public T get() { return ob; }
    }
    
    class Toy {
        @Override 
        public String toString() { 
            return "I am a Toy";
        }
    }
    
    class Robot {
        @Override 
        public String toString() { 
            return "I am a Robot";
        }
    }
    
    class BoxHandler {
        public static <T> void outBox(Box<? extends T> box) { //꺼내기만 할 수 있는 메소드 
            T t = box.get();
            System.out.println(t);
        }
    
        public static <T> void inBox(Box<? super T> box, T n) {//넣기만 할 수 있는 메소
            box.set(n); 
        } 
    }//Box<Toy> 인스턴스와 Box<Robot> 인스턴스를 동시에 허용할 수 있도록 정
    
    class BoundedWildcardGenericMethod {
        public static void main(String[] args) {
            Box<Toy> tBox = new Box<>();
            BoxHandler.inBox(tBox, new Toy());
            BoxHandler.outBox(tBox); 
    
            Box<Robot> rBox = new Box<>();
            BoxHandler.inBox(rBox, new Robot());
            BoxHandler.outBox(rBox);
        }
    }

    아래와 같이 오버로딩하여 메소드를 정의하는 방법이 존재한다.

    class BoxHandler {
        // 다음 두 메소드는 오버로딩 인정 안됨.
        public static void outBox(Box<? extends Toy> box) {...}
        public static void outBox(Box<? extends Robot> box) {...}
    
        //  다음 두 메소드는 두 번째 매개변수로 인해 오버로딩 인정 됨.
        public static void inBox(Box<? super Toy> box, Toy n) {...}
        public static void inBox(Box<? super Robot> box, Robot n) {...}
    }
    1. 첫번째 / 두 개의 메소드의 오버로딩이 성립하지 않는 이유는

      • 바는 제네릭 등장 이전에 정의된 클래스들과의 상호 호환성 유지를 위해 컴파일 시 제네릭과 와일드카드 관련 정보를 지우는 과정을 거친다. 즉 위의 두 매개변수 선언은 컴파일 과정에서 다음과 같이 수정이 된다.

        • Box<? extends Toy> box -> Box box
          Box<? extends Robot> box -> Box box
      • 위와 같이 컴파일러가 제네릭 정보를 지우는 행위를 가리켜 'Type Erasure'라 한다. 아래와 같이 에러 발생

      • name clash:
        outBox(Box<? extends Robot>) and outBox(Box<? extends Toy>)have the same erasure

        ==

        {이름 충돌 : outBox(Box<? extends Robot>)와 outBox(Box<? extends Toy>)은 'Type Erasure'에 의해 매개변수 정보가 같아집니다. }

    2. 두번째 / 오버로딩이 인정되는 이유는 제네릭과 관련없는 두 번째 매개변수의 자료형이 다르기 때문이다.

    public static <T> void outBox(Box<? extends T> box) {...} 와 같은 제네릭 메소드의 정의로 대신한다.



    Generic 문제1

    1. 수납공간이 둘로 나눠져 있는 상자를 표현한 클래스를 DDBox<U, D>라는 이름으로 하나 더 정의하여 DBox<L, R> 인스턴스 둘을 이 상자에 저장하고자 한다. 그럼 다음 main 메소드를 기반으로 컴파일 및 실행이 가능하도록 DDBox<U, D> 제네릭 클래스를 정의해보자.

    c:\JavaStudy>java DDBoxDemo
    Apple & 25
    Orange & 33

    내가 푼것

    package com.test.memo;
    
    class DBox<L, R> {
        private L left;
        private R right;
    
        public void set(L o, R r) {
            left = o;
            right = r;
        }
    
        public String toString() {
            return left + " & " + right;
        }
    }
    
    class DDBox<U, D> {
        private U fruit;
        private D num;
    
        void set(U f, D n) {
            fruit = f;
            num = n;
        }
    
        @Override
        public String toString() {
            return super.toString() + "\n" + fruit + "\n" + num;
        }
    
    }
    
    public class Practice2 {
    
        public static void main(String[] args) {
            DBox<String, Integer> box1 = new DBox<>();
            box1.set("Apple", 25);
            DBox<String, Integer> box2 = new DBox<>();
            box2.set("Orange", 33);
            DDBox<DBox<String, Integer>, DBox<String, Integer>> ddbox = new DDBox<>();
            ddbox.set(box1, box2); // 두 개의 상자를 하나의 상자에 담음
            System.out.println(ddbox); // 상자의 내용물 출력
        }
    }

    • 그냥 해당 값 지역변수에 저장해서 toString()으로 꺼내줬다. >> 이렇게하던 선생님처럼 하던 상관없다.

    선생님 풀이

    class DBox<L, R> {
        private L left;     // 왼쪽 수납 공간
        private R right;    // 오른쪽 수납 공간
    
        public void set(L o, R r) {
            left = o;
            right = r;
        } 
    
        @Override
        public String toString() {
            return left + " & " +right;
        }
    }
    
    class DDBox<U, D> {
        private U up;
        private D down;
    
        public void set(U u, D d) {
            up = u;
            down = d;
        }
    
        @Override
        public String toString() {
            return up.toString() + "\n" + down.toString();
        }
    }
    
    class DDBoxDemo {
        public static void main(String[] args) {
            DBox<String, Integer> box1 = new DBox<>();
            box1.set("Apple", 25);
    
            DBox<String, Integer> box2 = new DBox<>();
            box2.set("Orange", 33);
    
            DDBox<DBox<String, Integer>, DBox<String, Integer>> ddbox = new DDBox<>();
            ddbox.set(box1, box2);
    
            System.out.println(ddbox);
        }
    }
    • 어차피 메인메서드에서 DBox가 DDBox다이아몬드안에 들어가 있으니까 지역 변수의 toString()으로 반환시키면 DBox를 통해 반환되는 것이였나..

    1. 는 문제 1의 내용과 결과를 보이는 프로그램을 작성하되 DBox 클래스 하나만 활용하여 작성해보자.(상자에 담긴 내용물의 출력 형태는 달라도 괜찮다. 내용물만 전부 출력이 되면 된다.)

    선생님 풀이

    package com.test.memo;
    
    class DBox<L, R> {
        private L left;
        private R right;
    
        public void set(L o, R r) {
            left = o;
            right = r;
        }
    
        public String toString() {
            return left + " & " + right;
        }
    }
    
    //class DDBox<U, D> {
    //    private U fruit;
    //    private D num;
    //
    //    void set(U f, D n) {
    //        fruit = f;
    //        num = n;
    //    }
    //
    //    @Override
    //    public String toString() {
    //        return super.toString() + "\n" + fruit + "\n" + num;
    //    }
    //}
    
    public class Practice2 {
    
        public static void main(String[] args) {
            DBox<String, Integer> box1 = new DBox<>();
            box1.set("Apple", 25);
            DBox<String, Integer> box2 = new DBox<>();
            box2.set("Orange", 33);
    
            DBox<DBox<String, Integer>, DBox<String, Integer>> ddBox = new DBox<>();
            ddBox.set(box1, box2);
            System.out.println(ddBox);
    
    //        DDBox<DBox<String, Integer>, DBox<String, Integer>> ddbox = new DDBox<>();
    //        ddbox.set(box1, box2); // 두 개의 상자를 하나의 상자에 담음
    //        System.out.println(ddbox); // 상자의 내용물 출력
        }
    }

    Generic 문제 2

    1. swapBox 메소드를 정의하되, Box 인스턴스를 인자로 전달받을 수 있도록 정의하자. 단 이때 Box 인스턴스의 T는 Number 또는 이를 상속하는 하위 클래스만 올 수 있도록 제한된 매개변수 선언을 하자.

    class Box {
    private T ob;

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

    public T get() {
    return ob;
    }

    }
    class BoxSwapDemo {
    // 이 위치에 swapBox 메소드 정의하자.
    public static void main(String[] args) {
    Box box1 = new Box<>();
    box1.set(99);
    Box box2 = new Box<>();
    box2.set(55);
    System.out.println(box1.get() + " & " + box2.get());
    swapBox(box1, box2); // 정의해야 할 swapBox 메소드
    System.out.println(box1.get() + " & " + box2.get());
    }
    }

    99 & 55
    55 & 99

    package com.test.memo;
    
    class Box<T> {
        private T ob;
    
        public void set(T o) {
            ob = o;
        }
    
        public T get() {
            return ob;
        }
    }
    
    public class Practice2 {
    
        private static <T extends Number> void swapBox(Box<T> box1, Box<T> box2) {
            T temp = box1.get();
            box1.set(box2.get());
            box2.set(temp);
    
        }
    
        public static void main(String[] args) {
            Box<Integer> box1 = new Box<>();
            box1.set(99);
    
            Box<Integer> box2 = new Box<>();
            box2.set(55);
    
            System.out.println(box1.get() + " & " + box2.get());
            swapBox(box1, box2); // 정의해야 할 swapBox 메소드
            System.out.println(box1.get() + " & " + box2.get());
    
        }
    }
    • Number를 상속하게 제한을 둔다.

    • 객체로 저장되어 있으니, get과 set을 이용해 치환해주는 것이다.


    Generic 문제 3

    컴파일 과정에서는 이 실수가 드러나지 않는다. 실수가 컴파일 과정에서 발견될 수 있도록 매개변수 선언을 수정하자. 그리고 프로그래머의 실수를 바로잡자.

    class Box<T> {
        private T ob;     
        public void set(T o) { ob = o; }
        public T get() { return ob; }
    }
    
    class BoundedWildcardDemo {
        public static void addBox(Box<Integer> b1, Box<Integer> b2, Box<Integer> b3) {
            b3.set(b1.get() + b2.get());    // 프로그래머의 실수가 있는 부분
        }
    
        public static void main(String[] args) {
            Box<Integer> box1 = new Box<>();
            box1.set(24);
            Box<Integer> box2 = new Box<>();
            box2.set(37);        
            Box<Integer> result = new Box<>();
            result.set(0);
    
            addBox(result, box1, box2);    // result에 24 + 37의 결과 저장     
            System.out.println(result.get());    // 61 출력 
        }
    }
    • 메인 메서드에서 값을 저장할 result가 b1에 들어가는데 갑자기 b3에 값을 담는거 아닌가..?

      • 일부러 잘못만든것이였다.

    정답

    package com.test.memo;
    
    class Box<T> {
        private T ob;
    
        public void set(T o) {
            ob = o;
        }
    
        public T get() {
            return ob;
        }
    }
    
    public class Practice2 {            //꺼내는게 안되고              넣는게 안된다. 
        public static void addBox(Box<? super Integer> b1, Box<? extends Integer> b2, Box<? extends Integer> b3) {
            b1.set(b2.get() + b3.get()); // 프로그래머의 실수가 있던 부분
        }
    
        public static void main(String[] args) {
            Box<Integer> box1 = new Box<>();
            box1.set(24);
            Box<Integer> box2 = new Box<>();
            box2.set(37);
            Box<Integer> result = new Box<>();
            result.set(0);
    
            addBox(result, box1, box2); // result에 24 + 37의 결과 저장
            System.out.println(result.get()); // 61 출력
        }
    }
    • 메서드 인자값에 와일드카드를 다 넣어서 넣기만 할 수 있고, 꺼낼기만 할 수 있게 고치면 우리가 눈에 알 수 있게 컴파일 에러가 난다. >> 개발자가 실수한것을 인지할 수 있도록

      • 후에 개발자가 실수한 내용을 고친다.

    Generic 문제 4

    class Box<T> {
        private T ob;     
        public void set(T o) { ob = o; }
        public T get() { return ob; }
    }
    
    class BoundedWildcardGeneric {
        // box에 con과 동일한 내용물이 들었는지 확인
        public static <T> boolean compBox(Box<T> box, T con) {
            T bc = box.get();
            box.set(con);        // 프로그래머의 실수로 삽입된 문장, 때문에 내용물이 바뀐다.
            return bc.equals(con);
        }
    
        public static void main(String[] args) {
            Box<Integer> box1 = new Box<>();
            box1.set(24);
    
            Box<String> box2 = new Box<>();
            box2.set("Poly");
    
            if(compBox(box1, 25))
                System.out.println("상자 안에 25 저장");
    
            if(compBox(box2, "Moly"))
                System.out.println("상자 안에 Moly 저장");
    
            System.out.println(box1.get());
            System.out.println(box2.get());
        }
    }
    • set.() 하는 메서드의 인자값인 box<? extends T> 로 변경시키면 컴파일 할 때 오류가 뜨는것을 발견할 수 있다.

      • 이렇게 개발자의 실수로 실행 중 오류가 나는것을 예방하기 위해 와일드 카드를 사용하는 것이다.(상한 제한/ 하한 제한)

    이클립스 소스코드 비교하는 법

    • 비교할 파일을 우클릭 compare with - each other 하면 소스코드 두개가 다른 부분이 나온다.

0개의 댓글