Java 대신 제네릭이 필요할 때 그리고 스위칭의 단점이 있습니까?
다음 예에서 Hamcrest 매처와 함께 JUnit 사용 :
Map<String, Class<? extends Serializable>> expected = null;
Map<String, Class<java.util.Date>> result = null;
assertThat(result, is(expected));
다음과 같은 JUnit assertThat메소드 서명으로 컴파일되지 않습니다 .
public static <T> void assertThat(T actual, Matcher<T> matcher)
컴파일러 오류 메시지는 다음과 같습니다.
Error:Error:line (102)cannot find symbol method
assertThat(java.util.Map<java.lang.String,java.lang.Class<java.util.Date>>,
org.hamcrest.Matcher<java.util.Map<java.lang.String,java.lang.Class
<? extends java.io.Serializable>>>)
그러나 assertThat메소드 서명을 다음과 같이 변경하면
public static <T> void assertThat(T result, Matcher<? extends T> matcher)
그런 다음 컴파일이 작동합니다.
따라서 세 가지 질문이 있습니다.
- 현재 버전이 정확히 컴파일되지 않는 이유는 무엇입니까? 여기서 공분산 문제를 모호하게 이해했지만, 필요한 경우 설명 할 수 없었습니다.
assertThat방법을 변경하면 단점 이Matcher<? extends T>있습니까? 그렇게하면 깨질 다른 경우가 있습니까?assertThatJUnit 에서 메소드 의 일반화에 대한 요점이 있습니까?MatcherJUnit을 아무것도하지 않는 형태의 안전을 강제하기 위해 원하는 일반, 그냥 외모에 입력되지 않은 일치하는 메소드를 호출하기 때문에이 같은 클래스는, 그것을 필요로하지 않는 것Matcher사실 만하지 않습니다 일치하면 테스트가 실패합니다. 안전하지 않은 작업이 필요하지 않습니다 (또는 그렇게 보입니다).
참고로 다음은 JUnit 구현입니다 assertThat.
public static <T> void assertThat(T actual, Matcher<T> matcher) {
assertThat("", actual, matcher);
}
public static <T> void assertThat(String reason, T actual, Matcher<T> matcher) {
if (!matcher.matches(actual)) {
Description description = new StringDescription();
description.appendText(reason);
description.appendText("\nExpected: ");
matcher.describeTo(description);
description
.appendText("\n got: ")
.appendValue(actual)
.appendText("\n");
throw new java.lang.AssertionError(description.toString());
}
}
먼저 http://www.angelikalanger.com/GenericsFAQ/JavaGenericsFAQ.html 로 안내해야합니다 . 그녀는 놀라운 일을합니다.
기본 아이디어는 당신이 사용하는 것입니다
<T extends SomeClass>
실제 매개 변수가 될 수 SomeClass있거나 하위 유형일 수 있습니다 .
귀하의 예에서
Map<String, Class<? extends Serializable>> expected = null;
Map<String, Class<java.util.Date>> result = null;
assertThat(result, is(expected));
당신은 expected을 구현하는 모든 클래스를 나타내는 Class 객체를 포함 할 수 있다고 말하고 있습니다 Serializable. 결과 맵은 Date클래스 객체 만 보유 할 수 있다고 말합니다 .
당신이 결과를 전달하면있는 거 설정 T정확히 Map의 String에 Date일치하지 않는 클래스 객체 Map의 String어떤이의에 Serializable.
한 가지 확인해야 할 사항-확실 Class<Date>하지 Date않습니까? 의지도 String로는 Class<Date>일반적으로 대단히 유용 소리가 나지 않는다 (이 보유 할 수있는 모든입니다 Date.class값이 아닌 인스턴스로 Date)
일반화 assertThat와 관련 하여이 메소드는 메소드가 Matcher결과 유형에 맞는가 전달 되도록 보장 할 수 있습니다 .
Thanks to everyone who answered the question, it really helped clarify things for me. In the end Scott Stanchfield's answer got the closest to how I ended up understanding it, but since I didn't understand him when he first wrote it, I am trying to restate the problem so that hopefully someone else will benefit.
I'm going to restate the question in terms of List, since it has only one generic parameter and that will make it easier to understand.
The purpose of the parametrized class (such as List<Date> or Map<K, V> as in the example) is to force a downcast and to have the compiler guarantee that this is safe (no runtime exceptions).
Consider the case of List. The essence of my question is why a method that takes a type T and a List won't accept a List of something further down the chain of inheritance than T. Consider this contrived example:
List<java.util.Date> dateList = new ArrayList<java.util.Date>();
Serializable s = new String();
addGeneric(s, dateList);
....
private <T> void addGeneric(T element, List<T> list) {
list.add(element);
}
This will not compile, because the list parameter is a list of dates, not a list of strings. Generics would not be very useful if this did compile.
The same thing applies to a Map<String, Class<? extends Serializable>> It is not the same thing as a Map<String, Class<java.util.Date>>. They are not covariant, so if I wanted to take a value from the map containing date classes and put it into the map containing serializable elements, that is fine, but a method signature that says:
private <T> void genericAdd(T value, List<T> list)
Wants to be able to do both:
T x = list.get(0);
and
list.add(value);
In this case, even though the junit method doesn't actually care about these things, the method signature requires the covariance, which it is not getting, therefore it does not compile.
On the second question,
Matcher<? extends T>
Would have the downside of really accepting anything when T is an Object, which is not the APIs intent. The intent is to statically ensure that the matcher matches the actual object, and there is no way to exclude Object from that calculation.
The answer to the third question is that nothing would be lost, in terms of unchecked functionality (there would be no unsafe typecasting within the JUnit API if this method was not genericized), but they are trying to accomplish something else - statically ensure that the two parameters are likely to match.
EDIT (after further contemplation and experience):
One of the big issues with the assertThat method signature is attempts to equate a variable T with a generic parameter of T. That doesn't work, because they are not covariant. So for example you may have a T which is a List<String> but then pass a match that the compiler works out to Matcher<ArrayList<T>>. Now if it wasn't a type parameter, things would be fine, because List and ArrayList are covariant, but since Generics, as far as the compiler is concerned require ArrayList, it can't tolerate a List for reasons that I hope are clear from the above.
It boils down to:
Class<? extends Serializable> c1 = null;
Class<java.util.Date> d1 = null;
c1 = d1; // compiles
d1 = c1; // wont compile - would require cast to Date
You can see the Class reference c1 could contain a Long instance (since the underlying object at a given time could have been List<Long>), but obviously cannot be cast to a Date since there is no guarantee that the "unknown" class was Date. It is not typsesafe, so the compiler disallows it.
However, if we introduce some other object, say List (in your example this object is Matcher), then the following becomes true:
List<Class<? extends Serializable>> l1 = null;
List<Class<java.util.Date>> l2 = null;
l1 = l2; // wont compile
l2 = l1; // wont compile
...However, if the type of the List becomes ? extends T instead of T....
List<? extends Class<? extends Serializable>> l1 = null;
List<? extends Class<java.util.Date>> l2 = null;
l1 = l2; // compiles
l2 = l1; // won't compile
I think by changing Matcher<T> to Matcher<? extends T>, you are basically introducing the scenario similar to assigning l1 = l2;
It's still very confusing having nested wildcards, but hopefully that makes sense as to why it helps to understand generics by looking at how you can assign generic references to each other. It's also further confusing since the compiler is inferring the type of T when you make the function call (you are not explicitly telling it was T is).
The reason your original code doesn't compile is that <? extends Serializable> does not mean, "any class that extends Serializable," but "some unknown but specific class that extends Serializable."
For example, given the code as written, it is perfectly valid to assign new TreeMap<String, Long.class>()> to expected. If the compiler allowed the code to compile, the assertThat() would presumably break because it would expect Date objects instead of the Long objects it finds in the map.
One way for me to understand wildcards is to think that the wildcard isn't specifying the type of the possible objects that given generic reference can "have", but the type of other generic references that it is is compatible with (this may sound confusing...) As such, the first answer is very misleading in it's wording.
In other words, List<? extends Serializable> means you can assign that reference to other Lists where the type is some unknown type which is or a subclass of Serializable. DO NOT think of it in terms of A SINGLE LIST being able to hold subclasses of Serializable (because that is incorrect semantics and leads to a misunderstanding of Generics).
I know this is an old question but I want to share an example that I think explains bounded wildcards pretty well. java.util.Collections offers this method:
public static <T> void sort(List<T> list, Comparator<? super T> c) {
list.sort(c);
}
If we have a List of T, the List can, of course, contain instances of types that extend T. If the List contains Animals, the List can contain both Dogs and Cats (both Animals). Dogs have a property "woofVolume" and Cats have a property "meowVolume." While we might like to sort based upon these properties particular to subclasses of T, how can we expect this method to do that? A limitation of Comparator is that it can compare only two things of only one type (T). So, requiring simply a Comparator<T> would make this method usable. But, the creator of this method recognized that if something is a T, then it is also an instance of the superclasses of T. Therefore, he allows us to use a Comparator of T or any superclass of T, i.e. ? super T.
what if you use
Map<String, ? extends Class<? extends Serializable>> expected = null;
'IT' 카테고리의 다른 글
| LINQ에 대한 학습 (0) | 2020.05.15 |
|---|---|
| HTML / CSS / JavaScript를 사용하여 데스크탑 앱을 개발하는 방법은 무엇입니까? (0) | 2020.05.15 |
| 머큐리얼 이클립스 플러그인 (0) | 2020.05.15 |
| virtualenv가 글로벌 사이트 패키지에서 특정 패키지를 상속 받도록하십시오. (0) | 2020.05.15 |
| D3 데이텀과 데이터의 차이점은 무엇입니까? (0) | 2020.05.15 |