DevOps

Java의 유형 종속성, 2-1 (Collections API, 제네릭, 람다식 활용)

IT오이시이 2017. 6. 14. 10:33
728x90

Type dependency in Java, Part 2

Using covariance and contravariance in your Java programs



원문 : http://www.itworld.com/article/3197118/learn-java/type-dependency-in-java-part-2.html


유형 호환성을 이해하는 것은 좋은 Java 프로그램을 작성하는 기본이지만 Java 언어 요소 간의 차이점은 초급자에게는 매우 학술적으로 보일 수 있습니다. 이 두 편의 기사는 도전 과제를 해결할 준비가 된 소프트웨어 개발자를 대상으로합니다. Part 1 에서는 배열 유형과 제네릭 유형과 같은 더 단순한 요소와 특수 Java 언어 요소 인 와일드 카드 간의 공변 관계 및 반 행위 관계를 설명했습니다. Part 2에서는 Java Collections API, 제네릭 및 람다 식의 형식 종속성에 대해 설명합니다.

우리는 바로 들어가기 때문에 Part 1을 아직 읽지 않았다면 거기에서 시작하는 것이 좋습니다.

반공 진성을위한 API 예제

첫 번째 예제에서는 Java Collections API 의 Comparator버전을 고려해야한다 java.util.Collections.sort(). 이 메소드의 서명은 다음과 같습니다.


<T> void sort(List<T> list, Comparator<? super T> c)

sort()방법은 정렬 List. 대개 오버로드 된 버전을 서명과 함께 사용하는 것이 더 쉽습니다.


sort(List<T extends Comparable<? super T>>)

이 경우, extends Comparable해당 표현 sort()필요한 메소드 비교 요소 (즉에만 호출 될 수있다 compareTo), 소자 형 정의 (또는 퍼에서 한 감사합니다 :superT)


sort(integerList); // Integer implements Comparable 
sort(customerList); // works only if Customer implements Comparable

비교를 위해 제네릭 사용

분명히 그 요소가 서로 비교 될 수있는 경우에만 목록을 정렬 할 수 있습니다. 비교는 인터페이스에 compareTo속한 단일 메소드에 의해 수행됩니다 ComparablecompareTo요소 클래스에 구현해야합니다 .

그러나이 유형의 요소는 한 방향으로 만 정렬 할 수 있습니다. 예를 들어 Customer자신의 ID로 정렬 할 수 있지만 생일이나 우편 번호로 정렬 할 수는 없습니다. 의 Comparator버전을 사용하는 sort()것이 더 유연합니다 :


publicstatic <T> void sort(List<T> list, Comparator<? super T> c)

이제 우리는 요소의 클래스가 아닌 다른 Comparator객체 를 비교 합니다. 이 일반 인터페이스에는 하나의 객체 메소드가 있습니다.


int compare(T o1, T o2);

반 변형 매개 변수

객체를 두 번 이상 인스턴스화하면 다른 기준을 사용하여 객체를 정렬 할 수 있습니다. 그러나 우리는 정말로 그러한 복잡한 Comparator<? super T>타입 파라미터를 필요로 합니까? 대부분의 경우 Comparator<T>충분할 것입니다. 다음과 compare()같이이 메서드를 사용 하여 List<T>객체의 두 요소를 비교할 수 있습니다 .

