กุญแจสู่ความผิดพลาดของคุณอยู่ในการประกาศทั่วไปประเภทของ:F
F extends Function<T, R>
คำสั่งที่ไม่ทำงานคือ: ครั้งแรกที่คุณมีใหม่new Builder<MyInterface>().with(MyInterface::getNumber, 4L);
การประกาศของชั้นจึงหมายถึงBuilder<MyInterface>
T = MyInterface
ตามประกาศของwith
คุณF
จะต้องเป็นFunction<T, R>
ซึ่งเป็นFunction<MyInterface, R>
ในสถานการณ์นี้ ดังนั้นพารามิเตอร์getter
ต้องใช้MyInterface
เป็นพารามิเตอร์ (ความพึงพอใจโดยอ้างอิงวิธีการMyInterface::getNumber
และMyInterface::getLong
) และผลตอบแทนซึ่งจะต้องเป็นชนิดเดียวกันเป็นพารามิเตอร์ที่สองไปยังฟังก์ชันR
with
ทีนี้มาดูกันว่านี่จะเป็นกรณีของคุณทั้งหมดหรือไม่:
// 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);
คุณสามารถ "แก้ไข" ปัญหานี้ด้วยตัวเลือกต่อไปนี้:
// 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);
นอกเหนือจากจุดนี้การตัดสินใจออกแบบส่วนใหญ่สำหรับตัวเลือกที่ช่วยลดความซับซ้อนของรหัสสำหรับแอปพลิเคชันของคุณดังนั้นเลือกสิ่งที่เหมาะกับคุณที่สุด
เหตุผลที่คุณไม่สามารถทำสิ่งนี้ได้โดยไม่ต้องส่งคำสั่งนั้นอยู่ในข้อมูลต่อไปนี้จากข้อกำหนดภาษา Java :
การแปลงมวยถือว่าการแสดงออกของประเภทดั้งเดิมเป็นนิพจน์ของประเภทการอ้างอิงที่สอดคล้องกัน โดยเฉพาะการแปลงเก้ารายการต่อไปนี้เรียกว่าการแปลงมวย :
- จากประเภทบูลีนถึงประเภทบูลีน
- จาก type byte เป็น type Byte
- จากประเภทสั้นถึงพิมพ์สั้น
- จากประเภทถ่านเพื่อพิมพ์ตัวละคร
- จาก type int เป็น Type Integer
- จากพิมพ์ยาวเป็นพิมพ์ยาว
- จากประเภทลอยไปลอยประเภท
- จาก type double เป็น type Double
- จากประเภท null เป็นประเภท null
อย่างที่คุณเห็นชัดเจนไม่มีการแปลงมวยโดยปริยายจากยาวเป็นจำนวนและการแปลงแบบขยายจาก Long เป็น Number สามารถเกิดขึ้นได้เมื่อคอมไพเลอร์แน่ใจว่ามันต้องการตัวเลขและไม่ใช่แบบยาว ในฐานะที่มีความขัดแย้งระหว่างอ้างอิงวิธีการที่ต้องใช้จำนวนและ 4L ที่ให้ลองคอมไพเลอร์ (ด้วยเหตุผลบางอย่าง ???) ไม่สามารถที่จะทำให้ก้าวกระโดดตรรกะที่ยาวเป็น-หมายเลขและได้ข้อสรุปว่าเป็นF
Function<MyInterface, Number>
แต่ฉันจัดการเพื่อแก้ไขปัญหาโดยการแก้ไขลายเซ็นฟังก์ชั่นเล็กน้อย:
public <R> Builder<T> with(Function<T, ? super R> getter, R returnValue) {
return null;//TODO
}
หลังจากการเปลี่ยนแปลงนี้เกิดขึ้นต่อไปนี้:
// 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);
แก้ไข:
หลังจากใช้เวลากับมันนานขึ้นมันเป็นเรื่องยากที่จะบังคับใช้ความปลอดภัยของประเภทผู้ใช้ฐาน นี่คือตัวอย่างการทำงานที่ใช้วิธีการตั้งค่าเพื่อบังคับใช้ความปลอดภัยของผู้สร้าง:
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");
}
}
ให้ความสามารถในการสร้างวัตถุที่ปลอดภัยชนิดหวังว่าในอนาคตเราจะสามารถส่งคืนวัตถุข้อมูลที่ไม่เปลี่ยนรูปจากผู้สร้าง (อาจจะเพิ่มtoRecord()
วิธีการอินเทอร์เฟซและระบุผู้สร้างเป็นBuilder<IntermediaryInterfaceType, RecordType>
) ดังนั้นคุณไม่ต้องกังวลเกี่ยวกับวัตถุที่เป็นผลลัพธ์ที่จะถูกแก้ไข จริงๆแล้วมันเป็นเรื่องน่าละอายอย่างยิ่งที่ต้องใช้ความพยายามอย่างมากในการสร้างตัวสร้างฟิลด์ที่ยืดหยุ่นได้ แต่มันอาจเป็นไปไม่ได้หากไม่มีคุณสมบัติใหม่การสร้างรหัสหรือการสะท้อนที่น่ารำคาญ
MyInterface
ไหม