IT TIP

제네릭 메소드의 여러 와일드 카드는 Java 컴파일러 (그리고 나!)를 매우 혼란스럽게 만듭니다.

itqueen 2020. 12. 12. 12:54
반응형

제네릭 메소드의 여러 와일드 카드는 Java 컴파일러 (그리고 나!)를 매우 혼란스럽게 만듭니다.


먼저 간단한 시나리오를 고려해 보겠습니다 ( ideone.com의 전체 소스 참조 ).

import java.util.*;

public class TwoListsOfUnknowns {
    static void doNothing(List<?> list1, List<?> list2) { }

    public static void main(String[] args) {
        List<String> list1 = null;
        List<Integer> list2 = null;
        doNothing(list1, list2); // compiles fine!
    }
}

두 와일드 카드는 서로 관련이 없으므로 doNothinga List<String>및 a를 사용하여 호출 할 수 있습니다 List<Integer>. 즉, 둘 ?은 완전히 다른 유형을 참조 할 수 있습니다. 따라서 다음은 컴파일되지 않으며 예상되는 것입니다 ( ideone.com에서도 ).

import java.util.*;

public class TwoListsOfUnknowns2 {
    static void doSomethingIllegal(List<?> list1, List<?> list2) {
        list1.addAll(list2); // DOES NOT COMPILE!!!
            // The method addAll(Collection<? extends capture#1-of ?>)
            // in the type List<capture#1-of ?> is not applicable for
            // the arguments (List<capture#2-of ?>)
    }
}

지금까지는 훌륭했지만 여기에서 매우 혼란스러워지기 시작합니다 ( ideone.com에서 볼 수 있음 ).

import java.util.*;

public class LOLUnknowns1 {
    static void probablyIllegal(List<List<?>> lol, List<?> list) {
        lol.add(list); // this compiles!! how come???
    }
}

위의 코드는 Eclipse와 sun-jdk-1.6.0.17ideone.com에서 저를 위해 컴파일 되지만 그래야합니까? a List<List<Integer>> lol와 a List<String> list와 유사한 두 개의 관련되지 않은 와일드 카드 상황이있을 수 TwoListsOfUnknowns없습니까?

실제로 그 방향에 대한 다음과 같은 약간의 수정은 컴파일되지 않습니다. 이는 예상 할 수 있습니다 ( ideone.com에서 볼 수 있음 ).

import java.util.*;

public class LOLUnknowns2 {
    static void rightfullyIllegal(
            List<List<? extends Number>> lol, List<?> list) {

        lol.add(list); // DOES NOT COMPILE! As expected!!!
            // The method add(List<? extends Number>) in the type
            // List<List<? extends Number>> is not applicable for
            // the arguments (List<capture#1-of ?>)
    }
}

따라서 컴파일러가 작업을 수행하는 것처럼 보이지만 다음을 얻습니다 ( ideone.com에서 볼 수 있음 ).

import java.util.*;

public class LOLUnknowns3 {
    static void probablyIllegalAgain(
            List<List<? extends Number>> lol, List<? extends Number> list) {

        lol.add(list); // compiles fine!!! how come???
    }
}

다시, 우리는 예를 들어 a List<List<Integer>> lol와 a를 가질 수 있습니다 List<Float> list. 그래서 이것은 컴파일되지 않아야합니다. 그렇죠?

사실, 더 단순한 LOLUnknowns1(두 개의 제한되지 않은 와일드 카드) 로 돌아가서 실제로 probablyIllegal어떤 식 으로든 호출 할 수 있는지 살펴 보겠습니다 . 먼저 "쉬운"경우를 시도하고 두 개의 와일드 카드에 대해 동일한 유형을 선택합니다 ( ideone.com에서 볼 수 있음 ).

import java.util.*;

public class LOLUnknowns1a {
    static void probablyIllegal(List<List<?>> lol, List<?> list) {
        lol.add(list); // this compiles!! how come???
    }

