자바 : 제한된 와일드 카드 또는 제한된 유형 매개 변수?


82

최근에 다음 기사를 읽었습니다. http://download.oracle.com/javase/tutorial/extra/generics/wildcards.html

내 질문은 다음과 같은 방법을 만드는 대신입니다.

public void drawAll(List<? extends Shape> shapes){
    for (Shape s: shapes) {
        s.draw(this);
    }
}

다음과 같은 메서드를 만들 수 있으며 제대로 작동합니다.

public <T extends Shape> void drawAll(List<T> shapes){
    for (Shape s: shapes) {
        s.draw(this);
    }
}

어떤 방법을 사용해야합니까? 이 경우 와일드 카드가 유용합니까?


? 속기 표기법입니다. 내부적으로 컴파일러는 어쨌든 유형 매개 변수로 대체합니다. 컴파일러 오류가 발생하면? 대신 서로 게이트 유형 매개 변수가 표시됩니다.
평판이 좋지 않음

9
정정 : 이전 댓글이 잘못되었습니다. 와일드 카드는 내가 생각했던 것보다 더 정교합니다.
평판이 좋지 않음

@irreputable 실제로 더 복잡하지만 유형 매개 변수로 대체하는 것에 대한 귀하의 요점은 전적으로 유효합니다. 선언 할 수없는 것은 단지 하나입니다.
유진

이 두 가지 방법을 있는 그대로 살펴보면 차이가없고 스타일 문제 일뿐입니다. (당신이 좀 더 일반적인 매개 변수를 추가하는 경우) 그렇지 않으면 일이 변경됩니다
유진

답변:


133

그것은 당신이 무엇에 따라 필요 할 수 있습니다. 다음과 같은 작업을 수행하려면 제한된 유형 매개 변수를 사용해야합니다.

public <T extends Shape> void addIfPretty(List<T> shapes, T shape) {
    if (shape.isPretty()) {
       shapes.add(shape);
    }
}

여기에 a List<T> shapes와 a T shape가 있으므로 안전하게 할 수 shapes.add(shape)있습니다. 선언 된 경우 안전하게 List<? extends Shape>이동할 수 없습니다add (a List<Square>및 a 가있을 수 있기 때문에 Circle).

따라서 제한된 유형 매개 변수에 이름을 지정하여 일반 메소드의 다른 곳에서 사용할 수 있습니다. 물론이 정보가 항상 필요한 것은 아니므로 유형 (예 :)에 대해 많이 알 필요가없는 경우 drawAll와일드 카드만으로 충분합니다.

경계 유형 매개 변수를 다시 참조하지 않더라도 경계가 여러 개인 경우 경계 유형 매개 변수가 여전히 필요합니다. 다음은 Angelika Langer의 Java Generics FAQ 에서 인용 한 것입니다.

와일드 카드 바운드와 유형 매개 변수 바운드의 차이점은 무엇입니까?

와일드 카드는 하나의 경계 만 가질 수 있지만 유형 매개 변수는 여러 경계를 가질 수 있습니다. 와일드 카드는 하한 또는 상한을 가질 수 있지만 유형 매개 변수에 대한 하한은 없습니다.

와일드 카드 경계와 유형 매개 변수 경계는 둘 다 경계라고 불리며 부분적으로 유사한 구문을 갖기 때문에 종종 혼동됩니다. […]

구문 :

  type parameter bound     T extends Class & Interface1 & … & InterfaceN

  wildcard bound  
      upper bound          ? extends SuperType
      lower bound          ? super   SubType

와일드 카드는 하한 또는 상한 중 하나만 가질 수 있습니다. 와일드 카드 경계 목록은 허용되지 않습니다.

constrast에서 유형 매개 변수는 여러 경계를 가질 수 있지만 유형 매개 변수에 대한 하한은 없습니다.

Effective Java 2nd Edition, Item 28의 인용문 : 제한된 와일드 카드를 사용하여 API 유연성을 높입니다 .

유연성을 극대화하려면 생산자 또는 소비자를 나타내는 입력 매개 변수에 와일드 카드 유형을 사용하십시오. […] PECS는 생산자 extends, 소비자를 의미합니다. super[…]

반환 유형으로 와일드 카드 유형을 사용하지 마십시오 . 사용자에게 추가적인 유연성을 제공하는 대신 클라이언트 코드에서 와일드 카드 유형을 사용하도록 강제합니다. 올바르게 사용 된 와일드 카드 유형은 클래스 사용자에게 거의 보이지 않습니다. 메서드가 허용해야하는 매개 변수를 받아들이고 거부해야하는 매개 변수를 거부하도록합니다. 클래스 사용자가 와일드 카드 유형에 대해 생각해야한다면 클래스의 API에 문제가있을 수 있습니다.

PECS 원칙을 적용하여 이제 addIfPretty예제 로 돌아가서 다음을 작성하여 더 유연하게 만들 수 있습니다.

public <T extends Shape> void addIfPretty(List<? super T> list, T shape) { … }

이제 우리는 addIfPrettya Circle, a List<Object>. 이것은 명백히 typesafe하지만 우리의 원래 선언은 그것을 허용 할만큼 충분히 유연하지 않았습니다.

관련 질문


요약

  • 제한된 유형 매개 변수 / 와일드 카드를 사용하면 API의 유연성이 향상됩니다.
  • 유형에 여러 매개 변수가 필요한 경우 경계 유형 매개 변수를 사용할 수밖에 없습니다.
  • 유형에 하한이 필요한 경우 경계 와일드 카드를 사용할 수밖에 없습니다.
  • "생산자"는 상한을, "소비자"는 하한을 가짐
  • 반환 유형에 와일드 카드를 사용하지 마십시오.

