Java의 유형 종속성, Part 1

유형 호환성을 이해하는 것은 좋은 Java 프로그램을 작성하는 데 필수적이지만 Java 언어 요소 간의 차이 상호 작용은 초심자에게 매우 학문적으로 보일 수 있습니다. 이 기사는 문제를 해결할 준비가 된 소프트웨어 개발자를위한 것입니다! 1 부에서는 배열 유형 및 일반 유형과 같은 더 간단한 요소와 특수한 Java 언어 요소 인 와일드 카드 간의 공변 및 반 변성 관계를 보여줍니다. 2 부에서는 일반적인 API 예제와 람다 식의 유형 종속성 및 분산을 살펴 봅니다.

다운로드 소스 다운로드이 기사 "Java의 유형 종속성, Part 1"에 대한 소스 코드를 가져옵니다. Andreas Solymosi 박사가 JavaWorld 용으로 만들었습니다.

개념 및 용어

다양한 Java 언어 요소 간의 공분산 및 반공 변성 관계에 들어가기 전에 공유 된 개념적 프레임 워크가 있는지 확인하십시오.

적합성

객체 지향 프로그래밍에서 호환성 은 그림 1과 같이 유형 간의 방향 관계를 나타냅니다.

안드레아스 솔리 모시

유형 의 변수간에 데이터를 전송할 수 있다면 두 유형이 Java에서 호환 된다고 말합니다 . 컴파일러가이를 수락하면 데이터 전송이 가능하며 할당 또는 매개 변수 전달을 통해 수행됩니다. 예를 들어, 할당 이 가능 short하기 int때문에 호환됩니다 intVariable = shortVariable;. 그러나 할당 이 불가능 boolean하기 int때문에 호환 intVariable = booleanVariable;되지 않습니다. 컴파일러는 그것을 받아들이지 않을 것입니다.

호환성이 감독 관계이기 때문에, 때로는 호환됩니다 만 에 호환되지 않습니다 , 여부를 같은 방법으로한다. 우리는 명시 적 또는 암시 적 호환성에 대해 논의 할 때 더 자세히 살펴볼 것입니다.T1T2T2T1

중요한 것은 참조 유형 간의 호환성 이 유형 계층 내 에서만 가능하다는 입니다. 모든 클래스 유형에 호환되는 Object모든 클래스가 암시 적으로 상속하기 때문에, 예를 들어, Object. 그러나은의 수퍼 클래스가 아니기 때문에 Integer와 호환 되지 않습니다 . 이다 호환 때문에, 의 (추상) 슈퍼 클래스입니다 . 동일한 유형 계층에 있기 때문에 컴파일러는 할당을 허용합니다 .FloatFloatIntegerIntegerNumberNumberIntegernumberReference = integerReference;

호환성을 명시 적으로 표시해야하는지 여부에 따라 암시 적 또는 명시 적 호환성 에 대해 이야기 합니다. 예를 들어 short는 (위에 표시된대로) 암시 적으로 호환 int되지만 그 반대 shortVariable = intVariable;는 아닙니다. 할당 이 불가능합니다. 그러나 short는 할당 이 가능 하기 때문에 명시 적으로 와 호환됩니다 . 여기서 우리는 형 변환이라고도 알려진 캐스팅으로 호환성을 표시해야합니다 .intshortVariable = (short)intVariable;

마찬가지로 참조 유형 중 : integerReference = numberReference;허용되지 않고 허용됩니다 integerReference = (Integer) numberReference;. 따라서, Integer이다 암시 적으로 호환 NumberNumber아니라 명시 적으로 호환 Integer.

의존

유형은 다른 유형에 따라 달라질 수 있습니다. 예를 들어 배열 유형 int[]은 기본 유형에 따라 다릅니다 int. 마찬가지로 제네릭 유형 ArrayList은 유형 에 따라 다릅니다 Customer. 메소드는 매개 변수 유형에 따라 유형에 따라 달라질 수도 있습니다. 예를 들어, 메소드 void increment(Integer i); 유형에 따라 다릅니다 Integer. 일부 일반 유형과 같은 일부 메소드는 둘 이상의 매개 변수가있는 메소드와 같이 둘 이상의 유형에 의존합니다.

공분산 및 반공 분산

공분산과 반공 분산은 유형에 따라 호환성을 결정합니다. 두 경우 모두 분산은 방향 관계입니다. 공분산 "동일한 방향으로 상이한"또는 번역 될 수 와 다른- 반면 contravariance의 "반대 방향으로 다른"수단 또는 대 - 다른 . 공변 및 반반 변 유형은 동일하지 않지만 둘 사이에는 상관 관계가 있습니다. 이름은 상관 관계의 방향을 의미합니다.

