gmarket-ssb / book-study

4 stars 2 forks source link

item31 #2

Open wrallee opened 2 years ago

wrallee commented 2 years ago

public static void swap(List list, int i, int j); // 타입 매개변수 스왑 public static void swap(List<?> list, int i, int j); // 와일드카드 스왑

두 방식은 무슨 차이가 있나요?

dev-hajs commented 2 years ago

결론

이해한 내용

책에서는 Item 29. 이왕이면 제네릭 타입으로 만들라 -> Item 30. 이왕이면 제네릭 메서드로 만들라 를 통해서
(public API 성격을 띄는 메소드는) 가급적 제네릭 메소드로 만드는 것을 권하고 있었습니다.

이 때, 제네릭 메서드를 만들 수 있는 방법에는 타입 매개변수를 사용하는 것(첫 번째)과 와일드카드를 사용하는 것(두 번째)이 있기에

메서드 선언에 타입 매개변수가 한 번만 나오면 와일드 카드로 대체하라. -p.189

와 같이 와일드카드(두 번째) 방식을 더 권고하였습니다.

다만 와일드카드 방식을 사용하게 되면, 책에서 예시로 든 .swap() 메소드 처럼
방금 꺼낸 원소를 리스트에 다시 넣을 수 없는 즉, 컴파일 에러가 발생하게 되는 케이스가 발생할 수 있습니다.
image


그러니 이러한 케이스가 있는 경우에는 private 도우미 메소드 를 따로 작성해서 사용하면 된다고 안내해줄 뿐인 것 같습니다.

dev-hajs commented 2 years ago

언제나 그렇듯 중복 되더라도 이해한 내용 편하게 같이 공유하는 이슈가 되었으면 좋겠습니다. 🥺

jaesay commented 2 years ago

계속 잘 이해안돼서 링크처럼 따라갔네요..어렵네요.. https://stackoverflow.com/questions/3486689/java-bounded-wildcards-or-bounded-type-parameter => https://docs.oracle.com/javase/specs/jls/se17/html/jls-5.html#jls-5.1.10 => https://stackoverflow.com/questions/4431702/what-is-a-capture-conversion-in-java-and-can-anyone-give-me-examples => https://web.archive.org/web/20201109035143/http://www.ibm.com/developerworks/java/library/j-jtp04298/index.html

저는 일단 아래처럼 이해했습니다.


마지막 링크에서 보면 아래와 같은 디자인 원칙이 있다고 합니다. don't give something a name if you're never going to refer to it by name(swap 예에서 네임은 E를 의미) 이유는 더 복잡한 메소드 선언에서 타입이 증가하면 선언을 읽기 어렵다 (readability 가 떨어진다)

그래서 아마 책에서 나온 “메서드 선언에 타입 매개변수가 한 번만 나오면 와일드카드로 대체하라”의 규칙이 나온 것 같고 public API의 경우 swap2가 더 좋은 디자인이라고 말하는 것 같습니다.

// E 나온곳이 선언부밖에 없으니까 와일드 카드로 바꾸자.
public static <E> void swap1(List<E> list, int i, int j) {
    list.set(i, list.set(j, list.get(i)));
}

// 그럼 swap 메서드를 호출하는 클라이언트는 복잡한 swapHelper의 존재를 모른 채 그 혜택을 누릴 수 있다.
// E를 없앴으니 더 읽기 쉽다?
public static void swap2(List<?> list, int i, int j) {
    // 필요하다면 private capture helper를 통해 unknown wildcard type 이름을 컴파일러에게 알려줄 수 있다.
    swapHelper(list, i, j);
}
jaesay commented 2 years ago

마지막 링크 정리

public class CaptureTest {

    public static void main(String[] args) {
        Box<String> box = new Box<>();
        box.put("김이름");
        String s = box.get();
        System.out.println("s = " + s);
        new CaptureTest().unbox(box);
    }

    // 와일드 카드
    // raw, Object랑 차이 (이미 알음)
    public void unbox(Box<?> box) {
        // wildcard box가 할 수 있는 것 들은?
        // 1. get() 메소드 사용할 수 있다.
        // 2. Object 상속 메소드를 사용할 수 있다.
        // => 꽤 많은 것들을 할 수 있다.
        // operation의 안정성을 증명할 수가 없기 때문에 put()은 사용할 수 없다.
        // Box<?>는 T라는 type parameter가 있다는 것은 알지만 정확히 어떤 타입인지는 모른다.
        // => 타입 제약 위반할지 알수가 없다. operation의 안정성을 증명할 수가 없기 때문에 put()은 사용할 수 없다.
        // special case: null literal is a valid value for any reference type. => null은 가능
        // 그럼 wildcard는 box.get()의 리턴 타입을 알 수 있나?
        // an unbounded wildcard is Object라고 판단하는 것이 최선이다. => 인텔리제이에서 box.get()을 변수 추출해보면 Object로 만들어짐
        System.out.println(box.get());
    }

    // Wildcard capture
    public void rebox(Box<?> box) {
        // 컴파일러가 box 안에 wildcard를 보면
        // ?는 T여야 하지만 정확히는 뭔지 모름
        // 그래서 placeholder를 만든다. 이 placeholder를 capture of that particular wildcard(Wildcard capture) 라고 한다.
        // 인텔리제이에서 확인해보면 capture of ? 타입으로 컴파일러가 할당한 것을 알 수 있다. => 상황에 따라 다른 캡쳐를 얻기 떄문에 캡쳐이름은 매번 다르다. e.g. foo(Pair<?,?> x, Pair<?,?> y)
        box.put(box.get()); // formal type parameter (T)와 호환될 지 증명 할 수 없어서 에러 발생
    }

    public void rebox(Box<?> box) {
        // Capture helper
        // the unknown wildcard type 이름을 컴파일러에게 알려줄 수 있다.
        reboxHelper(box);
    }

    // generic method: usually used to formulate type constraints between the parameters and/or return value of the method
    // 일반 적으로는 ^ 지만 여기서는 box의 type parameter의 이름을 주기 위해 사용 (capture conversion?) : Capture conversion is what allows the compiler to manufacture a placeholder type name for the captured wildcard, so that type inference can infer it to be that type.
    // 자바 컴파일러는 제네릭 메소드의 타입 추론을 할 수 있다 (당연)
    // 덕분에 이제 T나 V의 어떤 값이라는 것을 알 수 있음
    private <V> void reboxHelper(Box<V> box) {
        box.put(box.get());
    }
}
dev-hajs commented 2 years ago

리뷰 완료