Kluczem do błędu jest w ogólnym deklaracji typu F: F extends Function<T, R>. Oświadczenie, które nie działa: new Builder<MyInterface>().with(MyInterface::getNumber, 4L);Po pierwsze, masz nowy Builder<MyInterface>. Deklaracja klasy implikuje zatem T = MyInterface. Zgodnie z deklaracją with, Fmusi być Function<T, R>, co jest Function<MyInterface, R>w tej sytuacji. Dlatego parametr gettermusi przyjąć MyInterfaceparametr as (spełniony przez odwołania do metod MyInterface::getNumberi MyInterface::getLong) i zwrócić R, który musi być tego samego typu co drugi parametr funkcji with. Zobaczmy teraz, czy dotyczy to wszystkich twoich przypadków:
// 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);
Możesz „naprawić” ten problem za pomocą następujących opcji:
// 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);
Poza tym jest to głównie decyzja projektowa, dla której opcja zmniejsza złożoność kodu dla konkretnej aplikacji, więc wybierz to, co najbardziej ci odpowiada.
Powód, dla którego nie można tego zrobić bez przesyłania, jest następujący: ze specyfikacji języka Java :
Konwersja boksu traktuje wyrażenia typu pierwotnego jako wyrażenia odpowiedniego typu odwołania. W szczególności następujące dziewięć konwersji nazywane są konwersjami bokserskimi :
- Od typu logicznego do typu logicznego
- Od typu bajtu do typu Byte
- Od typu krótkiego do typu krótkiego
- Od typu char do typu Character
- Od typu int do typu Integer
- Od typu długiego do typu Długiego
- Od typu float do type Float
- Od typu double do typu double
- Od typu zerowego do typu zerowego
Jak wyraźnie widać, nie istnieje niejawna konwersja boksu z długiej na liczbę, a konwersja rozszerzająca z długiej na liczbę może wystąpić tylko wtedy, gdy kompilator jest pewien, że wymaga liczby, a nie długiej. Ponieważ istnieje konflikt między odwołaniem do metody wymagającym liczby a wartością 4L zapewniającą wartość Long, kompilator (z jakiegoś powodu ???) nie może wykonać logicznego skoku, że Long jest liczbą i wydedukować, że Fjest to Function<MyInterface, Number>.
Zamiast tego udało mi się rozwiązać problem, lekko edytując podpis funkcji:
public <R> Builder<T> with(Function<T, ? super R> getter, R returnValue) {
return null;//TODO
}
Po tej zmianie następują:
// 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);
Edycja:
po spędzeniu nad tym trochę czasu, irytująco trudno jest egzekwować bezpieczeństwo oparte na getterach. Oto działający przykład, który wykorzystuje metody ustawiające w celu wymuszenia bezpieczeństwa typu konstruktora:
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");
}
}
Pod warunkiem, że jest to bezpieczna dla typu konstrukcja obiektu, mam nadzieję, że w pewnym momencie w przyszłości będziemy mogli zwrócić niezmienny obiekt danych z konstruktora (może dodając toRecord()metodę do interfejsu i określając konstruktor jako a Builder<IntermediaryInterfaceType, RecordType>), więc nie musisz się nawet martwić modyfikacją powstałego obiektu. Szczerze mówiąc, to absolutny wstyd, że wymaga tyle wysiłku, aby uzyskać bezpieczną dla typu, elastycznego konstruktora, ale prawdopodobnie jest to niemożliwe bez nowych funkcji, generowania kodu lub irytującej refleksji.
MyInterface?