Javaでの型の依存関係、パート1

型の互換性を理解することは、優れたJavaプログラムを作成するための基本ですが、Java言語要素間の差異の相互作用は、初心者には非常に学術的に思えるかもしれません。この記事は、課題に取り組む準備ができているソフトウェア開発者を対象としています。パート1では、配列型やジェネリック型などの単純な要素と、特別なJava言語要素であるワイルドカードとの間の共変および反変の関係について説明します。第2部では、一般的なAPIの例とラムダ式での型の依存関係と分散について説明します。

ダウンロードソースをダウンロードこの記事「Javaの型依存関係、パート1」のソースコードを入手してください。AndreasSolymosi博士によってJavaWorld用に作成されました。

概念と用語

さまざまなJava言語要素間の共変性と反変性の関係に入る前に、共有の概念フレームワークがあることを確認しましょう。

互換性

オブジェクト指向プログラミングでは、互換性とは、図1に示すように、型間の直接的な関係を指します。

アンドレアスソリモシ

タイプの変数間でデータを転送できる場合、Javaでは2つのタイプに互換性あると言います。コンパイラがデータ転送を受け入れる場合はデータ転送が可能であり、割り当てまたはパラメータの受け渡しによって行われます。例として、割り当てが可能であるため、shortと互換性があります。ただし、割り当てが不可能なため、とは互換性がありません。コンパイラはそれを受け入れません。intintVariable = shortVariable;booleanintintVariable = booleanVariable;

互換性が指向関係であるため、時々に互換性があるが、に互換性がないのと同じ方法ではない、または。明示的または暗黙的な互換性について説明するときに、これについてさらに詳しく説明します。T1T2T2T1

重要なのは、参照型間の互換性は型階層内でのみ可能あるということです。Objectたとえば、すべてのクラスはObject。から暗黙的に継承するため、すべてのクラスタイプはと互換性があります。ただし、はのスーパークラスではないため、Integerとは互換性がありません。はの(抽象的な)スーパークラスであるため、互換性があります。それらは同じ型階層にあるため、コンパイラーは割り当てを受け入れます。FloatFloatIntegerIntegerNumberNumberIntegernumberReference = integerReference;

私たちは、について話暗黙的または明示的な互換性が明示的かどうかにマークする必要があるかどうかに応じて、互換性。たとえば、shortは(上記のように)暗黙的に互換性intがありますが、その逆shortVariable = intVariable;はありません。割り当てはできません。ただし、割り当てが可能であるため、shortは明示的にと互換性があります。ここでは、型変換とも呼ばれるキャストによって互換性をマークする必要があります。intshortVariable = (short)intVariable;

同様に、参照型の中で:integerReference = numberReference;integerReference = (Integer) numberReference;受け入れられず、受け入れられるだけです。したがって、Integerとは暗黙的に互換性がありますNumberが、Numberとは明示的にのみ互換性がありIntegerます。

依存

タイプは他のタイプに依存する場合があります。たとえば、配列型int[]はプリミティブ型に依存しintます。同様に、ジェネリック型ArrayListは型に依存しCustomerます。メソッドは、パラメーターのタイプに応じて、タイプに依存する場合もあります。たとえば、メソッドvoid increment(Integer i); タイプによって異なりIntegerます。一部のメソッド(一部の汎用タイプなど)は、複数のパラメーターを持つメソッドなど、複数のタイプに依存しています。

共変性と反変性

共分散と反変性は、タイプに基づいて互換性を決定します。いずれの場合も、分散は有向関係です。共分散は「同じ方向で異なる」または「異なる」と解釈できますが、変性は「反対方向で異なる」または「反対-異なる」を意味します。共変型と反変型は同じではありませんが、それらの間には相関関係があります。名前は、相関の方向を示しています。

したがって、共分散とは、2つのタイプの互換性が、それらに依存するタイプの互換性を意味することを意味します。型の互換性を考えると、図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では配列型は共変であると言えます。この記事の後半で、ジェネリック型間の共分散の例を見ていきます。

共変性

共変性と同様に、反変性は有向関係です。共分散は-異なるを意味しますが、反変性は-異なるを意味します。前に述べたように、名前は相関の方向を表します。また、分散は一般的に型の属性ではなく、依存型(配列やジェネリック型など、およびパート2で説明するメソッド)の属性であることに注意することも重要です。

の互換性が)から)の互換性を意味する場合、のような依存型A(T)反変と呼ばれます。図4に示します。T1T2A(T2A(T1

アンドレアスソリモシ

A language element (type or method) A(T) depending on T is covariant if the compatibility of T1 to T2 implies the compatibility of A(T1) to A(T2). If the compatibility of T1 to T2 implies the compatibility of A(T2) to A(T1), then the type A(T) is contravariant. If the compatibility of T1 between T2 does not imply any compatibility between A(T1) and A(T2), then A(T) is invariant.

Array types in Java are not implicitly contravariant, but they can be explicitly contravariant , just like generic types. I'll offer some examples later in the article.

タイプに依存する要素:メソッドとタイプ

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ただし、他の方向で同じ割り当てを行うと、がスローされる可能性があるため、Javaコンパイラでは型キャストでのみ許可されることに注意してください。

配列型の共変性と反変性

Javaでは、一部の配列型は共変および/または反変です。場合、共分散の場合には、この手段ということTに対応してU、その後T[]も互換性がありますU[]。共変性の場合、それは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そのコンポーネントの1つが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()が生の型を決定することです。これが、型パラメーターがメソッドのシグニチャーに属していない理由です。したがって、2つのメソッド宣言

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

インターフェイス(または抽象クラス)定義で一緒に発生してはなりません。