[java] 제네릭 메소드를 언제 사용하고 언제 와일드 카드를 사용해야합니까?

OracleDocGenericMethod의 일반 메서드에 대해 읽고 있습니다. 와일드 카드를 사용할 때와 제네릭 메서드를 사용할 때를 말할 때 비교에 대해 꽤 혼란 스럽습니다. 문서에서 인용.

interface Collection<E> {
    public boolean containsAll(Collection<?> c);
    public boolean addAll(Collection<? extends E> c);
}

대신 여기에서 일반 메서드를 사용할 수 있습니다.

interface Collection<E> {
    public <T> boolean containsAll(Collection<T> c);
    public <T extends E> boolean addAll(Collection<T> c);
    // Hey, type variables can have bounds too!
}

[…] 이것은 타입 인자가 다형성에 사용되고 있음을 알려줍니다. 유일한 효과는 다른 호출 사이트에서 다양한 실제 인수 유형을 사용할 수 있도록하는 것입니다. 이 경우 와일드 카드를 사용해야합니다. 와일드 카드는 여기에서 표현하려는 유연한 하위 유형 지정을 지원하도록 설계되었습니다.

와 같은 와일드 카드 (Collection<? extends E> c);가 일종의 다형성을 지원 한다고 생각하지 않습니까? 그렇다면 왜 일반적인 방법 사용이 좋지 않은 것으로 간주됩니까?

계속해서 다음과 같이 말합니다.

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

이것은 무엇을 의미 하는가?

그들은 예를 제시했습니다

class Collections {
    public static <T> void copy(List<T> dest, List<? extends T> src) {
    ...
}

[…]

와일드 카드를 전혀 사용하지 않고 다른 방법으로이 메서드에 대한 서명을 작성할 수 있습니다.

class Collections {
    public static <T, S extends T> void copy(List<T> dest, List<S> src) {
    ...
}

이 문서는 두 번째 선언을 권장하지 않고 첫 번째 구문의 사용을 장려합니까? 첫 번째 선언과 두 번째 선언의 차이점은 무엇입니까? 둘 다 같은 일을하는 것 같나요?

누군가이 영역에 빛을 비출 수 있습니까?



답변

와일드 카드와 유형 매개 변수가 동일한 작업을 수행하는 특정 위치가 있습니다. 그러나 유형 매개 변수를 사용해야하는 특정 위치도 있습니다.

  1. 다른 유형의 메서드 인수에 일부 관계를 적용하려면 와일드 카드를 사용할 수 없으며 형식 매개 변수를 사용해야합니다.

메소드를 예로 들어, 메소드에 전달 된 srcdest목록 copy()이 동일한 매개 변수 유형이어야 한다고 가정하면 다음 과 같은 유형 매개 변수로 수행 할 수 있습니다.

public static <T extends Number> void copy(List<T> dest, List<T> src)

여기에서 dest와 둘 다 에 src대해 동일한 매개 변수화 된 유형이 있는지 확인 List합니다. 따라서에서 src으로 요소를 복사하는 것이 안전 합니다 dest.

그러나 계속해서 와일드 카드를 사용하는 방법을 변경하면 :

public static void copy(List<? extends Number> dest, List<? extends Number> src)

예상대로 작동하지 않습니다. 두번째 경우에, 당신은 통과 할 수 List<Integer>List<Float> as dest및을src . 따라서 요소를에서 로로 이동 src하는 dest것은 더 이상 형식에 안전하지 않습니다. 이런 종류의 관계가 필요하지 않으면 유형 매개 변수를 전혀 사용하지 않아도됩니다.

와일드 카드와 유형 매개 변수 사용의 다른 차이점은 다음과 같습니다.

  • 매개 변수화 된 유형 인수가 하나만있는 경우 유형 매개 변수도 작동하지만 와일드 카드를 사용할 수 있습니다.
  • 유형 매개 변수는 다중 경계를 지원하지만 와일드 카드는 지원하지 않습니다.
  • 와일드 카드는 상한과 하한을 모두 지원하고 유형 매개 변수는 상한 만 지원합니다. 따라서 List유형 Integer또는 수퍼 클래스 를 사용하는 메소드를 정의 하려면 다음을 수행 할 수 있습니다.

    public void print(List<? super Integer> list)  // OK

    그러나 유형 매개 변수를 사용할 수 없습니다.

