Ключ до вашої помилку в загальній декларації типу 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
- Від нульового типу до нульового типу
Як ви добре бачите, немає неявного перетворення боксу з довгого в число, а розширення перетворення з довгого в число може відбуватися лише тоді, коли компілятор впевнений, що йому потрібне число, а не довге. Оскільки існує конфлікт між посиланням на метод, який вимагає Числа, та 4L, що забезпечує Long, компілятор (чомусь ???) не в змозі зробити логічний стрибок, що Long is-a Number, і вивести, що Fє a 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?