대단히 감사합니다, 당신의 설명은 매우 명확하고 교훈입니다
토니 르

1
@Tony :이 책은 바운드가 없을 때 와일드 카드와 유형 매개 변수 중에서 선택하는 방법에 대한 짧은 토론도 제공합니다. 기본적으로 형식 매개 변수가 메서드 선언에 한 번만 나타나는 경우 와일드 카드를 사용합니다. reverse(List<?>)JLS java.sun.com/docs/books/jls/third_edition/html/…
polygenelubricants

다음 코드에 대해 UnsupportedOperationException이 발생합니다. <code> public static <T extends Number> void add (List <? super T> list, T num) {list.add (num); } </ code>
Samra 2013 년

1
또한- ?메소드 doWork(? type)에서 해당 매개 변수를 사용할 수 없기 때문에 메소드 매개 변수로 전달할 수 없습니다. 유형 매개 변수를 사용해야합니다.
Tomasz Mularczyk

1
@VivekVardhan 당신은 와일드 카드를 사용하여 그러한 방법을 만들 수 없습니다. 사용할 결정할 때, 설명하는 최고의 소스 중 하나입니다.
Eugene

6

귀하의 예에서는 T를 사용할 필요가 없습니다. 다른 곳에서는 해당 유형을 사용하지 않기 때문입니다.

하지만 다음과 같이했다면 :

public <T extends Shape> T drawFirstAndReturnIt(List<T> shapes){
    T s = shapes.get(0);
    s.draw(this);
    return s;
}

또는 polygenlubricants가 말한 것처럼 목록의 유형 매개 변수를 다른 유형 매개 변수와 일치시키고 싶다면 :

public <T extends Shape> void mergeThenDraw(List<T> shapes1, List<T> shapes2) {
    List<T> mergedList = new ArrayList<T>();
    mergedList.addAll(shapes1);
    mergedList.addAll(shapes2);
    for (Shape s: mergedList) {
        s.draw(this);
    }
}

첫 번째 예제에서는 Shape의 자식을받을 수있는 함수에 결과를 전달할 수 있기 때문에 Shape 만 반환 한 다음 좀 더 형식 안전성을 얻습니다. 예를 들어 a List<Square>를 내 메서드에 전달한 다음 결과 Square를 Squares 만 사용하는 메서드에 전달할 수 있습니다. '?'를 사용한 경우 결과 Shape를 Type 안전하지 않은 Square로 캐스팅해야합니다.

두 번째 예에서는 두 목록이 동일한 유형 매개 변수 ( '?'로 할 수 없음)를 가지고 있는지 확인하여 두 목록 모두의 모든 요소를 ​​포함하는 목록을 만들 수 있습니다. .


나는 Shape s = ...이어야 한다고 믿고 T s = ..., 그렇지 return s;않으면 컴파일해서는 안됩니다.
polygenelubricants

1

2 개의 SinglyLinkQueue를 병합하려는 아래의 James Gosling 4 판의 Java 프로그래밍에서 다음 예제를 고려하십시오.

public static <T1, T2 extends T1> void merge(SinglyLinkQueue<T1> d, SinglyLinkQueue<T2> s){
    // merge s element into d
}

public static <T> void merge(SinglyLinkQueue<T> d, SinglyLinkQueue<? extends T> s){
        // merge s element into d
}

위의 두 방법 모두 동일한 기능을 가지고 있습니다. 그렇다면 어느 것이 바람직합니까? 답은 두 번째입니다. 저자 자신의 말로 :

"일반적인 규칙은 와일드 카드가있는 코드가 일반적으로 여러 유형 매개 변수가있는 코드보다 가독성이 높기 때문에 가능하면 와일드 카드를 사용하는 것입니다. 유형 변수가 필요한지 결정할 때 해당 유형 변수가 두 개 이상의 매개 변수를 연결하는 데 사용되는지 자문 해보십시오. 또는 매개 변수 유형을 반환 유형과 연결합니다. 대답이 아니오 인 경우 와일드 카드로 충분합니다. "

참고 : 책에서는 두 번째 방법 만 제공되며 유형 매개 변수 이름은 'T'대신 S입니다. 첫 번째 방법은 책에 없습니다.


1

내가 이해하는 한, 와일드 카드는 유형 매개 변수가 필요하지 않은 상황에서 더 간결한 코드를 허용합니다 (예 : 여러 곳에서 참조되거나 다른 답변에서 자세히 설명한 것처럼 여러 경계가 필요하기 때문에).

링크에서 귀하는이 방향을 암시하는 다음 문장을 읽었 음을 나타냅니다 ( "Generic Methods"아래).

일반 메서드를 사용하면 형식 매개 변수를 사용하여 메서드 및 / 또는 반환 형식에 대한 하나 이상의 인수 형식 간의 종속성을 표현할 수 있습니다. 이러한 종속성이 없으면 제네릭 메서드를 사용하면 안됩니다.

[...]

와일드 카드를 사용하는 것이 명시 적 유형 매개 변수를 선언하는 것보다 더 명확하고 간결하므로 가능할 때마다 선호해야합니다.

[...]

또한 와일드 카드는 필드, 지역 변수 및 배열의 ​​유형과 같이 메서드 서명 외부에서 사용할 수 있다는 이점도 있습니다.


0

두 번째 방법은 좀 더 장황하지만 T내부 를 참조 할 수 있습니다 .

for (T shape : shapes) {
    ...
}

내가 이해하는 한 그게 유일한 차이점입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.