클래스 DateComparator는 구현 비교기 < 자바 . util . Date > { public int compare ( Date d1 , Date d2 ) { return ... } // 두 Date 객체를 비교 } List < java . util . 날짜 > dateList = ... ; // Date 객체 
목록 sort ( dateList , new DateComparator ()); // dateList를 정렬합니다.    
	      
		

      

그러나 더 복잡한 버전의 메소드를 사용하면 추가 Collection.sort()사용 사례를 설정할 수 있습니다. 의 contravariant 유형 매개 변수는 Comparable유형의 목록을 정렬하는 것을 가능하게 List<java.sql.Date>하기 때문에, java.util.Date의 슈퍼입니다 java.sql.Date:


List<java.sql.Date> sqlList = ... ;
sort(sqlList, new DateComparator());

서명에서 contravariance를 생략하면 (지정되지 않았 거나 안전하지 않은 sort()경우에만 <T>사용됨 <?>) 컴파일러는 마지막 줄을 유형 오류로 거부합니다.

전화하기 위해서


sort(sqlList, new SqlDateComparator());

당신은 여분의 특색없는 클래스를 써야 할 것이다 :


class SqlDateComparator extends DateComparator {}

추가 방법

Collections.sort()반동 매개 변수를 갖춘 유일한 Java Collections API 메소드는 아닙니다. 같은 방법 addAll()binarySearch()copy()fill(), 등이 유사한 유연하게 사용할 수 있습니다.

Collections메소드 는 반올림 (contravariant) 결과 유형 max()을 min()제공합니다.


public static <T extends Object & Comparable<? super T>> T max(
	Collection<? extends T> collection) { ... }

여기에서 볼 수 있듯이 형식 매개 변수는 두 개 이상의 조건을 만족시키기 위해 요청할 수 있습니다 &. 는 extends Object불필요한 나타날 수 있지만, 그 규정 max()형식의 결과를 반환 Object하지 행의 Comparable바이트 코드에. 바이트 코드에는 형식 매개 변수가 없습니다.

의 오버로드 된 버전 max()과는 Comparator심지어 더 재미있다 :


public static <T> T max(Collection<? extends T> collection,
	Comparator<? super T> comp)

이것은 max()역행 (contravariant)  공변 (covariant) 유형 매개 변수를 모두가 집니다. 의 요소가있는 동안 Collection어떤 (명시 적으로 지정하지) 형 (가능한 서로 다른) 아형이어야 Comparator동일한 형태의 슈퍼 인스턴스화되어야한다. 컴파일러의 추론 알고리즘은이 중간 유형과 다음과 같은 호출을 구별하기 위해 많이 필요합니다.


Collection<AType> collection = ... ; 
Comparator<AnOtherType> comparator = ... ; 
max(collection, comparator);

유형 매개 변수의 박스형 바인딩

자바 컬렉션 API를 입력 의존성과 분산의 우리의 마지막 예를 들어,의는의 서명 재고 할 sort()과를 Comparable. 두 개의 extends& 를 사용한다는 점에 유의하십시오 super.


static <T extends Comparable<? super T>> void sort(List<T> list) { ... }

이 경우 우리는 인스턴스화를 바인딩 할 때 참조의 호환성에 관심이 없습니다. 이 sort()메소드의 인스턴스는 구현 list하는 클래스의 요소 로 객체를 정렬합니다 Comparable. 대부분의 경우 정렬은 <? super T>메서드의 서명 없이 작동 합니다.


sort(dateList); // java.util.Date implements Comparable<java.util.Date> 
sort(sqlList); // java.sql.Date implements Comparable<java.sql.Date> 

그러나 type 매개 변수의 하한은 추가 유연성을 허용합니다. Comparable반드시 요소 클래스에서 구현 될 필요는 없다. 수퍼 클래스에서 구현 한 것으로 충분합니다. 예 :


class SuperClass implements Comparable<SuperClass> {
		public int compareTo(SuperClass s) { ... } }
	class SubClass extends SuperClass {} // without overloading of compareTo()
	List<SuperClass> superList = ...;
	sort(superList);
	List<SubClass> subList = ...;
	sort(subList);

컴파일러는 다음 행을 받아들입니다.


static <T extends Comparable<? super T>> void sort(List<T> list) { ... }

그것을 거절한다.

static <T extends Comparable<T>> void sort(List<T> list) { ... }

이 거부 이유 SubClass는 컴파일러가 매개 변수의 형식 List<SubClass>에서 결정할 subList형식이 형식 매개 변수로 적합하지 않기 때문입니다 T extends Comparable<T>. 유형 SubClass이 구현되지 않습니다 Comparable<SubClass>. 그것은 단지 구현 Comparable<SuperClass>합니다. 두 요소는 암시 적 공분산의 부족으로 인해 호환되지 않지만 SubClass호환 가능합니다 SuperClass.

우리가 사용하는 경우 반면에, <? super T>컴파일러는 기대하지 않는 SubClass구현하기 위해 Comparable; 그렇게된다면 충분 SuperClass합니다. 메서드 compareTo()가 상속되고 개체를 SuperClass호출 할 수 있기 때문에 충분 SubClass합니다. <? super T>이를 표현하여 반항을 초래합니다.

형식 매개 변수의 반 변형 액세스 변수

상한 또는 하한 은 공변 또는 반 관행 참조에 의해 참조되는 인스턴스화 의 유형 매개 변수 에만 적용됩니다 . Generic<? extends SuperType> covariantReference;and 의 경우 Generic<? super SubType> contravariantReference;다른 Generic인스턴스화 객체를 만들고 참조 할 수 있습니다 .

메소드의 매개 변수 및 결과 유형 (예 : 일반 유형의 입력 및 출력 매개 변수 유형)에 대해 다른 규칙이 유효 합니다. 위에서 정의 된 SubType대로 호환 가능한 임의의 객체를 메소드의 매개 변수 로 전달할 수 있습니다 write().


contravariantReference.write(new SubType()); // OK
contravariantReference.write(new SubSubType()); // OK too
contravariantReference.write(new SuperType()); // type error
((Generic<? super SuperType>)contravariantReference).write(
	new SuperType()); // OK

contravariance 때문에 매개 변수를 전달할 수 write()있습니다. 이것은 공변 (또한 제한되지 않은) 와일드 카드 유형과 대조됩니다.

상황은 바인딩을 통해 결과 유형에 대해 변경되지 않습니다. 유형의 결과를 read()계속 전달하며 다음 ?과 만 호환됩니다 Object.


Object o = contravariantReference.read();
SubType st = contravariantReference.read(); // type error

마지막 행은 contravariantReference타입 을 선언 했음에도 불구하고 에러를 발생 Generic<? super SubType>시킵니다.

결과 유형은 참조 유형이 명시 적으로 변환 된 후에 만 다른 유형과 호환됩니다.


SuperSuperType sst = ((Generic<SuperSuperType>)contravariantReference).read();
sst = (SuperSuperType)contravariantReference.read(); // unsafer alternative

이전 목록의 예제는 유형 변수에 대한 읽기 또는 쓰기 액세스가 parameter메소드 (읽기 및 쓰기) 또는 직접 (예제의 데이터)에 관계없이 동일한 방식으로 작동 함 을 보여줍니다 .

매개 변수 유형의 변수 읽기 및 쓰기

표 1은 Object모든 클래스와 와일드 카드가 호환되기 때문에 항상 변수로 읽는 것이 가능하다는 것을 보여줍니다 Object. 글 쓰는 것은 와일드 카드와 호환되지 Object않기 때문에, 적절한 캐스팅을 한 후에 반작용적인 참조를 통해서만 가능합니다 Object. 공변 관계가없는 변수로 형변환하지 않고 읽는 것이 가능합니다. contravariant 참조로 작성하는 것이 가능합니다.

1 번 테이블. 유형 변수에 대한 읽기 및 쓰기 액세스parameter

판독 (입력)readObjectwriteObject수퍼 유형을 읽는다.수퍼 유형 작성하위 유형 읽기하위 유형 작성
와일드 카드?승인오류캐스트캐스트캐스트캐스트
공변량?extends승인오류승인캐스트캐스트캐스트
반항적 인?super승인캐스트캐스트캐스트캐스트승인

표 1의 행은 참조 정렬을 나타내고 액세스 할 데이터 유형에 대한 열을 나타 냅니다. "supertype"W "subtype"표제는 와일드 카드 범위를 나타냄니다. "캐스트"항목은 참조를 캐스팅해야 함을 의미합니다. 마지막 4 개의 열에서 "OK"의 인스턴스는 공분산 및 반공립에 대한 일반적인 경우를 나타냅니다.

자세한 설명과 함께 테이블에 대한 체계적인 테스트 프로그램에 대해서는이 기사의 끝 부분을 참조하십시오.

객체 만들기

한편으로는 추상적이기 때문에 와일드 카드 유형의 객체를 만들 수 없습니다. 한편, 무제한의 와일드 카드 형의 배열 객체를 작성할 수 있습니다. 그러나 다른 일반 인스턴스화의 객체는 작성할 수 없습니다.


Generic<Object>[] genericArray = new Generic<Object>[20]; // type error
Generic<?>[] wildcardArray = new Generic<?>[20]; // OK
genericArray = (Generic<Object>[])wildcardArray; // unchecked conversion
genericArray[0] = new Generic<Object>();
genericArray[0] = new Generic<String>(); // type error
wildcardArray[0] = new Generic<String>(); // OK

배열의 공분산으로 인해 와일드 카드 배열 유형 Generic<?>[]은 모든 인스턴스화의 배열 유형의 상위 유형입니다. 따라서 위 코드의 마지막 줄에 할당이 가능합니다.

generic 클래스 내에서는 type 매개 변수의 객체를 만들 수 없습니다. 예를 들어, 구현의 ArrayList생성자에서 배열 객체는 Object[]생성시 유형이어야합니다 . 그런 다음이를 유형 매개 변수의 배열 유형으로 변환 할 수 있습니다.


class MyArrayList<E> implements List<E> {
	private final E[] content;
	MyArrayList(int size) {
		content = new E[size]; // type error
		content = (E[])new Object[size]; // workaround
	}
	...
}

더 안전한 해결 방법 Class을 위해 실제 형식 매개 변수의 값을 생성자에 전달합니다 .


content = (E[])java.lang.reflect.Array.newInstance(myClass, size);

여러 유형 매개 변수

제네릭 형식은 둘 이상의 형식 매개 변수를 가질 수 있습니다. 유형 매개 변수는 공분산 및 반공 변성의 동작을 변경하지 않으며 아래와 같이 여러 유형 매개 변수가 함께 발생할 수 있습니다.


class G<T1, T2> {}
G<? extends SuperType, ? super SubType> reference;
reference = new G<SuperType, SubType>(); // without variance
reference = new G<SubType, SuperType>(); // with co- and contravariance

제네릭 인터페이스 java.util.Map<K, V>는 여러 유형 매개 변수의 예제로 자주 사용됩니다. 인터페이스에는 두 개의 유형 매개 변수 (키와 값에 각각 하나씩)가 있습니다. 객체를 키와 연관시키는 것이 유용합니다. 예를 들어, 객체를 더 쉽게 찾을 수 있습니다. 전화 번호부는 Map여러 유형 매개 변수를 사용 하는 객체 의 예입니다 . 가입자 이름이 키이고 전화 번호가 값입니다.

인터페이스의 구현 java.util.HashMap에는 임의의 Map객체를 연관 테이블로 변환하기위한 생성자가 있습니다.


public HashMap(Map<? extends K,? extends V> m) ...

공분산으로 인해이 경우 매개 변수 개체의 형식 매개 변수는 정확한 형식 매개 변수 클래스 K및 에 해당하지 않아도 V됩니다. 대신 공분산을 통해 적용 할 수 있습니다.


Map<CustomerNumber, Customer> customers;
	...
contacts = new HashMap<Id, Person>(customers); // covariant

여기서는 Id의 수퍼 유형이고 CustomerNumber,의 Person수퍼 유형입니다 Customer.

방법의 차이

우리는 유형의 차이에 대해 이야기했습니다. 이제는 좀 더 쉬운 주제로 넘어 갑시다.

728x90
반응형