Ключ к вашей ошибке находится в общем объявлении типа 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 :
Преобразование бокса обрабатывает выражения примитивного типа как выражения соответствующего ссылочного типа. В частности, следующие девять преобразований называются преобразованиями в бокс :
- Из типа Boolean в тип Boolean
- Из байта типа в тип Byte
- От типа short к типу Short
- От типа char к типу Character
- От типа int к типу Integer
- От типа long к типу Long
- Сот типа float до типа Float
- От типа double до типа Double
- От типа null до типа null
Как вы можете видеть,не является неявным преобразованием бокса из long в Number, и расширение преобразования из Long в Number может происходить, только когда компилятор уверен, что ему требуется число, а не LongПоскольку существует конфликт между ссылкой на метод, которая требует Number, и 4L, который предоставляет Long, компилятор (по какой-то причине ???) не может сделать логический скачок, что Long is-a Number, и вывести, что F
isa 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>
), так что вам даже не придется беспокоиться о результирующем объектебыть измененным. Честно говоря, это просто позор, что требуется так много усилий, чтобы получить безопасный для типов компоновщик с гибкими полями, но это, вероятно, невозможно без некоторых новых функций, генерации кода или раздражающего количества размышлений.