범형의 협동과 역변
11258 단어 Java 기본 사항
Number num = new Integer(1);
ArrayList list = new ArrayList(); //type mismatch
List extends Number> list = new ArrayList();
list.add(new Integer(1)); //error
list.add(new Float(1.2f)); //error
Integer는 Number의 하위 클래스입니다. Integer 유형의 실례는 Number 유형의 변수에 값을 부여할 수 있는데 왜 ArrayList는 ArrayList에 값을 부여할 수 없습니까?이것은 자바의 일반 어댑터와 협동과 역변을 이해해야 한다.
협동과 역변
Liskov 교체 지침
모든 인용 기류 (부류) 는 하위 클래스의 대상을 투명하게 사용할 수 있어야 한다.
LSP는 다음과 같은 네 가지 의미로 구성됩니다.
정의
역변과 협동은 유형 전환(type transformation) 후의 계승 관계를 설명하는데 그 정의는 A, B가 유형을 나타내면 f(⋅)는 유형 전환을 나타내고 ≤는 계승 관계를 나타낸다(예를 들어 A≤B는 A가 B에서 파생된 하위 클래스임을 나타낸다).
f(\8901)는 역변(contravariant)이고 A≤B일 때 f(B)≤f(A)가 성립된다.
f(⋅)는 협동(covariant)으로 A≤B일 때 f(A)≤f(B)가 성립된다.
f(\8901)는 변하지 않는다(invariant). A≤B일 때 상기 두 식은 모두 성립되지 않는다. 즉, f(A)와 f(B)는 상호 간에 상속 관계가 없다.
유형 협동성
배열은 협동적이다
// CovariantArrays.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();
fruit[1] = new Jonathan();
try {
fruit[0] = new Fruit();
} catch (Exception e) {
System.out.println(e);
}
try {
fruit[0] = new Orange();
} catch (Exception e) {
System.out.println(e);
}
}
}
fruit 수조는 컴파일하는 동안 컴파일할 수 있습니다.하지만 운행 중 이상이 발생할 수 있습니다.fruit[0]는 Apple 유형이기 때문에 값이 Orange 유형일 때 이상이 발생합니다.
범형은 변하지 않는다
메서드
호출 방법
result = method(n)
;Liskov 교체 원칙에 따라 전입형삼 n의 유형은method형삼의 하위 유형, 즉typeof(n)≤typeof(method's parameter)
이어야 한다.result는 method 반환값의 기본 유형이어야 합니다. typeof(methods's return)≤typeof(result)
static Number method(Number num) { return 1; } Object result = method(new Integer(2));//correct Number result = method(new Object());//error Integer result = method(new Integer(2));//error
Java 1.4에서 하위 클래스 덮어쓰기(override) 상위 클래스 메서드에서 참조와 반환 값의 유형은 상위 클래스와 일치해야 합니다.
class Super { Number method(Number n) { ... } }
class Sub extends Super { @Override Number method(Number n) { ... } }
Java 1.5에서 시작하여 하위 클래스가 상위 클래스 메서드를 덮어쓸 때 코디네이션이 보다 구체적인 유형을 반환할 수 있습니다.
class Super { Number method(Number n) { ... } }
class Sub extends Super { @Override Integer method(Number n) { ... } }
어댑터 도입 협동, 역변
자바의 범용형은 변하지 않지만 때때로 역변과 협동을 실현해야 하는데 어떻게 해야 합니까?이때, 와일드카드?쓸모가 있다.
은(는) 다음과 같은 일반적인 공동 변화를 구현했습니다.
List extends Number> list = new ArrayList();
는 다음과 같은 일반적인 역변을 실현했다.
List super Number> list = new ArrayList();
extends와 슈퍼
왜 (시작 코드에서) List list가dd Integer 및 Float에서 컴파일 오류가 발생합니까?우선dd의 실현을 살펴보자.
public interface List extends Collection { boolean add(E e); }
dd 방법을 호출할 때 일반 E가 자동으로 ,list가 가지고 있는 유형은 Number와 Number파 출산류 중의 어떤 유형이고 그 중에서 Integer 유형을 포함하지만 Integer 유형을 특별히 가리키지 않는다(Integer는 스페어 타이어처럼!!!)그러므로 add Integer 에서 컴파일 오류가 발생했습니다.add 메서드를 호출하기 위해 super 키워드를 사용하여 다음을 수행할 수 있습니다.
List super Number> list = new ArrayList(); list.add(new Integer(1)); list.add(new Float(1.2f));
super Number>list가 가지고 있는 유형은 Number와 Number의 기본 클래스 중의 특정한 유형이고 그 중에서 Integer와 Float는 반드시 이 유형의 하위 클래스임을 나타낸다.그래서 dd 방법이 정확하게 호출될 수 있습니다.위의 예에서 보듯이 extends는 범형의 상계를 확정했고 슈퍼는 범형의 하계를 확정했다.
PECS
도대체 언제 extends를 사용하고 언제 슈퍼를 사용합니까?Effective Java는 다음과 같은 답을 제공합니다.
PECS: producer-extends, consumer-super.
타입이 T생산자를 나타내면 extends T>를, 소비자를 나타내면 슈퍼 T>를 사용한다.
예를 들어, 간단한 Stack API:
public class Stack{
public Stack();
public void push(E e):
public E pop();
public boolean isEmpty();
}
pushAll(Iterable src)
방법을 실현하려면 src의 요소를 하나하나 창고에 넣는다.public void pushAll(Iterable src){
for(E e : src)
push(e)
}
실례화
Stack
의 대상stack이 있다고 가정하면 src는Iterable
와Iterable
가 있다.pushAll 방법을 호출할 때 type mismatch 오류가 발생합니다. 자바의 일반형은 변할 수 없기 때문에 Iterable
와 Iterable
모두 Iterable
의 하위 형식이 아닙니다.따라서// Wildcard type for parameter that serves as an E producer
public void pushAll(Iterable extends E> src) {
for (E e : src)
push(e);
}
popAll(Collection dst)
방법을 실현하려면 Stack의 요소를dd에서dst로 순서대로 꺼내십시오. 만약 어댑터가 필요하지 않으면:// popAll method without wildcard type - deficient!
public void popAll(Collection dst) {
while (!isEmpty())
dst.add(pop());
}
마찬가지로 실례화
Stack
의 대상인stack,dst는Collection