Kita semua tahu Long memanjang Number
. Jadi mengapa ini tidak dikompilasi?
Dan bagaimana mendefinisikan metode with
sehingga program dapat dikompilasi tanpa ada panduan manual?
import java.util.function.Function;
public class Builder<T> {
static public interface MyInterface {
Number getNumber();
Long getLong();
}
public <F extends Function<T, R>, R> Builder<T> with(F getter, R returnValue) {
return null;//TODO
}
public static void main(String[] args) {
// works:
new Builder<MyInterface>().with(MyInterface::getLong, 4L);
// works:
new Builder<MyInterface>().with(MyInterface::getNumber, (Number) 4L);
// works:
new Builder<MyInterface>().<Function<MyInterface, Number>, Number> with(MyInterface::getNumber, 4L);
// works:
new Builder<MyInterface>().with((Function<MyInterface, Number>) MyInterface::getNumber, 4L);
// compilation error: Cannot infer ...
new Builder<MyInterface>().with(MyInterface::getNumber, 4L);
// compilation error: Cannot infer ...
new Builder<MyInterface>().with(MyInterface::getNumber, Long.valueOf(4));
// compiles but also involves typecast (and Casting Number to Long is not even safe):
new Builder<MyInterface>().with( myInterface->(Long) myInterface.getNumber(), 4L);
// compiles but also involves manual conversion:
new Builder<MyInterface>().with(myInterface -> myInterface.getNumber().longValue(), 4L);
// compiles (compiler you are kidding me?):
new Builder<MyInterface>().with(castToFunction(MyInterface::getNumber), 4L);
}
static <X, Y> Function<X, Y> castToFunction(Function<X, Y> f) {
return f;
}
}
- Tidak dapat menyimpulkan argumen tipe untuk
<F, R> with(F, R)
- Jenis getNumber () dari tipe Builder.MyInterface adalah Number, ini tidak kompatibel dengan tipe pengembalian deskriptor: Panjang
Untuk kasus penggunaan, lihat: Mengapa jenis pengembalian lambda tidak dicentang pada waktu kompilasi
java
type-inference
jukzi
sumber
sumber
MyInterface
?<F extends Function<T, R>, R, S extends R> Builder<T> with(F getter, S returnValue)
tetapi mendapatkanjava.lang.Number cannot be converted to java.lang.Long)
, yang mengejutkan karena saya tidak melihat dari mana kompiler mendapatkan ide bahwa nilai pengembaliangetter
harus dikonversi kereturnValue
.Number getNumber()
untuk<A extends Number> A getNumber()
membuat barang berfungsi. Tidak tahu apakah ini yang Anda inginkan. Seperti yang orang lain katakan, masalahnya adalahMyInterface::getNumber
fungsi yang mengembalikanDouble
misalnya dan tidakLong
. Deklarasi Anda tidak mengizinkan penyusun untuk mempersempit jenis pengembalian berdasarkan informasi lain yang ada. Dengan menggunakan tipe pengembalian generik Anda mengizinkan kompiler untuk melakukannya, maka itu berfungsi.Jawaban:
Ungkapan ini:
dapat ditulis ulang sebagai:
Memperhatikan tanda tangan metode akun:
R
akan disimpulkanLong
F
akanFunction<MyInterface, Long>
dan Anda melewati referensi metode yang akan disimpulkan sebagai
Function<MyInterface, Number>
Ini adalah kuncinya - bagaimana seharusnya kompiler memprediksi bahwa Anda benar-benar ingin kembaliLong
dari fungsi dengan tanda tangan seperti itu?Itu tidak akan melakukan downcasting untuk Anda.Karena
Number
merupakan superclass dariLong
danNumber
tidak perluLong
(ini sebabnya ia tidak dapat dikompilasi) - Anda harus melemparkannya sendiri:membuat
F
menjadiFunction<MyIinterface, Long>
atau meneruskan argumen umum secara eksplisit selama pemanggilan metode seperti yang Anda lakukan:dan tahu
R
akan dilihatNumber
dan kode akan dikompilasi.sumber
with
ditulis. AndaMJyInterface::getNumber
memiliki tipeFunction<MyInterface, Number>
jadiR=Number
dan kemudian Anda juga memilikiR=Long
argumen lain (ingat bahwa literal Java bukan polimorfik!). Pada titik ini kompiler berhenti karena tidak selalu mungkin untuk mengonversi aNumber
keLong
. Satu-satunya cara untuk memperbaikinya adalah dengan mengubahMyInterface
agar digunakan<A extends Number> Number
sebagai tipe pengembalian, ini membuat kompiler memilikiR=A
dan kemudianR=Long
dan karenaA extends Number
ia dapat menggantikanA=Long
Kunci untuk kesalahan Anda dalam deklarasi generik jenis
F
:F extends Function<T, R>
. Pernyataan yang tidak berfungsi adalah:new Builder<MyInterface>().with(MyInterface::getNumber, 4L);
Pertama, Anda punya yang baruBuilder<MyInterface>
. Oleh karena itu, deklarasi kelas menyiratkanT = MyInterface
. Sesuai deklarasi Anda tentangwith
,F
harus aFunction<T, R>
, yang merupakanFunction<MyInterface, R>
dalam situasi ini. Oleh karena itu, parametergetter
harus mengambilMyInterface
parameter as (dipenuhi oleh referensi metodeMyInterface::getNumber
danMyInterface::getLong
), dan kembaliR
, yang harus jenis yang sama dengan parameter kedua ke fungsiwith
. Sekarang, mari kita lihat apakah ini berlaku untuk semua kasus Anda:Anda dapat "memperbaiki" masalah ini dengan opsi berikut:
Di luar titik ini, sebagian besar merupakan keputusan desain yang opsi mengurangi kompleksitas kode untuk aplikasi khusus Anda, jadi pilihlah apa pun yang paling cocok untuk Anda.
Alasan Anda tidak dapat melakukan ini tanpa melakukan casting terletak pada yang berikut, dari Spesifikasi Bahasa Jawa :
Seperti yang dapat Anda lihat dengan jelas, tidak ada konversi tinju tersirat dari panjang ke Angka, dan konversi pelebaran dari Panjang ke Angka hanya dapat terjadi ketika kompiler yakin bahwa itu membutuhkan Angka dan bukan Panjang. Karena ada konflik antara referensi metode yang memerlukan angka dan 4L yang menyediakan panjang, kompiler (untuk beberapa alasan ???) tidak dapat membuat lompatan logis bahwa Long is-a Number dan menyimpulkan bahwa
F
aFunction<MyInterface, Number>
.Sebaliknya, saya berhasil menyelesaikan masalah dengan sedikit mengedit tanda tangan fungsi:
Setelah perubahan ini, berikut ini terjadi:
Sunting:
Setelah menghabiskan lebih banyak waktu untuk itu, sangat sulit untuk menegakkan keselamatan tipe berbasis pengambil. Berikut ini adalah contoh kerja yang menggunakan metode setter untuk menegakkan keamanan tipe pembangun:
Memberikan kemampuan tipe-aman untuk membangun objek, mudah-mudahan di beberapa titik di masa mendatang kita akan dapat mengembalikan objek data yang tidak dapat diubah dari pembangun (mungkin dengan menambahkan
toRecord()
metode ke antarmuka, dan menetapkan pembangun sebagai aBuilder<IntermediaryInterfaceType, RecordType>
), jadi Anda bahkan tidak perlu khawatir tentang objek yang dihasilkan sedang dimodifikasi. Sejujurnya, ini memalukan sekali karena membutuhkan begitu banyak upaya untuk mendapatkan pembangun bidang-jenis-aman yang aman, tetapi mungkin tidak mungkin tanpa beberapa fitur baru, pembuatan kode, atau jumlah refleksi yang mengganggu.sumber
Tampaknya kompiler telah menggunakan nilai 4L untuk memutuskan bahwa R adalah Long, dan getNumber () mengembalikan Nomor, yang belum tentu merupakan Long.
Tapi saya tidak yakin mengapa nilai lebih diutamakan daripada metode ini ...
sumber
Kompiler Java secara umum tidak bagus dalam menyimpulkan tipe generik ganda / bersarang atau wildcard. Seringkali saya tidak bisa mendapatkan sesuatu untuk dikompilasi tanpa menggunakan fungsi pembantu untuk menangkap atau menyimpulkan beberapa tipe.
Tapi, apakah Anda benar-benar perlu untuk menangkap jenis persis
Function
sebagaiF
? Jika tidak, mungkin karya berikut berfungsi, dan seperti yang Anda lihat, juga tampaknya berfungsi dengan subtipeFunction
.sumber
Bagian yang paling menarik terletak pada perbedaan antara 2 garis itu, saya pikir:
Dalam kasus pertama,
T
secara eksplisitNumber
, demikian4L
jugaNumber
, tidak ada masalah. Dalam kasus kedua,4L
adalah aLong
, begituT
juga aLong
, jadi fungsi Anda tidak kompatibel, dan Java tidak bisa tahu apakah yang Anda maksudkanNumber
atauLong
.sumber
Dengan tanda tangan berikut:
semua contoh Anda dikompilasi, kecuali yang ketiga, yang secara eksplisit membutuhkan metode untuk memiliki dua variabel tipe.
Alasan mengapa versi Anda tidak berfungsi, adalah karena referensi metode Java tidak memiliki satu jenis tertentu. Sebaliknya, mereka memiliki tipe yang diperlukan dalam konteks yang diberikan. Dalam kasus Anda,
R
disimpulkanLong
karena4L
, tetapi pengambil tidak dapat memiliki jenisFunction<MyInterface,Long>
karena di Jawa, tipe generik adalah invarian dalam argumen mereka.sumber
with( getNumber,"NO NUMBER")
mana yang tidak diinginkan. Juga tidak benar bahwa obat generik selalu invarian (lihat stackoverflow.com/a/58378661/9549750 untuk pembuktian bahwa obat generik setter berperilaku selain yang digunakan oleh pengambil)Thing<Cat>
keThing<? extends Animal>
variabel, tetapi untuk kovarians nyata saya akan berharap bahwa aThing<Cat>
dapat ditugaskan ke aThing<Animal>
. Bahasa lain, seperti Kotlin, memungkinkan untuk mendefinisikan variabel tipe co dan contravarian.