Java 범용 요약(3): 와일드카드 사용

소개
앞의 두 문장은 범용 기본 사용법, 유형 지우기범용 배열를 소개했다.일반적인 사용에서 또 하나의 중요한 것을 어댑터라고 하는데 본고는 어댑터의 사용을 소개한다.
이 시리즈의 다른 두 문장:
  • Java 범용 요약(一): 기본 사용법 및 유형 지우기
  • Java 범용 요약(二): 범용과 수조
  • 수조의 협동
    어댑터를 이해하기 전에 먼저 수조를 알아보자.Java의 수조는 협동하는 것입니다. 무슨 뜻입니까?다음 예제를 참조하십시오.
    
    class Fruit {}
    class Apple extends Fruit {}
    class Jonathan extends Apple {}
    class Orange extends Fruit {}
    public class CovariantArrays {
     public static void main(String[] args) { 
     Fruit[] fruit = new Apple[10];
     fruit[0] = new Apple(); // OK
     fruit[1] = new Jonathan(); // OK
     // Runtime type is Apple[], not Fruit[] or Orange[]:
     try {
      // Compiler allows you to add Fruit:
      fruit[0] = new Fruit(); // ArrayStoreException
     } catch(Exception e) { System.out.println(e); }
     try {
      // Compiler allows you to add Oranges:
      fruit[0] = new Orange(); // ArrayStoreException
     } catch(Exception e) { System.out.println(e); }
     }
    } /* Output:
    java.lang.ArrayStoreException: Fruit
    java.lang.ArrayStoreException: Orange
    *///:~
    
    main 방법의 첫 번째 줄은 Apple 그룹을 만들어서 Fruit 그룹에 인용합니다.이것은 의미가 있다. Apple은 Fruit의 하위 클래스이고 하나의 Apple 대상도 일종의 Fruit 대상이기 때문에 하나의 Apple 수조도 일종의 Fruit 수조이다.이것은 수조의 협동이라고 하는데 자바는 수조를 협동으로 설계했는데 이에 대해 논란이 있고 어떤 사람들은 이것이 일종의 결함이라고 생각한다.
    비록 Apple[]은 Fruit[]로'위로 전환'할 수 있지만, 그룹 요소의 실제 유형은 Apple입니다. 우리는 그룹에 Apple이나 Apple의 하위 클래스만 넣을 수 있습니다.위의 코드에서 그룹에Fruit 객체와 Orange 객체를 넣었습니다.컴파일러의 경우 이것은 컴파일할 수 있지만 실행 시기에 JVM은 수조의 실제 유형이 Apple[]이라는 것을 알 수 있기 때문에 다른 대상이 수조에 가입할 때 이상을 던진다.
    범용 디자인의 목적 중 하나는 이런 운행 시기의 오류를 컴파일러에서 발견할 수 있도록 하는 것이다. 범용 용기류로 수조를 대체하면 무슨 일이 일어날지 보자.
    
    // Compile Error: incompatible types:
    ArrayList<Fruit> flist = new ArrayList<Apple>();
    위의 코드는 아예 컴파일할 수 없다.범주형에 관련되었을 때, 비록 Apple은 Fruit의 하위 유형이지만, Array List < Apple > 은 Array List < Fruit > 의 하위 유형이 아니며, 범주형은 협동을 지원하지 않습니다.
    와일드카드 사용
    위에서 알 수 있듯이 List<Number> list = ArrayList<Integer> 이러한 문장은 인티거가 Number의 하위 유형임에도 불구하고 컴파일할 수 없다.그렇다면 만약에 우리가 이런'상향적 전환'의 관계를 맺어야 한다면 어떻게 해야 합니까?이것은 어댑터가 역할을 발휘해야 한다.
    상단 경계 어댑터
    이용 형식의 어댑터로 범용 상향 전환을 실현할 수 있습니다.
    
    public class GenericsAndCovariance {
     public static void main(String[] args) {
     // Wildcards allow covariance:
     List<? extends Fruit> flist = new ArrayList<Apple>();
     // Compile Error: can't add any type of object:
     // flist.add(new Apple());
     // flist.add(new Fruit());
     // flist.add(new Object());
     flist.add(null); // Legal but uninteresting
     // We know that it returns at least Fruit:
     Fruit f = flist.get(0);
     }
    }
    위의 예에서 flist의 유형은 List<? extends Fruit> 우리는 그것을 하나의 유형의 List라고 읽을 수 있다. 이 유형은 Fruit의 어떤 유형을 계승할 수 있다.이 목록이 Fruit의 모든 유형을 가질 수 있다는 것은 아닙니다.어댑터는 특정한 유형을 대표한다.'어떤 특정한 유형을 나타내지만flist는 지정하지 않았다'는 뜻이다.이렇게 하는 것은 이해하기 어렵다. 구체적으로 이 예에 대한 해석은 flist 인용은 특정한 유형의 List를 가리킬 수 있다. 이 유형이 Fruit에서 계승되기만 하면 Fruit나 Apple, 예를 들어 예중의 new ArrayList<Apple>일 수 있지만 flist로 전환하기 위해 flist는 이 구체적인 유형이 무엇인지에 대해 관심을 가지지 않는다.
    위에서 말한 바와 같이 어댑터List<? extends Fruit>는 특정한 유형(Fruit 또는 그 하위 클래스)의 List를 표시하지만 이 실제 유형이 도대체 무엇인지는 관심이 없다. 어쨌든 Fruit의 하위 유형이고 Fruit는 그 상위 경계이다.그러면 이런 리스트에 대해서 저희가 뭘 할 수 있을까요?사실 이 리스트가 도대체 어떤 유형을 가지고 있는지 모르면 어떻게 안전하게 대상을 추가할 수 있습니까?위의 코드에서flist에 모든 대상을 추가합니다. Apple이든 Orange든 Fruit 대상이든 컴파일러가 허용하지 않습니다. 유일하게 추가할 수 있는 것은null입니다.그래서 만약에 범용적인 상향 전환List<? extends Fruit> flist = new ArrayList<Apple>()을 한다면 우리는 이 리스트에 어떤 대상을 추가할 수 있는 능력을 잃게 될 것이다. 설령 Object라 하더라도 안 된다.
    다른 한편, Fruit로 돌아가는 방법을 사용하면 안전합니다.이 목록에서 실제 유형이 무엇이든지 간에 Fruit로 바뀔 수 있다는 것을 알고 있기 때문에 컴파일러는 Fruit로 되돌아갈 수 있습니다.
    어댑터의 작용과 제한을 알게 된 후, 매개 변수를 받아들이는 어떤 방법도 우리는 호출할 수 없을 것 같다.사실은 그렇지 않다. 아래의 예를 봐라.
    
    public class CompilerIntelligence {
     public static void main(String[] args) {
     List<? extends Fruit> flist =
     Arrays.asList(new Apple());
     Apple a = (Apple)flist.get(0); // No warning
     flist.contains(new Apple()); // Argument is ‘Object'
     flist.indexOf(new Apple()); // Argument is ‘Object'
     //flist.add(new Apple());  
     }
    }
    
    위의 예에서flist의 유형은 List<? extends Fruit>입니다. 범용 매개 변수는 제한된 어댑터를 사용했기 때문에 우리는 그 안에 어떤 유형의 대상을 넣는 예를 잃어버렸고 마지막 코드는 컴파일할 수 없습니다.
    그러나flist는contains와indexOf 방법을 호출할 수 있으며, 모두 애플 대상을 매개 변수로 받아들였다.ArrayList의 원본 코드를 보면 add()는 일반적인 형식을 매개 변수로 받아들이지만, contains와 indexOf는 Object 형식의 매개 변수를 받아들입니다. 다음은 방법에 서명합니다.
    
    public boolean add(E e)
    public boolean contains(Object o)
    public int indexOf(Object o)
    따라서 만약에 우리가 범용 파라미터를 <? extends Fruit> 로 지정할 때add() 방법의 파라미터가 ? extends Fruit 로 바뀌면 컴파일러는 이 파라미터가 받아들인 것이 도대체 Fruit의 어떤 유형인지 판단할 수 없기 때문에 어떤 유형도 받아들이지 않습니다.
    그러나contains와 indexOf의 유형은 Object이고 어댑터와 관련이 없기 때문에 컴파일러는 이 두 가지 방법을 호출할 수 있습니다.이것은 모든 것이 범용 클래스의 작성자에 의해 호출이 안전하다는 것을 결정하고 Object를 이러한 안전 방법의 매개 변수로 삼는다는 것을 의미한다.만약 어떤 방법이 형식 매개 변수가 어댑터일 때 호출되는 것을 허락하지 않는다면, 이 방법의 매개 변수는 형식 매개 변수를 사용해야 한다. 예를 들어add (E e) 를 사용해야 한다.
    우리가 직접 범용 클래스를 작성할 때, 위에서 소개한 것이 유용하다.다음은 Holder 클래스를 작성합니다.
    
    public class Holder<T> {
     private T value;
     public Holder() {}
     public Holder(T val) { value = val; }
     public void set(T val) { value = val; }
     public T get() { return value; }
     public boolean equals(Object obj) {
     return value.equals(obj);
     }
     public static void main(String[] args) {
     Holder<Apple> Apple = new Holder<Apple>(new Apple());
     Apple d = Apple.get();
     Apple.set(d);
     // Holder<Fruit> Fruit = Apple; // Cannot upcast
     Holder<? extends Fruit> fruit = Apple; // OK
     Fruit p = fruit.get();
     d = (Apple)fruit.get(); // Returns ‘Object'
     try {
      Orange c = (Orange)fruit.get(); // No warning
     } catch(Exception e) { System.out.println(e); }
     // fruit.set(new Apple()); // Cannot call set()
     // fruit.set(new Fruit()); // Cannot call set()
     System.out.println(fruit.equals(d)); // OK
     }
    } /* Output: (Sample)
    java.lang.ClassCastException: Apple cannot be cast to Orange
    true
    *///:~
    Holer 클래스에서 set () 방법은 형식 매개 변수 T의 대상을 매개 변수로 받아들이고, get () 는 T 형식을 되돌리고, equals () 는 Object를 매개 변수로 받아들입니다.fruit의 유형은 Holder<? extends Fruit>이기 때문에 set () 방법은 어떤 대상의 추가도 받아들일 수 없지만 equals () 는 정상적으로 작동할 수 있습니다.
    하단 경계 어댑터
    와일드카드의 또 다른 방향은 "초유형 와일드카드": ? super T, T는 유형 매개 변수의 하계입니다. 이런 형식의 와일드카드를 사용하면 우리는 대상을 전달할 수 있습니다."아니면 예를 들어 설명합니다.
    
    public class SuperTypeWildcards {
     static void writeTo(List<? super Apple> apples) {
     apples.add(new Apple());
     apples.add(new Jonathan());
     // apples.add(new Fruit()); // Error
     }
    }
    writeTo 방법의 매개 변수 apples의 유형은 List<? super Apple> 이것은 어떤 종류의 List를 표시하고, 이 유형은 Apple의 기본 유형입니다.즉, 우리는 실제 유형이 무엇인지 모르지만, 이 유형은 틀림없이 애플의 아버지 유형일 것이다.따라서 이 목록에 Apple이나 하위 유형의 대상을 추가하는 것은 안전하다는 것을 알 수 있습니다. 이 대상들은 모두 Apple로 전환할 수 있습니다.그러나 우리는 Fruit 대상에 가입하는 것이 안전한지 모르겠다. 왜냐하면 그러면 이 List가 애플과 무관한 유형을 추가할 수 있기 때문이다.
    하위 유형 경계와 초유형 경계를 이해하면 범용 유형에 쓰기 (방법 매개 변수에 대상을 전달하는 방법) 와 범용 유형에서 읽기 (방법에서 대상을 되돌려주는 방법) 를 알 수 있습니다.다음은 예입니다.
    
    public class Collections { 
     public static <T> void copy(List<? super T> dest, List<? extends T> src) 
     {
     for (int i=0; i<src.size(); i++) 
     dest.set(i,src.get(i)); 
     } 
    }
    src는 원본 데이터의 List입니다. 이 안에서 데이터를 읽어야 하기 때문에 상단 경계를 사용하여 어댑터를 정합니다:, 추출된 요소를 T로 변환합니다.dest는 쓰기 대상 List이므로 다음 경계를 사용하여 와일드카드를 지정합니다:, 쓸 수 있는 요소 형식은 T와 하위 형식입니다.
    경계 와일드카드 없음
    또 하나의 어댑터는 무경계 어댑터이며, 그 사용 형식은 하나의 단독 물음표: ListList<?> listlist는 특정한 유형을 가진 List를 표시하지만 구체적으로 어떤 유형인지 모른다.그럼 그 안에 대상을 추가할 수 있을까요?물론 안 됩니다. 실제 어떤 유형인지 모르기 때문에 어떤 유형도 추가할 수 없습니다. 이것은 안전하지 않습니다.단독List list, 즉 범용 매개 변수가 전송되지 않았기 때문에 이list가 가지고 있는 원소의 유형Object을 표시하기 때문에 어떤 유형의 대상을 추가할 수 있지만 컴파일러가 경고 정보를 가지고 있을 뿐이다.
    총결산
    어댑터의 사용은 범용 매개 변수에 대해 몇 가지 제한을 해서 코드를 더욱 안전하게 할 수 있다. 상단 경계와 하단 경계에 대한 어댑터는 다음과 같다.
  • 이런 형식을 사용하면list는 List<? extends C> list (또는 다른 List의 하위 클래스) 의 대상을 인용할 수 있음을 나타낸다. 이 대상에 포함된 요소 유형은 C의 하위 유형 (C 자체를 포함) 의 일종이다.
  • 이런 형식을 사용하면list는 ArrayList (또는 다른 List의 하위 클래스) 의 대상을 인용할 수 있음을 나타낸다. 이 대상에 포함된 요소는 C의 슈퍼 형식 (C 자체를 포함) 의 일종이다.
  • 대부분의 경우 범형의 사용은 비교적 간단하지만, 범형을 지원하는 코드를 스스로 작성하려면 범형에 대해 깊이 있게 이해해야 한다.이 몇 편의 글은 범형의 기본 용법, 유형 지우기, 범형 수조와 어댑터의 사용을 소개하고 가장 자주 사용하는 요점을 포함하며 범형의 총결은 여기에 쓰인다.
    이상은 본문의 전체 내용입니다. 본고의 내용이 여러분의 학습이나 업무에 일정한 도움을 줄 수 있는 동시에 저희를 많이 지지해 주시기 바랍니다!

    좋은 웹페이지 즐겨찾기