     public <T super Integer> void print(List<T> list)  // Won't compile

참조 :


답변

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입니다. 첫 번째 방법은 책에 없습니다.


답변

첫 번째 질문에서 매개 변수의 유형과 메서드의 반환 유형 사이에 관계가 있으면 제네릭을 사용하십시오.

예를 들면 :

public <T> T giveMeMaximum(Collection<T> items);
public <T> Collection<T> applyFilter(Collection<T> items);

여기서 특정 기준에 따라 T의 일부를 추출합니다. T가 Long있으면 메서드가 반환 Long되고 Collection<Long>; 실제 반환 유형은 매개 변수 유형에 따라 다르므로 제네릭 유형을 사용하는 것이 유용하고 권장됩니다.

그렇지 않은 경우 와일드 카드 유형을 사용할 수 있습니다.

public int count(Collection<?> items);
public boolean containsDuplicate(Collection<?> items);

이 두 예에서 컬렉션의 항목 유형이 무엇이든 반환 유형은 intboolean입니다.

귀하의 예에서 :

interface Collection<E> {
    public boolean containsAll(Collection<?> c);
    public boolean addAll(Collection<? extends E> c);
}

이 두 함수는 컬렉션의 항목 유형에 관계없이 부울을 반환합니다. 두 번째 경우에는 E의 하위 클래스 인스턴스로 제한됩니다.

두 번째 질문 :

class Collections {
    public static <T> void copy(List<T> dest, List<? extends T> src) {
    ...
}

이 첫 번째 코드를 사용하면 이기종 List<? extends T> src을 매개 변수로 전달할 수 있습니다 . 이 목록은 모두 기본 클래스 T를 확장하는 한 서로 다른 클래스의 여러 요소를 포함 할 수 있습니다.

만약 당신이 :

interface Fruit{}

class Apple implements Fruit{}
class Pear implements Fruit{}
class Tomato implements Fruit{}

당신은 할 수 있습니다

List<? extends Fruit> basket = new ArrayList<? extends Fruit>();
basket.add(new Apple());
basket.add(new Pear());
basket.add(new Tomato());
List<Fruit> fridge = new ArrayList<Fruit>();

Collections.copy(fridge, basket);// works 

반면에

class Collections {
    public static <T, S extends T> void copy(List<T> dest, List<S> src) {
    ...
}

List<S> srcT의 하위 클래스 인 하나의 특정 클래스 S로 제한 합니다. 목록에는 T도 구현하더라도 한 클래스 (이 인스턴스에서는 S)의 요소 만 포함 할 수 있고 다른 클래스는 포함 할 수 없습니다. 이전 예제를 사용할 수는 없지만 다음과 같이 할 수 있습니다.

List<Apple> basket = new ArrayList<Apple>();
basket.add(new Apple());
basket.add(new Apple());
basket.add(new Apple());
List<Fruit> fridge = new ArrayList<Fruit>();

Collections.copy(fridge, basket); /* works since the basket is defined as a List of apples and not a list of some fruits. */


답변

와일드 카드 메소드도 일반적입니다. 일부 유형의 유형으로 호출 할 수 있습니다.

<T>구문 형태 변수의 이름을 정의합니다. 타입 변수가 어떤 용도로든 사용된다면 (예 : 메소드 구현이나 다른 타입에 대한 제약으로), 이름을 지정하는 것이 합리적입니다. 그렇지 않으면 다음을 사용할 수 있습니다.? 익명 변수로 . 그래서, 그냥 지름길처럼 보입니다.

또한 ?필드를 선언 할 때 구문을 피할 수 없습니다.

class NumberContainer
{
 Set<? extends Number> numbers;
}


답변

나는 당신의 질문에 하나씩 대답하려고 노력할 것입니다.

와 같은 와일드 카드 (Collection<? extends E> c);가 일종의 다형성을 지원 한다고 생각하지 않습니까?

아니요. 그 이유는 제한된 와일드 카드 에 정의 된 매개 변수 유형이 없기 때문입니다 . 알려지지 않은 것입니다. “아는”모든 것은 “격리”가 유형이라는 것입니다.E (정의 된 것)이라는 것입니다. 따라서 제공된 값이 제한된 유형과 일치하는지 여부를 확인하고 정당화 할 수 없습니다.

따라서 와일드 카드에서 다형성 동작을 갖는 것은 합리적이지 않습니다.

이 문서는 두 번째 선언을 권장하지 않고 첫 번째 구문의 사용을 장려합니까? 첫 번째 선언과 두 번째 선언의 차이점은 무엇입니까? 둘 다 같은 일을하는 것 같나요?

이 경우 첫 번째 옵션은 T항상 경계가있는 것처럼 더 좋으며 source하위 클래스의 값 (알 수없는 값)을 확실히 갖게됩니다.T .

따라서 모든 숫자 목록을 복사한다고 가정하면 첫 번째 옵션은

Collections.copy(List<Number> dest, List<? extends Number> src);

src본질적으로 수용 할 수 List<Double>, List<Float>파라미터 화 된 타입에서 발견 할 수있는 상한이 있으므로 등에dest .

두 번째 옵션은 S복사하려는 모든 유형에 대해 바인딩하도록 강제합니다.

//For double 
Collections.copy(List<Number> dest, List<Double> src); //Double extends Number.

//For int
Collections.copy(List<Number> dest, List<Integer> src); //Integer extends Number.

같이 S 바인딩이 필요한 파라미터 화 된 형태이다.

이게 도움이 되길 바란다.


답변

여기에 나열되지 않은 또 다른 차이점이 있습니다.

static <T> void fromArrayToCollection(T[] a, Collection<T> c) {
    for (T o : a) {
        c.add(o); // correct
    }
}

그러나 다음과 같은 경우 컴파일 시간 오류가 발생합니다.

static <T> void fromArrayToCollection(T[] a, Collection<?> c) {
    for (T o : a) {
        c.add(o); // compile time error
    }
}


답변

내가 아는 한 와일드 카드가 엄격하게 필요한 경우는 단 하나의 사용 사례가 있습니다 (즉, 명시 적 유형 매개 변수를 사용하여 표현할 수없는 것을 표현할 수 있음). 하한을 지정해야 할 때입니다.

그 외에도 언급 한 문서의 다음 명령문에 설명 된대로 와일드 카드는 더 간결한 코드를 작성하는 데 사용됩니다.

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

[…]

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

[…]

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