Ketik ketergantungan di Java, Bagian 2

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 dua bagian 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, wildcard. Bagian 2 mengeksplorasi ketergantungan tipe di Java Collections API, generik, dan ekspresi lambda.

Kami akan langsung membahasnya, jadi jika Anda belum membaca Bagian 1, saya sarankan untuk memulainya dari sana.

Contoh API untuk pelanggaran

Untuk contoh pertama kami, pertimbangkan Comparatorversi java.util.Collections.sort(), dari Java Collections API. Tanda tangan metode ini adalah:

  void sort(List list, Comparator c) 

The sort()Metode macam apa List. Biasanya lebih mudah menggunakan versi yang kelebihan beban, dengan tanda tangan:

 sort(List
    
     ) 
    

Dalam hal ini, extends Comparablemenyatakan bahwa sort()dapat dipanggil hanya jika elemen pembanding metode yang diperlukan (yaitu compareTo)telah ditentukan dalam tipe elemen (atau dalam supertipe-nya, berkat :? super T)

 sort(integerList); // Integer implements Comparable sort(customerList); // works only if Customer implements Comparable 

Menggunakan obat generik untuk perbandingan

Jelas, daftar hanya dapat diurutkan jika elemen-elemennya dapat dibandingkan satu sama lain. Perbandingan dilakukan dengan metode tunggal compareTo, yang termasuk dalam antarmuka Comparable. Anda harus mengimplementasikan compareTodi kelas elemen.

Jenis elemen ini dapat diurutkan hanya dengan satu cara. Misalnya, Anda dapat mengurutkan Customerberdasarkan ID mereka, tetapi tidak berdasarkan tanggal lahir atau kode pos. Menggunakan Comparatorversi sort()lebih fleksibel:

 publicstatic  void sort(List list, Comparator c) 

Sekarang kita membandingkan elemen bukan di kelas elemen, tetapi di Comparatorobjek tambahan . Antarmuka umum ini memiliki satu metode objek:

 int compare(T o1, T o2); 

Parameter kontravarian

Membuat instance objek lebih dari sekali memungkinkan Anda mengurutkan objek menggunakan kriteria yang berbeda. Tetapi apakah kita benar-benar membutuhkan Comparatorparameter tipe yang rumit ? Dalam banyak kasus, Comparatoritu sudah cukup. Kita bisa menggunakan compare()metodenya untuk membandingkan dua elemen dalam Listobjek, sebagai berikut:

class DateComparator mengimplementasikan Pembanding {public int bandingkan (Tanggal d1, Tanggal d2) {return ...} // membandingkan dua objek Tanggal} Daftar dateList = ...; // Daftar objek Tanggal sortir (dateList, DateComparator baru ()); // sortir dateList

Namun, menggunakan versi metode yang lebih rumit Collection.sort()menyiapkan kami untuk kasus penggunaan tambahan. Parameter tipe contravariant Comparablememungkinkan untuk mengurutkan daftar tipe List, karena java.util.Datemerupakan supertipe dari java.sql.Date:

 List sqlList = ... ; sort(sqlList, new DateComparator()); 

Jika kita menghilangkan kontradiksi dalam sort()tanda tangan (hanya menggunakan atau yang tidak ditentukan, tidak aman ), maka kompilator menolak baris terakhir sebagai kesalahan tipe.

Untuk menelepon

 sort(sqlList, new SqlDateComparator()); 

Anda harus menulis kelas ekstra tanpa fitur:

 class SqlDateComparator extends DateComparator {} 

Metode tambahan

Collections.sort()bukan satu-satunya metode Java Collections API yang dilengkapi dengan parameter kontravarian. Metode suka addAll(), binarySearch(), copy(), fill(), dan sebagainya, dapat digunakan dengan fleksibilitas yang sama.

Collectionsmetode menyukai max()dan min()menawarkan jenis hasil kontravarian:

 public static 
    