    public static void main(String[] args) {
        List<List<String>> lol = null;
        List<String> list = null;
        probablyIllegal(lol, list); // DOES NOT COMPILE!!
            // The method probablyIllegal(List<List<?>>, List<?>)
            // in the type LOLUnknowns1a is not applicable for the
            // arguments (List<List<String>>, List<String>)
    }
}

말이 안 돼! 여기서 우리는 두 가지 다른 유형을 사용하려고 시도하지도 않고 컴파일되지도 않습니다! 그것을 List<List<Integer>> lol만들고 List<String> list또한 유사한 컴파일 오류를 제공합니다! 실제로 내 실험에서 코드가 컴파일되는 유일한 방법은 첫 번째 인수가 명시 적 null유형 인 경우입니다 ( ideone.com에서 볼 수 있음 ).

import java.util.*;

public class LOLUnknowns1b {
    static void probablyIllegal(List<List<?>> lol, List<?> list) {
        lol.add(list); // this compiles!! how come???
    }

    public static void main(String[] args) {
        List<String> list = null;
        probablyIllegal(null, list); // compiles fine!
            // throws NullPointerException at run-time
    }
}

그래서 질문에 관해서입니다 LOLUnknowns1, LOLUnknowns1a그리고 LOLUnknowns1b:

  • 어떤 유형의 인수가 probablyIllegal허용됩니까?
  • lol.add(list);전혀 컴파일 해야합니까 ? 형식이 안전한가요?
  • 이것은 컴파일러 버그입니까, 아니면 와일드 카드에 대한 캡처 변환 규칙을 오해하고 있습니까?

부록 A : 이중 LOL?

궁금한 사람이 있으면 잘 컴파일됩니다 ( ideone.com에서 볼 수 있음 ).

import java.util.*;

public class DoubleLOL {
    static void omg2xLOL(List<List<?>> lol1, List<List<?>> lol2) {
        // compiles just fine!!!
        lol1.addAll(lol2);
        lol2.addAll(lol1);
    }
}

부록 B : 중첩 된 와일드 카드-실제로 무엇을 의미합니까 ???

추가 조사에 따르면 다중 와일드 카드는 문제와 관련이 없지만 중첩 된 와일드 카드가 혼동의 원인이 될 수 있습니다.

import java.util.*;

public class IntoTheWild {

    public static void main(String[] args) {
        List<?> list = new ArrayList<String>(); // compiles fine!

        List<List<?>> lol = new ArrayList<List<String>>(); // DOES NOT COMPILE!!!
            // Type mismatch: cannot convert from
            // ArrayList<List<String>> to List<List<?>>
    }
}

그래서 아마도 a List<List<String>>List<List<?>>. 어떤 동안 사실, List<E>입니다 List<?>, 그것은 어떤처럼 보이지 않는 것은 List<List<E>>List<List<?>>( ideone.com에서 볼 때 ) :

import java.util.*;

public class IntoTheWild2 {
    static <E> List<?> makeItWild(List<E> list) {
        return list; // compiles fine!
    }
    static <E> List<List<?>> makeItWildLOL(List<List<E>> lol) {
        return lol;  // DOES NOT COMPILE!!!
            // Type mismatch: cannot convert from
            // List<List<E>> to List<List<?>>
    }
}

그러면 새로운 질문이 생깁니다 List<List<?>>.


부록 B에서 알 수 있듯이 이것은 여러 와일드 카드와는 아무런 관련이 없으며 오히려 List<List<?>>실제 의미를 오해하는 것입니다.

먼저 Java 제네릭이 불변한다는 것이 무엇을 의미하는지 상기시켜 봅시다.

  1. An IntegerNumber
  2. A는 List<Integer>것입니다 하지List<Number>
  3. A는 List<Integer> ISList<? extends Number>

이제 중첩 된 목록 상황에 동일한 인수를 적용합니다 (자세한 내용은 부록 참조) .

  1. A List<String>는 (캡처 가능) aList<?>
  2. A List<List<String>>아님 (캡처 가능) aList<List<?>>
  3. A List<List<String>> IS (캡처 가능)List<? extends List<?>>

