Generik di kelas Java Comparator. Apa persisnya tipe T dan U?

Sep 09 2020

Pertimbangkan dua metode berikut. Satu-satunya perbedaan mereka adalah dalam tipe deklarasi Fungsi <> yang umum

public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
        Function<? super T, ? extends U> keyExtractor)
{
    Objects.requireNonNull(keyExtractor);
    return (Comparator<T> & Serializable)
        (c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
}
public static <T, U extends Comparable<? super U>> Comparator<T> comparingT(
        Function<T, ? extends U> keyExtractor) <-- Check here! T instead of ? super T
{
    Objects.requireNonNull(keyExtractor);
    return (Comparator<T> & Serializable)
        (c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
}

Katakanlah saya punya List<GamingComputer> = { ASUS, MSI }, di mana GamingComputermeluas Computer. Sekarang, saya ingin menyortirnya.

List.sort( comparing( Computer::getProperty ) )

Apa tipe T?

Intuisi saya: T=GamingComputer. comparing()mengambil keyExtractor, tipe siapa Function<Computer super GamingComputer, Property>. Akhirnya, comparing()kembali Comparator<GamingComputer>.

Kode ini, membuktikan intuisi saya, terkompilasi dengan sempurna:

Function<Computer, Property> f1 = Computer::getProperty;
Comparator<GamingComputer> c1 = comparing(f1);

Sekarang, dengan PECS, karena c1, c2ditambahkan ke collection / konstruktor / metode, selama collection menangani kelas induknya, ia bisa menangani kelas anak apa pun. Itulah alasan di baliknya <? super T>.

Seperti yang ditunjukkan dalam kode ini:

Function<Computer, Property> f2 = Computer::getProperty;
Comparator<GamingComputer> c2 = comparingT(f2); // FAILS to compile. Required Type: Comparator<GamingComputer>, Provided Comparator<Computer>
Comparator<Computer> c2 = comparingT(f2); // compiles successfuly

Karena f2bekerja dengan semua Computer, itu harus bisa bekerja dengan semua GamingComputerjuga. Namun, karena kami tidak mendeklarasikan tipe sebagai <? super T>, kami tidak dapat membuat Comparatordari GamingComputers.

Masuk akal. Kemudian...

Comparator<GamingComputer> c22 = comparingT(Computer::getProperty); // compiles successfuly... WT, excuse mi French, heck???

Dugaan saya: comparingT()dengan tipe T=GamingComputermemaksa tertunduk keyExtractor, yaitu Computer::getProperty. Itu memaksa semua Computersuntuk menggunakan GamingComputer::getProperty, yang mungkin bukan masalah, karena Comparator<GamingComputer>memang membandingkan GamingComputers.

Tapi, mengapa ini TIDAK dikompilasi?

Function<Computer, Property> f22 = GamingComputer::getProperty;

Kesalahannya sangat aneh:

Metode non-statis tidak dapat dirujuk dari konteks statis, yang mungkin merupakan bug dari Intellij

Metode non-statis tidak dapat dirujuk dari konteks statis di aliran java 8

Namun, saat menyusun:

java: incompatible types: invalid method reference
    method getPart in class function.GamingComputer cannot be applied to given types
      required: no arguments
      found: function.Computer
      reason: actual and formal argument lists differ in length

Jawaban

1 Sweeper Sep 09 2020 at 09:48

Intuisi saya: T=GamingComputer

Intuisi Anda benar.


Mengapa Comparator<GamingComputer> c22 = comparingT(Computer::getProperty);mengkompilasi?

Ini karena tidak seperti contoh antarmuka fungsional yang tidak berubah, referensi metode bersifat kovarian dan kontravarian. Menggunakan contoh dari sini , Anda dapat melakukan sesuatu seperti:

// in SomeClass
public static Integer function(Object o) {
    return 2;
}

// ...
Function<String, Object> function = SomeCLass::function;

Atau menggunakan kelas Anda, Anda dapat melakukan:

Function<GamingComputer, Property> f = Computer::getProperty;

Ini "seolah-olah" referensi metode ada ? superpada parameter mereka dan ? extendspada tipe kembalian! Detail tentang apa yang berhasil dan apa yang tidak ditentukan di bagian 15.13.2 dari Spesifikasi Bahasa Java.

Jadi untuk c22, Tmasih GamingComputer. Referensi metode Computer::getPropertydapat diubah Function<GamingComputer, Property>menjadi referensi metode.

Ini tidak dapat dikompilasi, meskipun f2"menyimpan" Computer::getProperty:

Comparator<GamingComputer> c2 = comparingT(f2);

karena f2bukan referensi metode itu sendiri. Ini adalah variabel.

Mengapa Function<Computer, Property> f22 = GamingComputer::getProperty;tidak dikompilasi?

f22akan dapat menerima segala jenis Computer, karena ia menerima Computer. Jika Anda memberikan f22komputer jenis lain (bukan GamingComputer), GamingComputer.getPropertytentu tidak akan bisa mengatasinya, bukan?