Chìa khóa cho lỗi của bạn nằm ở phần khai báo chung của loại F
: F extends Function<T, R>
. Tuyên bố không hoạt động là: new Builder<MyInterface>().with(MyInterface::getNumber, 4L);
Đầu tiên, bạn có một cái mới Builder<MyInterface>
. Tuyên bố của lớp do đó ngụ ý T = MyInterface
. Theo tuyên bố của bạn with
, F
phải là một Function<T, R>
, đó là một Function<MyInterface, R>
trong tình huống này. Do đó, tham số getter
phải lấy MyInterface
tham số dưới dạng (được thỏa mãn bởi các tham chiếu phương thức MyInterface::getNumber
và MyInterface::getLong
) và trả về R
, phải cùng loại với tham số thứ hai cho hàm with
. Bây giờ, hãy xem nếu điều này giữ cho tất cả các trường hợp của bạn:
// 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);
Bạn có thể "khắc phục" sự cố này bằng các tùy chọn sau:
// 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);
Ngoài thời điểm này, chủ yếu là quyết định thiết kế cho tùy chọn nào làm giảm độ phức tạp mã cho ứng dụng cụ thể của bạn, vì vậy hãy chọn bất cứ thứ gì phù hợp với bạn nhất.
Lý do mà bạn không thể thực hiện việc này mà không truyền nằm ở phần sau, từ Đặc tả ngôn ngữ Java :
Chuyển đổi quyền anh coi các biểu thức của kiểu nguyên thủy là biểu thức của kiểu tham chiếu tương ứng. Cụ thể, chín chuyển đổi sau đây được gọi là chuyển đổi quyền anh :
- Từ loại boolean đến loại Boolean
- Từ kiểu byte đến kiểu Byte
- Từ loại ngắn đến loại Ngắn
- Từ kiểu char đến kiểu Nhân vật
- Từ kiểu int đến kiểu Integer
- Từ loại dài đến loại Dài
- Từ kiểu float đến kiểu Float
- Từ loại đôi đến loại Đôi
- Từ loại null đến loại null
Như bạn có thể thấy rõ, không có chuyển đổi quyền anh ngầm từ dài thành Số và việc chuyển đổi mở rộng từ Dài thành Số chỉ có thể xảy ra khi trình biên dịch chắc chắn rằng nó yêu cầu Số chứ không phải Dài. Vì có mâu thuẫn giữa tham chiếu phương thức yêu cầu Số và 4L cung cấp Long, trình biên dịch (vì lý do nào đó ???) không thể thực hiện bước nhảy vọt logic mà Long là - Số và suy ra đó F
là a Function<MyInterface, Number>
.
Thay vào đó, tôi quản lý để giải quyết vấn đề bằng cách chỉnh sửa một chút chữ ký hàm:
public <R> Builder<T> with(Function<T, ? super R> getter, R returnValue) {
return null;//TODO
}
Sau khi thay đổi, điều sau đây xảy ra:
// 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);
Chỉnh sửa:
Sau khi dành nhiều thời gian hơn cho nó, thật khó khăn để thực thi an toàn loại dựa trên getter. Đây là một ví dụ hoạt động sử dụng các phương thức setter để thực thi loại an toàn của nhà xây dựng:
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");
}
}
Được cung cấp khả năng an toàn kiểu để xây dựng một đối tượng, hy vọng tại một thời điểm nào đó trong tương lai chúng ta sẽ có thể trả về một đối tượng dữ liệu bất biến từ trình xây dựng (có thể bằng cách thêm một toRecord()
phương thức vào giao diện và chỉ định trình xây dựng là a Builder<IntermediaryInterfaceType, RecordType>
), vì vậy bạn thậm chí không phải lo lắng về kết quả đối tượng bị sửa đổi. Thành thật mà nói, thật đáng xấu hổ khi nó đòi hỏi rất nhiều nỗ lực để có được một trình xây dựng linh hoạt trường an toàn loại, nhưng có lẽ không thể nếu không có một số tính năng mới, tạo mã hoặc phản xạ khó chịu.
MyInterface
không?