[java] 공분산, 불변 및 반공 분산이 평범한 영어로 설명됩니까?

오늘 저는 Java의 Covariance, Contravariance (및 Invariance)에 대한 기사를 읽었습니다. 영어 및 독일어 Wikipedia 기사와 IBM의 다른 블로그 게시물 및 기사를 읽었습니다.

그러나 나는 이것들이 정확히 무엇에 대해 여전히 약간 혼란 스럽습니까? 어떤 사람들은 유형과 하위 유형 간의 관계에 관한 것이라고 말하고 어떤 사람들은 유형 변환에 관한 것이라고 말하고 어떤 사람들은 메서드가 재정의되거나 오버로드되었는지 여부를 결정하는 데 사용된다고 말합니다.

그래서 저는 평범한 영어로 된 쉬운 설명을 찾고 있는데, 초보자에게 공분산과 반공 변 (및 불변)이 무엇인지 보여줍니다. 쉬운 예를위한 플러스 포인트.



답변

어떤 사람들은 유형과 하위 유형 간의 관계에 관한 것이라고 말하고 다른 사람들은 유형 변환에 관한 것이라고 말하고 다른 사람들은 메서드를 덮어 쓰거나 오버로드할지 여부를 결정하는 데 사용한다고 말합니다.

무엇보다도.

핵심적으로 이러한 용어는 유형 변환이 하위 유형 관계에 미치는 영향을 설명합니다. 그 경우입니다 AB유형은, f≤ (즉, 하위 관계 유형 변환, 그리고 A ≤ B수단 A의 하위 유형이다 B), 우리가

  • fA ≤ B암시하는 경우 공변f(A) ≤ f(B)
  • fA ≤ 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() { ... }
}

나는 모든 것을 덮었거나 오히려 표면을 긁었기를 바랍니다. 그래도 추상적이지만 중요한 유형 분산 개념을 이해하는 데 도움이되기를 바랍니다.


답변

자바 유형 시스템과 클래스를 취합니다.

어떤 유형 T의 모든 객체는 T의 하위 유형 객체로 대체 될 수 있습니다.

유형 차이-클래스 방법에는 다음과 같은 결과가 있습니다.

class A {
    public S f(U u) { ... }
}

class B extends A {
    @Override
    public T f(V v) { ... }
}

B b = new B();
t = b.f(v);
A a = ...; // Might have type B
s = a.f(u); // and then do V v = u;

다음을 확인할 수 있습니다.

  • T는 하위 유형 S 여야합니다 ( B는 A의 하위 유형이므로 공변 ).
  • V는 U의 상위 유형이어야합니다 ( 역상속 방향으로 반 변량 ).

이제 B가 A의 하위 유형 인 것과 공동 및 반대됩니다. 다음과 같은 강력한 유형이 더 구체적인 지식과 함께 도입 될 수 있습니다. 하위 유형에서.

공분산 (자바에서 사용 가능)은 하위 유형에서보다 구체적인 결과를 반환한다는 점에서 유용합니다. 특히 A = T 및 B = S 일 때 나타납니다. Contravariance는 더 일반적인 논쟁을 처리 할 준비가되었다고 말합니다.


답변

분산은 제네릭 매개 변수가 다른 클래스 간의 관계에 관한 것입니다. 그들의 관계는 우리가 그들을 캐스팅 할 수있는 이유입니다.

Co 및 Contra 분산은 매우 논리적입니다. 언어 유형 시스템은 실제 논리를 지원하도록합니다. 예를 들어 이해하기 쉽습니다.

공분산

예를 들어, 당신은 꽃을 사고 싶고 당신의 도시에 장미 가게와 데이지 가게라는 두 개의 꽃 가게가 있습니다.

누군가에게 “꽃 가게가 어디 있어요?”라고 물으면 누군가가 장미 가게가 어디 있는지 말해 주면 괜찮을까요? 네, 장미는 꽃이기 때문에 꽃을 사고 싶다면 장미를 살 수 있습니다. 누군가 데이지 가게 주소로 답장을 보낸 경우에도 마찬가지입니다. 다음은 공분산의 예입니다 .으로 캐스팅 A<C>할 수 있습니다 A<B>. 여기서는 C의 하위 클래스입니다 B.A (함수에서 결과로 반환) 일반적인 값을 생성합니다. 공분산은 생산자에 관한 것입니다.

유형 :

class Flower {  }
class Rose extends Flower { }
class Daisy extends Flower { }

interface FlowerShop<T extends Flower> {
    T getFlower();
}

class RoseShop implements FlowerShop<Rose> {
    @Override
    public Rose getFlower() {
        return new Rose();
    }
}

class DaisyShop implements FlowerShop<Daisy> {
    @Override
    public Daisy getFlower() {
        return new Daisy();
    }
}

질문은 “꽃집이 어디야?”이고 대답은 “거기 장미 가게”입니다.

static FlowerShop<? extends Flower> tellMeShopAddress() {
    return new RoseShop();
}

반 변성

예를 들어 여자 친구에게 꽃을 선물하고 싶습니다. 여자 친구가 꽃을 좋아한다면 장미를 좋아하는 사람이나 데이지를 좋아하는 사람으로 생각할 수 있습니까? 예, 그녀가 꽃을 좋아한다면 장미와 데이지를 모두 좋아할 것이기 때문입니다. 이것은 반 변성 의 예입니다. 으로 캐스팅 A<B>할 수 있습니다 A<C>. 여기서는의 C하위 클래스입니다 B.A 소비하는 일반적인 값. Contravariance는 소비자에 관한 것입니다.

유형 :

interface PrettyGirl<TFavouriteFlower extends Flower> {
    void takeGift(TFavouriteFlower flower);
}

class AnyFlowerLover implements PrettyGirl<Flower> {
    @Override
    public void takeGift(Flower flower) {
        System.out.println("I like all flowers!");
    }

}

당신은 어떤 꽃을 좋아하는 당신의 여자 친구를 장미를 사랑하는 사람으로 생각하고 그녀에게 장미를주는 것을 생각하고 있습니다.

PrettyGirl<? super Rose> girlfriend = new AnyFlowerLover();
girlfriend.takeGift(new Rose());

소스 에서 더 많은 것을 찾을 수 있습니다 .


답변