이러한 이해를 바탕으로 질문의 모든 스 니펫을 설명 할 수 있습니다. 혼란은 (거짓)과 같은 유형의 것을 믿는 발생 List<List<?>>등의 캔 캡처 유형 List<List<String>>, List<List<Integer>>등이이 NOT 사실.

즉, a List<List<?>>:

  • 요소가 하나의 알 수없는 유형의 목록 인 목록 아닙니다 .
    • ... 그것은 List<? extends List<?>>
  • 대신 요소가 모든 유형 의 목록 인 목록입니다 .

짧은 발췌

다음은 위의 요점을 설명하는 스 니펫입니다.

List<List<?>> lolAny = new ArrayList<List<?>>();

lolAny.add(new ArrayList<Integer>());
lolAny.add(new ArrayList<String>());

// lolAny = new ArrayList<List<String>>(); // DOES NOT COMPILE!!

List<? extends List<?>> lolSome;

lolSome = new ArrayList<List<String>>();
lolSome = new ArrayList<List<Integer>>();

더 많은 스 니펫

다음은 바운드 중첩 와일드 카드가있는 또 다른 예입니다.

List<List<? extends Number>> lolAnyNum = new ArrayList<List<? extends Number>>();

lolAnyNum.add(new ArrayList<Integer>());
lolAnyNum.add(new ArrayList<Float>());
// lolAnyNum.add(new ArrayList<String>());     // DOES NOT COMPILE!!

// lolAnyNum = new ArrayList<List<Integer>>(); // DOES NOT COMPILE!!

List<? extends List<? extends Number>> lolSomeNum;

lolSomeNum = new ArrayList<List<Integer>>();
lolSomeNum = new ArrayList<List<Float>>();
// lolSomeNum = new ArrayList<List<String>>(); // DOES NOT COMPILE!!

질문으로 돌아 가기

질문의 스 니펫으로 돌아가려면 다음이 예상대로 작동합니다 ( ideone.com에서 볼 수 있음 ).

public class LOLUnknowns1d {
    static void nowDefinitelyIllegal(List<? extends List<?>> lol, List<?> list) {
        lol.add(list); // DOES NOT COMPILE!!!
            // The method add(capture#1-of ? extends List<?>) in the
            // type List<capture#1-of ? extends List<?>> is not 
            // applicable for the arguments (List<capture#3-of ?>)
    }
    public static void main(String[] args) {
        List<Object> list = null;
        List<List<String>> lolString = null;
        List<List<Integer>> lolInteger = null;

        // these casts are valid
        nowDefinitelyIllegal(lolString, list);
        nowDefinitelyIllegal(lolInteger, list);
    }
}

lol.add(list);우리가 a List<List<String>> lol및 a를 가질 수 있기 때문에 불법 List<Object> list입니다. 실제로 문제가되는 문을 주석 처리하면 코드가 컴파일되고 main.

문제의 모든 probablyIllegal방법은 불법이 아닙니다. 그들은 모두 완벽하게 합법적이고 형식이 안전합니다. 컴파일러에는 버그가 전혀 없습니다. 해야 할 일을 정확히하고 있습니다.


참고 문헌

Related questions


Appendix: The rules of capture conversion

