Ketik ketergantungan di Java, Bagian 1

Memahami kompatibilitas tipe sangat penting untuk menulis program Java yang baik, tetapi interaksi varians antara elemen bahasa Java bisa tampak sangat akademis bagi yang belum tahu. Artikel ini ditujukan untuk pengembang perangkat lunak yang siap menghadapi tantangan! Bagian 1 mengungkapkan hubungan kovarian dan kontravarian antara elemen yang lebih sederhana seperti tipe array dan tipe generik, serta elemen bahasa Java khusus, karakter pengganti. Bagian 2 mempelajari ketergantungan tipe dan varians dalam contoh API umum dan ekspresi lambda.

unduh Unduh sumber Dapatkan kode sumber untuk artikel ini, "Ketik ketergantungan di Java, Bagian 1." Dibuat untuk JavaWorld oleh Dr. Andreas Solymosi.

Konsep dan terminologi

Sebelum kita membahas hubungan kovariansi dan kontravarian di antara berbagai elemen bahasa Jawa, mari kita pastikan bahwa kita memiliki kerangka kerja konseptual yang sama.

Kesesuaian

Dalam pemrograman berorientasi objek, kompatibilitas mengacu pada hubungan langsung antar tipe, seperti yang ditunjukkan pada Gambar 1.

Andreas Solymosi

Kami mengatakan bahwa dua tipe kompatibel di Java jika memungkinkan untuk mentransfer data antar variabel tipe. Transfer data dimungkinkan jika kompilator menerimanya, dan dilakukan melalui penugasan atau penerusan parameter. Sebagai contoh, shortkompatibel dengan intkarena penugasan intVariable = shortVariable;dimungkinkan. Tetapi booleantidak kompatibel intkarena penugasan intVariable = booleanVariable;tidak memungkinkan; kompilator tidak akan menerimanya.

Karena kompatibilitas adalah hubungan terarah, terkadang kompatibel tetapi tidak kompatibel , atau tidak dengan cara yang sama. Kita akan melihat ini lebih jauh ketika kita membahas kompatibilitas eksplisit atau implisit.T1T2T2T1

Yang penting adalah kompatibilitas di antara tipe referensi hanya mungkin dalam hierarki tipe. Semua tipe kelas kompatibel dengan Object, misalnya, karena semua kelas mewarisi secara implisit dari Object. Integertidak kompatibel dengan Float, bagaimanapun, karena Floatbukan superclass dari Integer. Integeradalah kompatibel untuk Number, karena Numbermerupakan (abstrak) superclass dari Integer. Karena mereka berada dalam hierarki tipe yang sama, kompilator menerima penugasan numberReference = integerReference;.

Kita berbicara tentang kompatibilitas implisit atau eksplisit , bergantung pada apakah kompatibilitas harus ditandai secara eksplisit atau tidak. Misalnya, pendek secara implisit kompatibel dengan int(seperti yang ditunjukkan di atas) tetapi tidak sebaliknya: penugasan shortVariable = intVariable;tidak memungkinkan. Namun, pendek secara eksplisit kompatibel int, karena penugasan shortVariable = (short)intVariable;itu mungkin. Di sini kita harus menandai kompatibilitas dengan casting , juga dikenal sebagai konversi tipe.

Demikian pula di antara jenis referensi: integerReference = numberReference;tidak dapat diterima, hanya integerReference = (Integer) numberReference;akan diterima. Oleh karena itu, Integeradalah implisit kompatibel untuk Numbertetapi Numberhanya secara eksplisit kompatibel untuk Integer.

Ketergantungan

Suatu tipe mungkin bergantung pada tipe lain. Misalnya, tipe array int[]bergantung pada tipe primitif int. Demikian pula, tipe generik ArrayListbergantung pada tipenya Customer. Metode juga dapat bergantung pada tipe, tergantung pada tipe parameternya. Misalnya metode void increment(Integer i); tergantung pada tipenya Integer. Beberapa metode (seperti beberapa jenis umum) bergantung pada lebih dari satu jenis - seperti metode yang memiliki lebih dari satu parameter.

Kovarian dan kontravarian

Kovarian dan kontravarian menentukan kompatibilitas berdasarkan jenis. Dalam kedua kasus tersebut, varians adalah hubungan terarah. Kovarian dapat diterjemahkan sebagai "berbeda dalam arah yang sama," atau dengan-berbeda , sedangkan kontravarian berarti "berbeda dalam arah yang berlawanan," atau melawan-berbeda . Jenis kovarian dan kontravarian tidak sama, tetapi ada korelasi di antara keduanya. Nama-nama tersebut menyiratkan arah korelasi.

Jadi, kovarian berarti bahwa kompatibilitas dua jenis menyiratkan kompatibilitas jenis yang bergantung padanya. Berdasarkan kompatibilitas tipe, diasumsikan bahwa tipe dependen adalah kovarian, seperti yang ditunjukkan pada Gambar 2.

Andreas Solymosi