      T max( Collection collection) { ... } 
    

Seperti yang Anda lihat di sini, parameter tipe dapat diminta untuk memenuhi lebih dari satu kondisi, hanya dengan menggunakan &. The extends Objectmungkin muncul berlebihan, tetapi menetapkan bahwa max()pengembalian hasil dari jenis Objectdan bukan dari baris Comparabledalam bytecode. (Tidak ada parameter tipe dalam bytecode.)

Versi max()with yang kelebihan beban Comparatorbahkan lebih lucu:

 public static  T max(Collection collection, Comparator comp) 

Ini max()memiliki parameter tipe kontravarian dan kovarian. Sementara elemen Collectionharus dari (mungkin berbeda) subtipe dari jenis tertentu (tidak diberikan secara eksplisit), Comparatorharus dipakai untuk supertipe dari jenis yang sama. Banyak hal yang diperlukan dari algoritme inferensi kompiler, untuk membedakan tipe di antara ini dari panggilan seperti ini:

 Collection collection = ... ; Comparator comparator = ... ; max(collection, comparator); 

Pengikatan kotak untuk parameter tipe

Sebagai contoh terakhir dari tipe dependensi dan varians di Java Collections API, mari pertimbangkan kembali tanda tangan sort()with Comparable. Perhatikan bahwa ini menggunakan extendsdan super, yang dikotak:

 static 
    
      void sort(List list) { ... } 
    

Dalam kasus ini, kami tidak begitu tertarik pada kompatibilitas referensi seperti yang kami lakukan dalam mengikat instantiation. Instance sort()metode ini mengurutkan listobjek dengan elemen implementasi kelas Comparable. Dalam kebanyakan kasus, pengurutan akan berfungsi tanpa tanda tangan metode:

 sort(dateList); // java.util.Date implements Comparable sort(sqlList); // java.sql.Date implements Comparable 

Namun, batas bawah parameter tipe memungkinkan fleksibilitas tambahan. Comparabletidak perlu diimplementasikan di kelas elemen; itu cukup untuk diterapkan di superclass. Sebagai contoh:

 class SuperClass implements Comparable { public int compareTo(SuperClass s) { ... } } class SubClass extends SuperClass {} // without overloading of compareTo() List superList = ...; sort(superList); List subList = ...; sort(subList); 

Kompilator menerima baris terakhir dengan

 static 
    
      void sort(List list) { ... } 
    

dan menolaknya dengan

static 
    
      void sort(List list) { ... } 
    

The reason for this rejection is that the type SubClass (which the compiler would determine from the type List in the parameter subList) is not suitable as a type parameter for T extends Comparable. The type SubClass doesn't implement Comparable; it only implements Comparable. The two elements are not compatible due to the lack of implicit covariance, although SubClass is compatible to SuperClass.

On the other hand, if we use , the compiler doesn't expect SubClass to implement Comparable; it's enough if SuperClass does it. It's enough because the method compareTo() is inherited from SuperClass and can be called for SubClass objects: expresses this, effecting contravariance.

Contravariant accessing variables of a type parameter

The upper or the lower bound applies only to type parameter of instantiations referred by a covariant or contravariant reference. In the case of Generic covariantReference; and Generic contravariantReference;, we can create and refer objects of different Generic instantiations.

Different rules are valid for the parameter and result type of a method (such as for input and output parameter types of a generic type). An arbitrary object compatible to SubType can be passed as parameter of the method write(), as defined above.

 contravariantReference.write(new SubType()); // OK contravariantReference.write(new SubSubType()); // OK too contravariantReference.write(new SuperType()); // type error ((Generic)contravariantReference).write( new SuperType()); // OK 

Because of contravariance, it's possible to pass a parameter to write(). This is in contrast to the covariant (also unbounded) wildcard type.

The situation doesn't change for the result type by binding: read() still delivers a result of type ?, compatible only to Object:

 Object o = contravariantReference.read(); SubType st = contravariantReference.read(); // type error 

The last line produces an error, even though we've declared a contravariantReference of type Generic.

The result type is compatible to another type only after the reference type has been explicitly converted:

 SuperSuperType sst = ((Generic)contravariantReference).read(); sst = (SuperSuperType)contravariantReference.read(); // unsafer alternative 

Examples in the previous listings show that reading or writing access to a variable of type parameter behaves the same way, regardless of whether it happens over a method (read and write) or directly (data in the examples).

Reading and writing to variables of type parameter

Table 1 shows that reading into an Object variable is always possible, because every class and the wildcard are compatible to Object. Writing an Object is possible only over a contravariant reference after appropriate casting, because Object is not compatible to the wildcard. Reading without casting into an unfitting variable is possible with a covariant reference. Writing is possible with a contravariant reference.

Table 1. Reading and writing access to variables of type parameter

reading

(input)

read

Object

write

Object

read

supertype   

write

supertype   

read

subtype    

write

subtype    

Wildcard

?

 OK  Error  Cast  Cast  Cast  Cast

Covariant

?extends

 OK  Error  OK  Cast  Cast  Cast

Contravariant

?super

 OK  Cast  Cast  Cast  Cast  OK

The rows in Table 1 refer to the sort of reference, and the columns to the type of data to be accessed. The headings of "supertype" and "subtype" indicate the wildcard bounds. The entry "cast" means the reference must be casted. An instance of "OK" in the last four columns refers to the typical cases for covariance and contravariance.

See the end of this article for a systematic test program for the table, with detailed explanations.

Creating objects

On the one hand, you cannot create objects of the wildcard type, because they are abstract. On the other hand, you can create array objects only of an unbounded wildcard type. You cannot create objects of other generic instantiations, however.

 Generic[] genericArray = new Generic[20]; // type error Generic[] wildcardArray = new Generic[20]; // OK genericArray = (Generic[])wildcardArray; // unchecked conversion genericArray[0] = new Generic(); genericArray[0] = new Generic(); // type error wildcardArray[0] = new Generic(); // OK 

Because of the covariance of arrays, the wildcard array type Generic[] is the supertype of the array type of all instantiations; therefore the assignment in the last line of the above code is possible.

Within a generic class, we cannot create objects of the type parameter. For example, in the constructor of an ArrayList implementation, the array object must be of type Object[] upon creation. We can then convert it to the array type of the type parameter:

 class MyArrayList implements List { private final E[] content; MyArrayList(int size) { content = new E[size]; // type error content = (E[])new Object[size]; // workaround } ... } 

For a safer workaround, pass the Class value of the actual type parameter to the constructor:

 content = (E[])java.lang.reflect.Array.newInstance(myClass, size); 

Multiple type parameters

A generic type can have more than one type parameter. Type parameters don't change the behavior of covariance and contravariance, and multiple type parameters can occur together, as shown below:

 class G {} G reference; reference = new G(); // without variance reference = new G(); // with co- and contravariance 

The generic interface java.util.Map is frequently used as an example for multiple type parameters. The interface has two type parameters, one for key and one for value. It's useful to associate objects with keys, for example so that we can more easily find them. A telephone book is an example of a Map object using multiple type parameters: the subscriber's name is the key, the phone number is the value.

The interface's implementation java.util.HashMap has a constructor for converting an arbitrary Map object into an association table:

 public HashMap(Map m) ... 

Because of covariance, the type parameter of the parameter object in this case does not have to correspond with the exact type parameter classes K and V. Instead, it can be adapted through covariance:

 Map customers; ... contacts = new HashMap(customers); // covariant 

Di sini, Idadalah supertipe dari CustomerNumber, dan Personsupertipe dari Customer.

Varians metode

Kami telah berbicara tentang varian tipe; sekarang mari beralih ke topik yang lebih mudah.