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) { ... }
이 문서는 두 번째 선언을 권장하지 않고 첫 번째 구문의 사용을 장려합니까? 첫 번째 선언과 두 번째 선언의 차이점은 무엇입니까? 둘 다 같은 일을하는 것 같나요?
누군가이 영역에 빛을 비출 수 있습니까?
답변
와일드 카드와 유형 매개 변수가 동일한 작업을 수행하는 특정 위치가 있습니다. 그러나 유형 매개 변수를 사용해야하는 특정 위치도 있습니다.
- 다른 유형의 메서드 인수에 일부 관계를 적용하려면 와일드 카드를 사용할 수 없으며 형식 매개 변수를 사용해야합니다.
메소드를 예로 들어, 메소드에 전달 된 src
및 dest
목록 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);
이 두 예에서 컬렉션의 항목 유형이 무엇이든 반환 유형은 int
및 boolean
입니다.
귀하의 예에서 :
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> src
T의 하위 클래스 인 하나의 특정 클래스 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
}
}
답변
내가 아는 한 와일드 카드가 엄격하게 필요한 경우는 단 하나의 사용 사례가 있습니다 (즉, 명시 적 유형 매개 변수를 사용하여 표현할 수없는 것을 표현할 수 있음). 하한을 지정해야 할 때입니다.
그 외에도 언급 한 문서의 다음 명령문에 설명 된대로 와일드 카드는 더 간결한 코드를 작성하는 데 사용됩니다.
일반 메서드를 사용하면 형식 매개 변수를 사용하여 메서드 및 / 또는 반환 형식에 대한 하나 이상의 인수 형식 간의 종속성을 표현할 수 있습니다. 이러한 종속성이 없으면 제네릭 메서드를 사용하면 안됩니다.
[…]
와일드 카드를 사용하는 것이 명시 적 유형 매개 변수를 선언하는 것보다 더 명확하고 간결하므로 가능할 때마다 선호해야합니다.
[…]
또한 와일드 카드는 필드, 지역 변수 및 배열의 유형으로 메서드 서명 외부에서 사용할 수 있다는 장점이 있습니다.