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