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


122

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) {
    ...
}

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

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

답변:


173

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

  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

참조 :


1
이것은 이상한 대답입니다. 왜 사용해야하는지 설명하지 않습니다 ?. `public static <T1 extends Number, T2 extends Number> void copy (List <T1> dest, List <T2> src)로 다시 작성할 수 있으며이 경우 무슨 일이 일어나고 있는지 분명해집니다.
kan

@kan. 그게 진짜 문제입니다. 유형 매개 변수를 사용하여 동일한 유형을 적용 할 수 있지만 와일드 카드로는이를 수행 할 수 없습니다. 유형 매개 변수에 대해 두 가지 유형을 사용하는 것은 다릅니다.
Rohit Jain

1
@ 벤츠. List유형 매개 변수를 사용하여 하한을 정의 할 수 없습니다 . List<T super Integer>유효하지 않으며 컴파일되지 않습니다.
Rohit Jain

2
@ 벤츠. 천만에요 :) 마지막에 게시 한 링크를 통해 이동하는 것이 좋습니다. Generics에서 얻을 수있는 최고의 리소스입니다.
Rohit Jain

3
@ jorgen.ringen- <T extends X & Y>> 다중 경계.
Rohit Jain

12

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


나는 책의 인용을 위해 투표했다, 그것은 직접적이고 간결하다
Kurapika

9

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

예를 들면 :

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. */

1
List<? extends Fruit> basket = new ArrayList<? extends Fruit>();유효한 구문이 아닙니다. 경계없이 ArrayList를 인스턴스화해야합니다.
Arnold Pistorius 2015

바구니가 배 목록 일 수 있으므로 위의 예에서 바구니에 사과를 추가 할 수 없습니다. 잘못된 예 AFAIK. 그리고 컴파일도하지 않습니다.
Khanna111

1
@ArnoldPistorius 저를 혼란스럽게 만듭니다. ArrayList의 API 문서를 확인했고 서명 된 생성자가 ArrayList(Collection<? extends E> c)있습니다. 왜 그렇게 말했는지 설명해 주시겠습니까?
Kurapika

@Kurapika 이전 Java 버전을 사용하고 있었을 수 있습니까? 댓글은 거의 3 년 전에 게시되었습니다.
Arnold Pistorius

2

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

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

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

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

3
댓글이 아니어야하나요?
Buhake Sindi

@BuhakeSindi 죄송합니다. 무엇이 명확하지 않습니까? 왜 -1? 질문에 대한 답이라고 생각합니다.
kan

2

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

와 같은 와일드 카드 (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 바인딩이 필요한 파라미터 화 된 형태이다.

이게 도움이 되길 바란다.


당신은 당신이 당신의 마지막 단락에 뜻 무엇을 설명 할 수
벤츠

...... 하나를 바인딩을 강제로 두번째 옵션을 언급 한 당신은 그 위에 정교한 수
벤츠

<S extends T>미국 S의 서브 클래스 파라미터 화 된 형태이다는 T, 그래서의 서브 클래스 파라미터 화 된 형태 (NO 와일드 카드)가 필요합니다 T.
Buhake Sindi

2

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

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

0

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

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

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

[...]

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

[...]

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


0

주로-> 와일드 카드는 Non-Generic 메서드의 매개 변수 / 인수 수준에서 제네릭을 적용합니다. 노트. 기본적으로 genericMethod에서 수행 할 수도 있지만 여기에서는? T 자체를 사용할 수 있습니다.

패키지 제네릭;

public class DemoWildCard {


    public static void main(String[] args) {
        DemoWildCard obj = new DemoWildCard();

        obj.display(new Person<Integer>());
        obj.display(new Person<String>());

    }

    void display(Person<?> person) {
        //allows person of Integer,String or anything
        //This cannnot be done if we use T, because in that case we have to make this method itself generic
        System.out.println(person);
    }

}

class Person<T>{

}

SO 와일드 카드에는 이와 같은 특정 사용 사례가 있습니다.

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