따라서 공분산 은 두 유형의 호환성이 두 유형에 종속 된 유형의 호환성을 의미합니다. 유형 호환성이 주어지면 그림 2에 표시된 것처럼 종속 유형이 공변이라고 가정합니다.

안드레아스 솔리 모시

의 호환성 에 대한이 의 호환성을 의미 )에을 ). 종속 유형을 공변 이라고합니다 . 또는 더 정확하게는 )는)에 공변합니다 .T1T2A(T1A(T2A(T)A(T1A(T2

다른 예를 들면 : 할당 numberArray = integerArray;이 가능 하기 때문에 (적어도 Java에서는) 배열 유형 Integer[]Number[]공변이 있습니다. 그래서, 우리는 말할 수 Integer[]있다 암시 공변Number[]. 그리고 그 반대는 사실 integerArray = numberArray;이 아니지만 ( 할당 불가능합니다. ) 타입 캐스팅 ( integerArray = (Integer[])numberArray;)을 사용한 할당 가능합니다. 따라서 우리 Number[]명시 적 으로 Integer[].

요약하면 : Integer암시 적으로 호환 Number되므로, Integer[]암시 적 공변입니다 Number[], 그리고 Number[]명시 적으로 공변입니다 Integer[]. 그림 3은 설명합니다.

안드레아스 솔리 모시

일반적으로 배열 유형은 Java에서 공변이라고 말할 수 있습니다. 기사 뒷부분에서 제네릭 유형 간의 공분산 예를 살펴 보겠습니다.

반 변성

공분산과 마찬가지로 반공 분산은 방향성 관계입니다. 공분산은 with-different를 의미 하고 , contravariance는 against-different를 의미 합니다. 앞서 언급했듯이 이름은 상관 관계의 방향을 나타 냅니다. 또한 분산은 일반적으로 유형의 속성이 아니라 종속 유형 (예 : 배열 및 제네릭 유형, 2 부에서 논의 할 메서드) 의 속성이라는 점에 유의해야합니다 .

같은 종속 형 A(T)이라고 contravariant 의 호환성 경우 에이 의 호환성을 의미 )로 ). 그림 4는 설명합니다.T1T2A(T2A(T1

안드레아스 솔리 모시

to 의 호환성이 ) to ) 의 호환성 을 의미하는 경우 A(T)에 의존 하는 언어 요소 (유형 또는 메서드) T공변 입니다. 의 호환성 경우 에이 의 호환성 의미 로)를 ), 다음 유형 이다 contravariant . 의 호환성 경우 와는 어떤 사이의 호환성을 의미하지 않는다 )과 ), 다음 입니다 불변 .T1T2A(T1A(T2T1T2A(T2A(T1A(T)T1T2A(T1A(T2A(T)

Java의 배열 유형은 내재적으로 반 변성 이 아니지만 일반 유형과 마찬가지로 명시 적으로 반 변성 이 될 수 있습니다 . 기사 뒷부분에서 몇 가지 예를 제공하겠습니다.

유형 종속 요소 : 방법 및 유형

Java에서 메소드, 배열 유형 및 일반 (매개 변수화 된) 유형은 유형 종속 요소입니다. 메소드는 매개 변수 유형에 따라 다릅니다. 배열 T[]유형은 해당 요소 유형에 따라 다릅니다 T. 제네릭 유형 G은 유형 매개 변수 인 T. 그림 5는 설명합니다.

안드레아스 솔리 모시

이 기사는 대부분 타입 호환성에 초점을 맞추고 있지만 2 부 끝 부분에서 메소드 간의 호환성에 대해 다룰 것입니다.

암시 적 및 명시 적 형식 호환성

Earlier, you saw the type T1 being implicitly (or explicitly) compatible to T2. This is only true if the assignment of a variable of type T1 to a variable of type T2 is allowed without (or with) tagging. Type casting is the most frequent way to tag explicit compatibility:

 variableOfTypeT2 = variableOfTypeT1; // implicit compatible variableOfTypeT2 = (T2)variableOfTypeT1; // explicit compatible 

For example, int is implicitly compatible to long and explicitly compatible to short:

 int intVariable = 5; long longVariable = intVariable; // implicit compatible short shortVariable = (short)intVariable; // explicit compatible 

Implicit and explicit compatibility exists not only in assignments, but also in passing parameters from a method call to a method definition and back. Together with input parameters, this means also passing a function result, which you would do as an output parameter.

Note that boolean isn't compatible to any other type, nor can a primitive and a reference type ever be compatible.

Method parameters

We say, a method reads input parameters and writes output parameters. Parameters of primitive types are always input parameters. A return value of a function is always an output parameter. Parameters of reference types can be both: if the method changes the reference (or a primitive parameter), the change remains within the method (meaning it isn't visible outside the method after the call--this is known as call by value). If the method changes the referred object, however, the change remains after being returned from the method--this is known as call by reference.

A (reference) subtype is implicitly compatible to its supertype, and a supertype is explicitly compatible to its subtype. This means that reference types are compatible only within their hierarchy branch--upward implicitly and downward explicitly:

 referenceOfSuperType = referenceOfSubType; // implicit compatible referenceOfSubType = (SubType)referenceOfSuperType; // explicit compatible 

The Java compiler typically allows implicit compatibility for an assignment only if there is no danger of losing information at runtime between the different types. (Note, however, that this rule isn't valid for losing precision, such as in an assignment from int to float.) For example, int is implicitly compatible to long because a long variable holds every int value. In contrast, a short variable does not hold any int values; thus, only explicit compatibility is allowed between these elements.

안드레아스 솔리 모시

그림 6의 암시 적 호환성이 관계를 가정합니다 이적은 : short호환됩니다 long.

그림 6에서 볼 수있는 것과 유사하게, 하위 유형 int의 참조에 상위 유형 의 참조 를 할당하는 것이 항상 가능합니다 . ClassCastException그러나 다른 방향의 동일한 할당은를 throw 할 수 있으므로 Java 컴파일러는 유형 캐스팅으로 만 허용합니다.

배열 유형에 대한 공분산 및 반공 분산

Java에서 일부 배열 유형은 공변 및 / 또는 반 변성입니다. 경우 공분산의 경우,이 수단은 T호환이며 U, 다음 T[]과 같은 호환이다 U[]. contravariance의 경우, 그 의미 U[]로 호환된다 T[]. 기본 유형의 배열은 Java에서 변하지 않습니다.

 longArray = intArray; // type error shortArray = (short[])intArray; // type error 

참조 유형의 배열은 암시 적으로 공변 적이고 명시 적으로 반 변성입니다 .

 SuperType[] superArray; SubType[] subArray; ... superArray = subArray; // implicit covariant subArray = (SubType[])superArray; // explicit contravariant 
안드레아스 솔리 모시

그림 7. 배열에 대한 암시 적 공분산

실제로 이것이 의미하는 것은 배열 구성 요소의 할당이 ArrayStoreException런타임에 발생할 수 있다는 것 입니다. 의 배열 참조가 SuperType의 배열 객체 를 참조하고 SubType해당 구성 요소 중 하나가 SuperType객체에 할당 되면 다음을 수행합니다.

 superArray[1] = new SuperType(); // throws ArrayStoreException 

This is sometimes called the covariance problem. The true problem is not so much the exception (which could be avoided with programming discipline), but that the virtual machine must check every assignment in an array element at runtime. This puts Java at an efficiency disadvantage against languages without covariance (where a compatible assignment for array references is prohibited) or languages like Scala, where covariance can be switched off.

An example for covariance

In a simple example, the array reference is of type Object[] but the array object and the elements are of different classes:

 Object[] objectArray; // array reference objectArray = new String[3]; // array object; compatible assignment objectArray[0] = new Integer(5); // throws ArrayStoreException 

공분산으로 인해 컴파일러는 배열 요소에 대한 마지막 할당의 정확성을 확인할 수 없습니다. JVM이이를 수행하며 상당한 비용이 발생합니다. 그러나 배열 유형간에 유형 호환성을 사용하지 않는 경우 컴파일러는 비용을 최적화 할 수 있습니다.

안드레아스 솔리 모시

Java에서 상위 유형의 객체를 참조하는 일부 유형의 참조 변수는 금지되어 있습니다. 그림 8의 화살표는 위쪽으로 향하지 않아야합니다.

제네릭 유형의 변형 및 와일드 카드

제네릭 (매개 변수화 된) 유형은 Java에서 내재적으로 불변합니다 . 즉, 제네릭 유형의 다른 인스턴스화가 서로 호환되지 않습니다. 유형 캐스팅조차도 호환성이 없습니다.

 Generic superGeneric; Generic subGeneric; subGeneric = (Generic)superGeneric; // type error superGeneric = (Generic)subGeneric; // type error 

그래도 유형 오류가 발생합니다 subGeneric.getClass() == superGeneric.getClass(). 문제는 메소드 getClass()가 원시 유형을 결정 한다는 것 입니다. 이것이 유형 매개 변수가 메소드의 서명에 속하지 않는 이유입니다. 따라서 두 메서드 선언은

 void method(Generic p); void method(Generic p); 

인터페이스 (또는 추상 클래스) 정의에서 함께 발생해서는 안됩니다.