어떤 사람들은 유형과 하위 유형 간의 관계에 관한 것이라고 말하고 다른 사람들은 유형 변환에 관한 것이라고 말하고 다른 사람들은 메서드를 덮어 쓰거나 오버로드할지 여부를 결정하는 데 사용한다고 말합니다.
무엇보다도.
핵심적으로 이러한 용어는 유형 변환이 하위 유형 관계에 미치는 영향을 설명합니다. 그 경우입니다 A
및 B
유형은, f
≤ (즉, 하위 관계 유형 변환, 그리고 A ≤ B
수단 A
의 하위 유형이다 B
), 우리가
f
A ≤ B
암시하는 경우 공변f(A) ≤ f(B)
f
A ≤ B
그것을 암시 한다면 반 변적f(B) ≤ f(A)
f
위의 어느 것도 유지되지 않으면 불변입니다.
예를 들어 보겠습니다. f(A) = List<A>
어디에서 List
선언 하자
class List<T> { ... }
가 f
공변, contravariant, 또는 불변는? 공변는 그 의미 List<String>
의 하위 유형 인 List<Object>
A는 것을, contravariant List<Object>
(A)의 하위 유형이다 List<String>
, 어느 쪽도 상대방의 서브 타입이라고하고 불변 즉 List<String>
및 List<Object>
불환 유형입니다. 자바에서는 후자가 사실이며, 제네릭 은 불변 이라고 (다소 비공식적으로) 말합니다 .
다른 예시. 하자 f(A) = A[]
. 가 f
공변, contravariant, 또는 불변는? 즉, String []은 Object []의 하위 유형이고, Object []는 String []의 하위 유형입니까, 아니면 다른 하위 유형의 하위 유형도 아닙니다. (답 : Java에서 배열은 공변입니다.)
이것은 여전히 추상적이었습니다. 좀 더 구체적으로 설명하기 위해 하위 유형 관계의 관점에서 Java의 어떤 연산이 정의되는지 살펴 보겠습니다. 가장 간단한 예는 할당입니다. 진술
x = y;
경우에만 컴파일됩니다 typeof(y) ≤ typeof(x)
. 즉, 방금 배웠습니다.
ArrayList<String> strings = new ArrayList<Object>();
ArrayList<Object> objects = new ArrayList<String>();
Java로 컴파일되지 않지만
Object[] objects = new String[1];
의지.
하위 유형 관계가 중요한 또 다른 예는 메소드 호출 표현식입니다.
result = method(a);
비공식적으로 말하면이 문은의 값을 a
메서드의 첫 번째 매개 변수에 할당 한 다음 메서드 본문을 실행 한 다음 메서드 반환 값을에 할당하여 평가됩니다 result
. 마지막 예제의 일반 할당과 같이 "오른쪽"은 "왼쪽"의 하위 유형이어야합니다. 즉,이 문은 typeof(a) ≤ typeof(parameter(method))
및 경우에만 유효 할 수 있습니다 returntype(method) ≤ typeof(result)
. 즉, 메소드가 다음과 같이 선언 된 경우 :
Number[] method(ArrayList<Number> list) { ... }
다음 표현식은 컴파일되지 않습니다.
Integer[] result = method(new ArrayList<Integer>());
Number[] result = method(new ArrayList<Integer>());
Object[] result = method(new ArrayList<Object>());
그러나
Number[] result = method(new ArrayList<Number>());
Object[] result = method(new ArrayList<Number>());
의지.
하위 유형이 중요한 또 다른 예는 재정의입니다. 치다:
Super sup = new Sub();
Number n = sup.method(1);
어디
class Super {
Number method(Number n) { ... }
}
class Sub extends Super {
@Override
Number method(Number n);
}
비공식적으로 런타임은 이것을 다음과 같이 다시 작성합니다.
class Super {
Number method(Number n) {
if (this instanceof Sub) {
return ((Sub) this).method(n); // *
} else {
...
}
}
}
표시된 줄을 컴파일하려면 재정의 메서드의 메서드 매개 변수가 재정의 된 메서드의 메서드 매개 변수의 상위 유형이어야하고 반환 유형은 재정의 된 메서드의 하위 유형이어야합니다. 공식적으로 말하면 f(A) = parametertype(method asdeclaredin(A))
적어도 반 f(A) = returntype(method asdeclaredin(A))
변성이어야하며 적어도 공변이어야합니다.
위의 "적어도"에 유의하십시오. 이는 합리적인 정적 유형 안전 객체 지향 프로그래밍 언어가 적용 할 최소 요구 사항이지만 프로그래밍 언어가 더 엄격하도록 선택할 수 있습니다. Java 1.4의 경우, 매개 변수 유형과 메소드 반환 유형은 메소드 parametertype(method asdeclaredin(A)) = parametertype(method asdeclaredin(B))
를 재정의 할 때 ( 즉, 재정의 할 때 유형 삭제 제외) 동일해야합니다 . Java 1.5 이후로, 공변 반환 유형은 재정의 할 때 허용됩니다. 즉, 다음은 Java 1.5에서 컴파일되지만 Java 1.4에서는 컴파일되지 않습니다.
class Collection {
Iterator iterator() { ... }
}
class List extends Collection {
@Override
ListIterator iterator() { ... }
}
나는 모든 것을 덮었거나 오히려 표면을 긁었기를 바랍니다. 그래도 추상적이지만 중요한 유형 분산 개념을 이해하는 데 도움이되기를 바랍니다.