DevOps

Java에서 유형 종속성, Part 1-2 (array, generic, wildcard)

IT오이시이 2017. 6. 14. 09:42
728x90

Type dependency in Java, Part 1-2

Covariance and contravariance for array types, generic types, and the wildcard element

원문 : http://www.javaworld.com/article/3172592/java-language/type-dependency-in-java-part-1.html?page=2


Java의 제네릭 형식은 암시 적으로 불변이지만 일부 변수는 공 변하게 사용할 수 있습니다. 이는 ?실제 유형 매개 변수로 사용할 수 있는 와일드 카드 ( ) 로 정의해야합니다 . Generic<?>제네릭 형식의 모든 인스턴스화에 대한 추상 수퍼 유형이므로 모든 인스턴스화는 Generic다음 Generic<?>과 같이 호환됩니다 .


Generic<?> wildcardReference; 
wildcardReference = new Generic<String>(); // implicitly compatible 
wildcardReference = new Generic<Integer>(); // implicitly compatible 

와일드 카드 유형은 추상 형식이므로 참조 용으로 만 사용할 수 있고 객체 용이 아닌 new Generic<?>()컴파일러에서 거부 할 수 있습니다.

와일드 카드의 사용 예는 해당 요소 유형과 상관없이 컬렉션 또는 배열을 조작하는 메소드의 매개 변수입니다. 공분산은 배열에 대해 다음과 같은 메소드를 작성하기 쉽도록 만듭니다.


static void swap(Object[] array, int i, int j) {
	... // swaps the elements with index i and j
}

이 메소드는 모든 유형이 Object배열 유형의 공분산과 호환되기 때문에 임의의 배열에 대해 호출 될 수 있습니다 .


Integer[] integerArray = {1, 2, 3};
swap(integerArray, 0, 2); // Integer[] is compatible to Object[]

이 메소드의 제네릭 버전은 호출이 호환성을 사용하지 않고 컴파일러에서 확인할 수있는 일반 인스턴스화를 사용하므로보다 안전합니다.


static <T> void swap(T[] array, int i, int j) { ... } 

두 스왑 정의는 구별 가능한 서명이 없기 때문에 동일한 클래스에서 함께 발생하지 않을 수 있습니다.

이러한 솔루션 ArrayList은 제네릭 형식에 대한 불변성 규칙 때문에 작동하지 않습니다. 와일드 카드는 공분산에 영향을주기 때문에 해결 방법으로 사용할 수 있습니다.


static void swap(List<?> list, int i, int j) { ... } // similarly 

와일드 카드 버전 호출은 임의의 요소 유형으로 가능합니다.


List<Integer> list = ...;
swap(list, 0, 2); // List<Integer> is compatible to List<?>

우리는 어떤 타입이나 슈퍼 타입이 공분산을 가능하게하는지 명시하지 않았기 때문에 그런 호환성을 불특정의 공분산 이라고 부릅니다 . 그것은 한 번에 두 가지 수준에서 발생하는 지정되지 않은 공분산에 따라 호환성에 대한 가능성 : 일반적인 유형 (의 수준 ArrayList에 List) 및 요소의 수준 ( Integer에 ?) :


ArrayList<Integer> arrayList = ... ; // ArrayList<T> implements List<T> 
swap(list, 0, 2); // ArrayList<Integer> is compatible to List<?> 

제네릭 형식에 대한 명시 적 공분산

광 공분산 이전 섹션에 도시 된 일반화 될 수있다. 경우 Generic<SubType>에 호환했다 Generic<SuperType>, 종류는 암시 적으로 공변 될 것이다. 와일드 카드를 바인딩 하면 extends형식이 명시 적 Generic<SubType>으로 공변 (covariant)하게 Generic<? extends SuperType>됩니다. 이 와일드 카드 유형 의 참조를 선언 할 수 있습니다 (모든 와일드 카드 유형은 추상이기 때문에 객체가 아닙니다).


Generic<? extends SuperType> covariantReference; 

이 참조 Generic는 다음의 하위 유형의 실제 유형 매개 변수가있는 인스턴스를 참조 할 수 있습니다 SuperType.


covariantReference = new Generic<SuperType>(); // normal 
covariantReference = new Generic<SubType>(); // covariant 
covariantReference = new Generic<Object>(); // type error: only subtypes work 

이것은 유형이 ( 위에서 설명한 것처럼 모든 유형 의 모든 인스턴스화의 추상적 인 상위 유형과 마찬가지로) 유형이 하위 유형이있는 Generic<? extends SuperType>모든 인스턴스화의 추상 상위 유형임을 의미합니다 .GenericSuperTypeGeneric<?>Generic