(This was brought up in the first revision of the answer; it's a worthy supplement to the type invariant argument.)

5.1.10 Capture Conversion

Let G name a generic type declaration with n formal type parameters A1…An with corresponding bounds U1…Un. There exists a capture conversion from G<T1…Tn> to G<S1…Sn>, where, for 1 <= i <= n:

  1. If Ti is a wildcard type argument of the form ? then …
  2. If Ti is a wildcard type argument of the form ? extends Bi, then …
  3. If Ti is a wildcard type argument of the form ? super Bi, then …
  4. Otherwise, Si = Ti.

Capture conversion is not applied recursively.

This section can be confusing, especially with regards to the non-recursive application of the capture conversion (hereby CC), but the key is that not all ? can CC; it depends on where it appears. There is no recursive application in rule 4, but when rules 2 or 3 applies, then the respective Bi may itself be the result of a CC.

Let's work through a few simple examples:

  • List<?> can CC List<String>
    • The ? can CC by rule 1
  • List<? extends Number> can CC List<Integer>
    • The ? can CC by rule 2
    • In applying rule 2, Bi is simply Number
  • List<? extends Number> can NOT CC List<String>
    • The ? can CC by rule 2, but compile time error occurs due to incompatible types

Now let's try some nesting:

  • List<List<?>> can NOT CC List<List<String>>
    • Rule 4 applies, and CC is not recursive, so the ? can NOT CC
  • List<? extends List<?>> can CC List<List<String>>
    • The first ? can CC by rule 2
    • In applying rule 2, Bi is now a List<?>, which can CC List<String>
    • Both ? can CC
  • List<? extends List<? extends Number>> can CC List<List<Integer>>
    • The first ? can CC by rule 2
    • In applying rule 2, Bi is now a List<? extends Number>, which can CC List<Integer>
    • Both ? can CC
  • List<? extends List<? extends Number>> can NOT CC List<List<Integer>>
    • The first ? can CC by rule 2
    • In applying rule 2, Bi is now a List<? extends Number>, which can CC, but gives a compile time error when applied to List<Integer>
    • Both ? can CC

To further illustrate why some ? can CC and others can't, consider the following rule: you can NOT directly instantiate a wildcard type. That is, the following gives a compile time error:

    // WildSnippet1
    new HashMap<?,?>();         // DOES NOT COMPILE!!!
    new HashMap<List<?>, ?>();  // DOES NOT COMPILE!!!
    new HashMap<?, Set<?>>();   // DOES NOT COMPILE!!!

However, the following compiles just fine:

    // WildSnippet2
    new HashMap<List<?>,Set<?>>();            // compiles fine!
    new HashMap<Map<?,?>, Map<?,Map<?,?>>>(); // compiles fine!

The reason WildSnippet2 compiles is because, as explained above, none of the ? can CC. In WildSnippet1, either the K or the V (or both) of the HashMap<K,V> can CC, which makes the direct instantiation through new illegal.


  • No argument with generics should be accepted. In the case of LOLUnknowns1b the null is accepted as if the first argument was typed as List. For example this does compile :

    List lol = null;
    List<String> list = null;
    probablyIllegal(lol, list);
    
  • IMHO lol.add(list); shouldn't even compile but as lol.add() needs an argument of type List<?> and as list fits in List<?> it works.
    A strange example which make me think of this theory is :

    static void probablyIllegalAgain(List<List<? extends Number>> lol, List<? extends Integer> list) {
        lol.add(list); // compiles fine!!! how come???
    }
    

    lol.add() needs an argument of type List<? extends Number> and list is typed as List<? extends Integer>, it fits in. It won't work if it doesn't match. Same thing for the double LOL, and other nested wildcards, as long as the first capture matches the second one, everything is okay (and souldn't be).

  • Again, I'm not sure but it does really seem like a bug.

  • I'm glad to not be the only one to use lol variables all the time.

Resources :
http://www.angelikalanger.com, a FAQ about generics

EDITs :

  1. Added comment about the Double Lol
  2. And nested wildcards.

not an expert, but I think I can understand it.

let's change your example to something equivalent, but with more distinguishing types:

static void probablyIllegal(List<Class<?>> x, Class<?> y) {
    x.add(y); // this compiles!! how come???
}

let's change List to [] to be more illuminating:

static void probablyIllegal(Class<?>[] x, Class<?> y) {
    x.add(y); // this compiles!! how come???
}

now, x is not an array of some type of class. it is an array of any type of class. it can contain a Class<String> and a Class<Int>. this cannot be expressed with ordinary type parameter:

static<T> void probablyIllegal(Class<T>[] x  //homogeneous! not the same!

Class<?> is a super type of Class<T> for any T. If we think a type is a set of objects, set Class<?> is the union of all sets of Class<T> for all T. (does it include itselft? I dont know...)

참고URL : https://stackoverflow.com/questions/3546745/multiple-wildcards-on-a-generic-methods-makes-java-compiler-and-me-very-confu

반응형