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 baru Builder<MyInterface>
. Oleh karena itu, deklarasi kelas menyiratkan T = MyInterface
. Sesuai deklarasi Anda tentang with
, F
harus a Function<T, R>
, yang merupakan Function<MyInterface, R>
dalam situasi ini. Oleh karena itu, parameter getter
harus mengambil MyInterface
parameter as (dipenuhi oleh referensi metode MyInterface::getNumber
dan MyInterface::getLong
), dan kembali R
, yang harus jenis yang sama dengan parameter kedua ke fungsi with
. Sekarang, mari kita lihat apakah ini berlaku untuk semua kasus Anda:
// T = MyInterface, F = Function<MyInterface, Long>, R = Long
new Builder<MyInterface>().with(MyInterface::getLong, 4L);
// T = MyInterface, F = Function<MyInterface, Number>, R = Number
// 4L explicitly widened to Number
new Builder<MyInterface>().with(MyInterface::getNumber, (Number) 4L);
// T = MyInterface, F = Function<MyInterface, Number>, R = Number
// 4L implicitly widened to Number
new Builder<MyInterface>().<Function<MyInterface, Number>, Number>with(MyInterface::getNumber, 4L);
// T = MyInterface, F = Function<MyInterface, Number>, R = Number
// 4L implicitly widened to Number
new Builder<MyInterface>().with((Function<MyInterface, Number>) MyInterface::getNumber, 4L);
// T = MyInterface, F = Function<MyInterface, Number>, R = Long
// F = Function<T, not R> violates definition, therefore compilation error occurs
// Compiler cannot infer type of method reference and 4L at the same time,
// so it keeps the type of 4L as Long and attempts to infer a match for MyInterface::getNumber,
// only to find that the types don't match up
new Builder<MyInterface>().with(MyInterface::getNumber, 4L);
Anda dapat "memperbaiki" masalah ini dengan opsi berikut:
// stick to Long
new Builder<MyInterface>().with(MyInterface::getLong, 4L);
// stick to Number
new Builder<MyInterface>().with(MyInterface::getNumber, (Number) 4L);
// explicitly convert the result of getNumber:
new Builder<MyInterface>().with(myInstance -> (Long) myInstance.getNumber(), 4L);
// explicitly convert the result of getLong:
new Builder<MyInterface>().with(myInterface -> (Number) myInterface.getLong(), (Number) 4L);
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 :
Konversi tinju memperlakukan ekspresi dari tipe primitif sebagai ekspresi dari tipe referensi yang sesuai. Secara khusus, sembilan konversi berikut ini disebut konversi tinju :
- Dari tipe boolean ke tipe Boolean
- Dari tipe byte ke tipe Byte
- Dari tipe pendek ke tipe pendek
- Dari tipe char ke tipe Character
- Dari tipe int ke tipe Integer
- Dari tipe panjang ke tipe panjang
- Dari tipe float ke tipe Float
- Dari tipe double ke tipe Double
- Dari jenis nol ke jenis nol
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
a Function<MyInterface, Number>
.
Sebaliknya, saya berhasil menyelesaikan masalah dengan sedikit mengedit tanda tangan fungsi:
public <R> Builder<T> with(Function<T, ? super R> getter, R returnValue) {
return null;//TODO
}
Setelah perubahan ini, berikut ini terjadi:
// doesn't work, as it should not work
new Builder<MyInterface>().with(MyInterface::getLong, (Number), 4L);
// works, as it always did
new Builder<MyInterface>().with(MyInterface::getLong, 4L);
// works, as it should work
new Builder<MyInterface>().with(MyInterface::getNumber, (Number)4L);
// works, as you wanted
new Builder<MyInterface>().with(MyInterface::getNumber, 4L);
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:
public class Builder<T> {
static public interface MyInterface {
//setters
void number(Number number);
void Long(Long Long);
void string(String string);
//getters
Number number();
Long Long();
String string();
}
// whatever object we're building, let's say it's just a MyInterface for now...
private T buildee = (T) new MyInterface() {
private String string;
private Long Long;
private Number number;
public void number(Number number)
{
this.number = number;
}
public void Long(Long Long)
{
this.Long = Long;
}
public void string(String string)
{
this.string = string;
}
public Number number()
{
return this.number;
}
public Long Long()
{
return this.Long;
}
public String string()
{
return this.string;
}
};
public <R> Builder<T> with(BiConsumer<T, R> setter, R val)
{
setter.accept(this.buildee, val); // take the buildee, and set the appropriate value
return this;
}
public static void main(String[] args) {
// works:
new Builder<MyInterface>().with(MyInterface::Long, 4L);
// works:
new Builder<MyInterface>().with(MyInterface::number, (Number) 4L);
// compile time error, as it shouldn't work
new Builder<MyInterface>().with(MyInterface::Long, (Number) 4L);
// works, as it always did
new Builder<MyInterface>().with(MyInterface::Long, 4L);
// works, as it should
new Builder<MyInterface>().with(MyInterface::number, (Number)4L);
// works, as you wanted
new Builder<MyInterface>().with(MyInterface::number, 4L);
// compile time error, as you wanted
new Builder<MyInterface>().with(MyInterface::number, "blah");
}
}
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 a Builder<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.
MyInterface
?