바인딩에 와일드 카드를 사용하는 것은 type 매개 변수에 특정 속성이 필요한 경우에 필요합니다. 예를 들어 매개 변수 컬렉션의 요소를 조작하려는 경우입니다.


static void increment(Number[] array) { ... } // for every element + 1
static void increment(Collection<? extends Number> collection) { ... }
	// similarly

첫 번째 메서드는 Number[]배열 사이의 공분산의 결과로 매개 변수를 사용하여 호출 할 수 있습니다 .


increment(integerArray); // Integer[]  is compatible to Number[]
 

두 번째 방법은 모든 호출 할 수 ArrayListA의 파라미터 Number와일드와 공분산의 결과로서 실체화 :


increment(integerArrayList); // ArrayList<Integer> is compatible to Collection<? extends Number>

위의 코드 싹둑, 우리는 다시 두 가지 수준에서 호환성을 사용하고 있는지 참고 : 매개 변수화가 ArrayList<T>의 하위 유형 Collection<T>과 Integer의 하위 유형입니다 Number.

유형 매개 변수의 변수에 액세스하는 공변량

앞의 몇 가지 예에서 경계 유형의 와일드 카드를 사용하여 제네릭 유형 간의 명시 적 공분산을 확인할 수 있습니다. 이 공분산은 형식 매개 변수의 변수에서 작동하지만 일반 클래스의 메서드 매개 변수에서는 항상 작동하지 않습니다.

클래스에서 Generic우리 T는 메서드의 (입력 및 출력) 매개 변수의 형식으로 type 매개 변수 를 사용 한다고 가정합니다 .


class Generic<T> {
	T data;
	void write(T data) { this.data = data; } // T is input parameter type
	T read() { return data; } } // T is output parameter type

이 경우 메서드 write()를 직접 호출 할 수 없습니다 wildcardReference(형식 캐스팅 후에 만 ​​호출 할 수 있음).


wildcardReference.write(new Object()); // type error
((Generic<Object>)wildcardReference).write(new Object()); // OK
	// however, warning by the compiler: unchecked cast

와일드 카드는 유형이 아니기 때문에 호환되지 않는 유형이 Object있습니다. 그러나 와일드 카드 자체는 호환 가능합니다 Object(다른 유형에는 호환 되지 않습니다). 함수의 타입 파라미터 결과는 참조에 의해 참조 될 수 있습니다 Object:


Object object = wildcardReference.read(); 

이 규칙은 바운드 와일드 카드 유형에 적용됩니다. 입력 매개 변수는 직접 전달할 수 없습니다. 캐스팅 후에 만 ​​통과 할 수 있습니다. 출력 매개 변수는 바인딩의 유형 (또는 수퍼 유형) 변수에 지정할 수 있습니다.


covariantReference.write(new SuperType()); // type error
covariantReference.write(new SubType()); // type error
((Generic<SuperType>)covariantReference).write(new SuperType()); // OK
((Generic<SuperType>)covariantReference).write(new SubType()); // OK
((Generic<SubType>)covariantReference).write(new SubType()); //OK
object = covariantReference.read(); // OK
SuperType superReference = covariantReference.read(); // OK
SubType subReference1 = covariantReference.read(); // type error
SubType subReference2 = ((Generic<SubType>)covariantReference).read(); // OK
SubType subReference3 = (SubType)covariantReference.read(); // unsafe

마지막 두 프로그램 행의 유형 변환은 ClassCastException(언제나처럼) 던져 버릴 수 있으므로 안전하지 않습니다. 발생 여부는 마지막에있는 객체의 유형에 따라 달라집니다 write()new SubType()위의 시퀀스와 마찬가지로 예외가 발생하지 않습니다. 마지막 두 라인 사이의 차이점은 첫 번째의 참조 (변환되고 있다는 것이다 Generic<? extends SuperType>에 Generic<SubType>), 그리고 read()두 번째로하면서, 호출의 결과 read()(변환한다 ?으로 SubType첫번째이다 그래서) (약간 ) 안전한.

우리는이 안 바운드 와일드 카드에 의해 제한되는 것을 의미하는 것으로 해석 할 수 있습니다 ObjectGeneric<?>같은이 (거의) 동일한 효과를 . 따라서, 불분명 한 공분산은 호환성을 통한 명백한 공분산이라고 말할 수있다 . Generic<? extends Object>Object

