Beberapa mengatakan ini tentang hubungan antara tipe dan subtipe, yang lain mengatakan ini tentang konversi tipe dan yang lain mengatakan ini digunakan untuk memutuskan apakah suatu metode ditimpa atau kelebihan beban.
Semua yang di atas.
Intinya, istilah-istilah ini menjelaskan bagaimana hubungan subtipe dipengaruhi oleh transformasi tipe. Artinya, jika A
dan B
adalah tipe, f
adalah transformasi tipe, dan ≤ relasi subtipe ( A ≤ B
artinya itu A
adalah subtipe dari B
), kita punya
f
adalah kovarian jika A ≤ B
menyiratkan hal ituf(A) ≤ f(B)
f
bertentangan jika A ≤ B
menyiratkan ituf(B) ≤ f(A)
f
adalah invarian jika tidak satu pun dari yang di atas berlaku
Mari pertimbangkan sebuah contoh. Biarkan f(A) = List<A>
dimana List
dideklarasikan oleh
class List<T> { ... }
Apakah f
kovarian, kontravarian, atau invarian? Kovarian akan berarti bahwa a List<String>
adalah subtipe dari List<Object>
, kontravarian bahwa a List<Object>
adalah subtipe dari List<String>
dan invarian yang tidak merupakan subtipe dari yang lain, yaitu List<String>
dan List<Object>
merupakan tipe yang tidak dapat diubah. Di Jawa, yang terakhir benar, kami mengatakan (agak informal) bahwa obat generik tidak berubah.
Contoh lain. Biarkan f(A) = A[]
. Apakah f
kovarian, kontravarian, atau invarian? Artinya, apakah String [] adalah subtipe dari Objek [], Objek [] adalah subtipe dari String [], atau bukan merupakan subtipe dari yang lain? (Jawaban: Di Jawa, array adalah kovarian)
Ini masih agak abstrak. Untuk membuatnya lebih konkret, mari kita lihat operasi mana di Java yang didefinisikan dalam kaitannya dengan subtipe relasi. Contoh paling sederhana adalah penugasan. Pernyataan
x = y;
akan mengkompilasi hanya jika typeof(y) ≤ typeof(x)
. Artinya, kami baru saja mempelajari pernyataan itu
ArrayList<String> strings = new ArrayList<Object>();
ArrayList<Object> objects = new ArrayList<String>();
tidak akan dikompilasi di Jawa, tapi
Object[] objects = new String[1];
akan.
Contoh lain di mana hubungan subtipe penting adalah ekspresi pemanggilan metode:
result = method(a);
Secara informal, pernyataan ini dievaluasi dengan menetapkan nilai a
ke parameter pertama metode, kemudian menjalankan isi metode, dan kemudian menetapkan nilai kembali metode ke result
. Seperti tugas sederhana pada contoh terakhir, "sisi kanan" harus merupakan subtipe dari "sisi kiri", yaitu pernyataan ini hanya dapat valid jika typeof(a) ≤ typeof(parameter(method))
dan returntype(method) ≤ typeof(result)
. Artinya, jika metode dideklarasikan oleh:
Number[] method(ArrayList<Number> list) { ... }
tidak ada ekspresi berikut yang akan dikompilasi:
Integer[] result = method(new ArrayList<Integer>());
Number[] result = method(new ArrayList<Integer>());
Object[] result = method(new ArrayList<Object>());
tapi
Number[] result = method(new ArrayList<Number>());
Object[] result = method(new ArrayList<Number>());
akan.
Contoh lain di mana subtyping penting. Mempertimbangkan:
Super sup = new Sub();
Number n = sup.method(1);
dimana
class Super {
Number method(Number n) { ... }
}
class Sub extends Super {
@Override
Number method(Number n);
}
Secara informal, runtime akan menulis ulang ini menjadi:
class Super {
Number method(Number n) {
if (this instanceof Sub) {
return ((Sub) this).method(n); // *
} else {
...
}
}
}
Untuk baris yang ditandai untuk dikompilasi, parameter metode dari metode penggantian harus supertipe dari parameter metode dari metode yang diganti, dan tipe kembalian adalah subtipe dari metode yang diganti. Secara formal, f(A) = parametertype(method asdeclaredin(A))
setidaknya harus kontravarian, dan jika f(A) = returntype(method asdeclaredin(A))
setidaknya harus kovarian.
Perhatikan "setidaknya" di atas. Itu adalah persyaratan minimum yang akan diberlakukan oleh bahasa pemrograman berorientasi objek aman jenis statis yang masuk akal, tetapi bahasa pemrograman mungkin memilih untuk lebih ketat. Dalam kasus Java 1.4, tipe parameter dan tipe kembalian metode harus identik (kecuali untuk penghapusan tipe) saat mengganti metode, yaitu parametertype(method asdeclaredin(A)) = parametertype(method asdeclaredin(B))
saat mengganti. Sejak Java 1.5, jenis kembalian kovarian diizinkan saat mengganti, yaitu berikut ini akan dikompilasi di Java 1.5, tetapi tidak di Java 1.4:
class Collection {
Iterator iterator() { ... }
}
class List extends Collection {
@Override
ListIterator iterator() { ... }
}
Saya harap saya menutupi semuanya - atau lebih tepatnya, menggores permukaan. Tetap saja saya berharap ini akan membantu untuk memahami konsep varian tipe yang abstrak, tetapi penting.