Kompatibilitas untuk menyiratkan kompatibilitas dari ) ke ). Jenis dependen disebut kovarian ; atau lebih tepatnya, ) adalah kovarian dari ).T1T2A(T1A(T2A(T)A(T1A(T2

Untuk contoh lain: karena penugasan numberArray = integerArray;dimungkinkan (setidaknya di Java), tipe array Integer[]dan Number[]kovarian. Jadi, kita dapat mengatakan bahwa Integer[]adalah implisit kovarian untuk Number[]. Dan sementara sebaliknya tidak benar - tugas integerArray = numberArray;tidak mungkin - tugas dengan tipe casting ( integerArray = (Integer[])numberArray;) adalah mungkin; Oleh karena itu, kita katakan, Number[]adalah eksplisit kovarian untuk Integer[].

Untuk meringkas: Integersecara implisit kompatibel dengan Number, oleh karena Integer[]itu secara implisit kovarian Number[], dan Number[]secara eksplisit kovarian untuk Integer[]. Gambar 3 mengilustrasikan.

Andreas Solymosi

Secara umum, kita dapat mengatakan bahwa tipe array adalah kovarian di Jawa. Kami akan melihat contoh kovarians di antara tipe generik nanti di artikel.

Kontravarian

Seperti kovariansi, kontravarian adalah hubungan terarah . Sedangkan kovarian artinya dengan-berbeda , kontravarian berarti melawan-berbeda . Seperti yang saya sebutkan sebelumnya, nama mengungkapkan arah korelasi . Penting juga untuk dicatat bahwa varians bukanlah atribut tipe secara umum, tetapi hanya tipe dependen (seperti array dan tipe generik, dan juga metode, yang akan saya bahas di Bagian 2).

Tipe dependen seperti A(T)disebut contravariant jika kompatibilitas ke menyiratkan kompatibilitas dari ) ke ). Gambar 4 mengilustrasikan.T1T2A(T2A(T1

Andreas Solymosi

Sebuah elemen bahasa (tipe atau metode) A(T)bergantung pada Tadalah kovarian jika kompatibilitas ke menyiratkan kompatibilitas dari ) ke ). Jika kompatibilitas ke menyiratkan kompatibilitas dari ) ke ), maka jenisnya adalah contravariant . Jika kompatibilitas antara tidak menyiratkan kesesuaian antara ) dan ), maka adalah invarian .T1T2A(T1A(T2T1T2A(T2A(T1A(T)T1T2A(T1A(T2A(T)

Tipe array di Java tidak secara implisit kontravarian , tetapi bisa juga kontravarian secara eksplisit , seperti tipe generik. Saya akan menawarkan beberapa contoh nanti di artikel.

Elemen yang bergantung pada tipe: Metode dan tipe

Di Java, metode, tipe array, dan tipe generik (parametrized) adalah elemen yang bergantung pada tipe. Metode bergantung pada jenis parameternya. Jenis larik,, T[]bergantung pada jenis elemennya T,. Jenis generik Gbergantung pada parameter jenisnya T,. Gambar 5 mengilustrasikan.

Andreas Solymosi

Sebagian besar artikel ini berfokus pada kompatibilitas tipe, meskipun saya akan menyentuh kompatibilitas di antara metode-metode menjelang akhir Bagian 2.

Kompatibilitas tipe implisit dan eksplisit

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.

Andreas Solymosi

Perhatikan bahwa kompatibilitas implisit pada Gambar 6 mengasumsikan hubungan itu transitif : shortkompatibel dengan long.

Mirip dengan apa yang Anda lihat pada Gambar 6, selalu mungkin untuk menetapkan referensi subtipe intreferensi supertipe. Perlu diingat bahwa penugasan yang sama ke arah lain bisa saja memunculkan ClassCastException, jadi compiler Java hanya mengizinkannya dengan tipe casting.

Kovarian dan kontravarian untuk tipe array

Di Java, beberapa tipe array adalah kovarian dan / atau kontravarian. Dalam kasus kovarian, ini berarti bahwa jika Tkompatibel dengan U, maka T[]juga kompatibel dengan U[]. Dalam kasus kontradiksi, itu berarti U[]kompatibel dengan T[]. Array tipe primitif tidak berubah di Jawa:

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

Array tipe referensi secara implisit kovarian dan secara eksplisit bertentangan , namun:

 SuperType[] superArray; SubType[] subArray; ... superArray = subArray; // implicit covariant subArray = (SubType[])superArray; // explicit contravariant 
Andreas Solymosi

Gambar 7. Kovariansi implisit untuk array

Artinya, secara praktis, adalah bahwa penugasan komponen array dapat dilakukan ArrayStoreExceptionpada waktu proses. Jika referensi array mereferensikan SuperTypeobjek array SubType, dan salah satu komponennya kemudian ditetapkan ke SuperTypeobjek, maka:

 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 

Karena kovarians, kompilator tidak dapat memeriksa kebenaran tugas terakhir ke elemen array - JVM melakukan ini, dan dengan biaya yang signifikan. Namun, kompilator dapat mengoptimalkan biaya, jika tidak ada penggunaan kompatibilitas tipe antara tipe array.

Andreas Solymosi

Ingatlah bahwa di Java, untuk variabel referensi dari beberapa tipe yang merujuk objek dari supertipe-nya dilarang: panah pada Gambar 8 tidak boleh diarahkan ke atas.

Varians dan karakter pengganti dalam tipe generik

Jenis generik (parametrized) secara implisit tidak berubah di Java, yang berarti bahwa instansiasi yang berbeda dari jenis generik tidak kompatibel satu sama lain. Transmisi tipe genap tidak akan menghasilkan kompatibilitas:

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

Kesalahan tipe muncul meskipun subGeneric.getClass() == superGeneric.getClass(). Masalahnya adalah bahwa metode tersebut getClass()menentukan tipe mentah - inilah mengapa parameter tipe tidak termasuk dalam tanda tangan suatu metode. Jadi, dua deklarasi metode

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

tidak boleh terjadi bersamaan dalam definisi antarmuka (atau kelas abstrak).