이러한 규칙은 매개 변수뿐 아니라 형식 매개 변수 유형의 변수에 대한 모든 읽기 또는 쓰기 액세스 (공개 또는 액세스 가능한 것으로 가정)에 유효합니다. 어떤 유형도 상위 경계와 호환 ?되지만 ?호환 될 수 Object없습니다 (경우에 따라 다른 유형으로 는 변환 할 수 없음).


wildcardReference.data = new Object(); // writing access -> type error
object = wildcardReference.data; // reading access is OK
wildcardReference.write(new Object()); // writing access -> type error
object = wildcardReference.read(); // reading access is OK
String string = wildcardReference.data; // error: ? is compatible only to Object
Generic<? extends String> covariant = new Generic<String>();
String s = covariant.data; // reading is possible with upper bound

매개 변수화 된 유형에 대한 반 공분산

Recall that contravariance means downwards compatibility. Arrays are explicitly contravariant; syntactically this can be expressed through type conversion (via casting, see above):


subArray = (SubType[])superArray; // explicit compatible (contravariant) 

Among different instantiations of a generic type, the compiler rejects type conversion. But generic types are explicitly contravariant, too. Syntactically this can be expressed through a lower bound of the wildcard with super:


Generic<? super SubType> contravariantReference; 

This variable can refer any instantiation of Generic with any supertype (e.g., an Object) of SubType:


contravariantReference = new Generic<SubType>(); // normal 
contravariantReference = new Generic<SuperType>(); // contravariant 
contravariantReference = new Generic<Object>(); // always possible 

Here, the assignment of the SuperType instantiation takes place downwards, namely to the SubType instantiation contravariantReference--this means contravariance.

상한은 읽기와 쓰기에 대한 행동을 변경하기 때문에 (앞 절에서 논의했듯이), 반공으로 Object둘 다 가능해진다.


Generic<? super Object> contravariantO = new Generic<Object>(); // like above
contravariantO.data = new Object(); // now is OK: writing is possible
object = contravariantO.data; // also reading
contravariantO.write(new Object()); // now is OK
object = contravariantO.read(); // ? is compatible to Object

? super다른 유형들과 같은 반동 ( ) 은 공분산 ( )과 String비교하여 읽기와 쓰기의 방향을 바꾼다 ? extends:


Generic<? super String> contravariantS = new Generic<String>();
contravariantS.data = new String(); // OK
string = contravariantS.data; // type error: contrary to covariant case
contravariantS.write(new String()); // OK
string = contravariantS.read(); // type error

그 이유는 하한 (여기 String)이 와일드 카드와 호환 가능하지만 그 반대도 마찬가지이기 때문입니다. 이 사이의 차이 contravariantO.read() (OK)와 contravariantS.read() (error)?호환됩니다 Object에 있지만 String.

1 부 결론

Java 유형은 내재적으로 또는 명시 적으로 서로 호환 될 수 있습니다. 런타임시 예외를 피하기 위해 명시 적 호환성이 프로그래머에 의해 주장되어야하지만 암시 적 호환성은 컴파일러에 의해 선언됩니다. 종속 (배열 및 일반) 유형도 호환 될 수 있습니다. 상위 호환성 (공분산이라고 함)은 주로 내재적 인 반면 하위 호환성 (반공립)은 배열 유형에 대해 명시 적입니다.

Java는 제네릭 형식에 암시 적 분산을 허용하지 않습니다. 그렇게하면 형식 안전성이 위협 받기 때문입니다. 와일드 카드는 (암시 적으로) 지정되지 않은 공분산을 허용하는 절충안입니다. 제네릭 형식에 대한 공분산 및 반항은 명시 할 수 있습니다. 개발자는 표 1과 같이 상한 및 하한을 정의해야합니다.

Java 표준 라이브러리의 최신 버전에서 발견되는 많은 물음표 (와일드 카드) 및 박스 일반에 대해 궁금해하신 분이라면이 기사의 제 2 부가 도움이 될 것입니다. 여러 API 예제에서 반공 변성을 살펴보고 컴파일러가 일반 유형의 변수에 액세스하지 못하는 이유를 설명합니다. 제네릭 형식의 개체를 만드는 방법과 분산 아이디어를 메서드 선언, 정의 및 호출로 전송하는 방법을 배우게됩니다. 우리는 람다의 호환성과 편차에 대해 간략하게 살펴볼 것입니다 - 프로그래밍 언어 애호가들에게 흥미로운 일반적인 람다 표현식을 포함합니다.

728x90
반응형