Wszyscy wiemy, że Long rozciąga się Number
. Dlaczego więc się nie kompiluje?
Jak zdefiniować metodę, with
aby program kompilował się bez ręcznego rzutowania?
import java.util.function.Function;
public class Builder<T> {
static public interface MyInterface {
Number getNumber();
Long getLong();
}
public <F extends Function<T, R>, R> Builder<T> with(F getter, R returnValue) {
return null;//TODO
}
public static void main(String[] args) {
// works:
new Builder<MyInterface>().with(MyInterface::getLong, 4L);
// works:
new Builder<MyInterface>().with(MyInterface::getNumber, (Number) 4L);
// works:
new Builder<MyInterface>().<Function<MyInterface, Number>, Number> with(MyInterface::getNumber, 4L);
// works:
new Builder<MyInterface>().with((Function<MyInterface, Number>) MyInterface::getNumber, 4L);
// compilation error: Cannot infer ...
new Builder<MyInterface>().with(MyInterface::getNumber, 4L);
// compilation error: Cannot infer ...
new Builder<MyInterface>().with(MyInterface::getNumber, Long.valueOf(4));
// compiles but also involves typecast (and Casting Number to Long is not even safe):
new Builder<MyInterface>().with( myInterface->(Long) myInterface.getNumber(), 4L);
// compiles but also involves manual conversion:
new Builder<MyInterface>().with(myInterface -> myInterface.getNumber().longValue(), 4L);
// compiles (compiler you are kidding me?):
new Builder<MyInterface>().with(castToFunction(MyInterface::getNumber), 4L);
}
static <X, Y> Function<X, Y> castToFunction(Function<X, Y> f) {
return f;
}
}
- Nie można wnioskować o argumentach typu dla
<F, R> with(F, R)
- Typ getNumber () z typu Builder.MyInterface to Number, jest to niezgodne z typem zwracanym przez deskryptor: Long
Przykład użycia patrz: Dlaczego nie jest sprawdzany typ zwracany przez lambda w czasie kompilacji
java
type-inference
jukzi
źródło
źródło
MyInterface
?<F extends Function<T, R>, R, S extends R> Builder<T> with(F getter, S returnValue)
ale dostałemjava.lang.Number cannot be converted to java.lang.Long)
, co jest zaskakujące, ponieważ nie widzę, skąd kompilator ma pomysł, nagetter
który trzeba będzie przekonwertować wartość zwracanąreturnValue
.Number getNumber()
na<A extends Number> A getNumber()
sprawia, że rzeczy działają. Nie mam pojęcia, czy tego właśnie chciałeś. Jak powiedzieli inni, problemem jest to, żeMyInterface::getNumber
może to być funkcja, któraDouble
na przykład zwraca, a nie zwracaLong
. Deklaracja nie pozwala kompilatorowi zawęzić typu zwracanego na podstawie innych obecnych informacji. Używając ogólnego typu zwracanego, pozwalasz na to kompilatorowi, dlatego działa.Odpowiedzi:
To wyrażenie:
może być przepisany jako:
Biorąc pod uwagę podpis metody:
R
zostaną wywnioskowane zLong
F
będzieFunction<MyInterface, Long>
i przekazujesz referencję do metody, która zostanie wprowadzona jako
Function<MyInterface, Number>
To jest klucz - w jaki sposób kompilator powinien przewidzieć, że rzeczywiście chcesz powrócićLong
z funkcji z taką sygnaturą? Nie zrobi to za Ciebie.Ponieważ
Number
jest nadklasąLong
iNumber
nie jest necessairlyLong
(dlatego nie skompilować) - trzeba by rzucić jawnie na własną rękę:czyni
F
sięFunction<MyIinterface, Long>
lub przechodzą generyczne argumenty wyraźnie podczas wywołania metody jak ty:i know
R
będzie postrzegane jako,Number
a kod się skompiluje.źródło
with
jest napisane. MaszMJyInterface::getNumber
typFunction<MyInterface, Number>
tak,R=Number
a następnie masz równieżR=Long
z drugiego argumentu (pamiętaj, że literały Java nie są polimorficzne!). W tym momencie kompilator zatrzymuje się, ponieważ nie zawsze można przekonwertować aNumber
na aLong
. Jedynym sposobem, aby to naprawić, jest zmianaMyInterface
na<A extends Number> Number
typ zwracany, co sprawia, że kompilator ma,R=A
a następnie,R=Long
a ponieważA extends Number
może zastąpićA=Long
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 nowyBuilder<MyInterface>
. Deklaracja klasy implikuje zatemT = MyInterface
. Zgodnie z deklaracjąwith
,F
musi byćFunction<T, R>
, co jestFunction<MyInterface, R>
w tej sytuacji. Dlatego parametrgetter
musi przyjąćMyInterface
parametr as (spełniony przez odwołania do metodMyInterface::getNumber
iMyInterface::getLong
) i zwrócićR
, który musi być tego samego typu co drugi parametr funkcjiwith
. Zobaczmy teraz, czy dotyczy to wszystkich twoich przypadków:Możesz „naprawić” ten problem za pomocą następujących opcji:
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 :
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
F
jest toFunction<MyInterface, Number>
.Zamiast tego udało mi się rozwiązać problem, lekko edytując podpis funkcji:
Po tej zmianie następują:
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:
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 aBuilder<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.źródło
Wydaje się, że kompilator użył wartości 4L, aby zdecydować, że R jest Długi, a getNumber () zwraca liczbę, która niekoniecznie jest Długa.
Ale nie jestem pewien, dlaczego wartość ma pierwszeństwo przed metodą ...
źródło
Kompilator Java na ogół nie jest dobry do wnioskowania o wielu / zagnieżdżonych typach ogólnych lub symbolach wieloznacznych. Często nie mogę uzyskać czegoś do skompilowania bez użycia funkcji pomocnika do przechwytywania lub wnioskowania o niektórych typach.
Ale czy naprawdę musisz uchwycić dokładny typ
Function
jakoF
? Jeśli nie, być może poniższe prace, a jak widać, również działają z podtypamiFunction
.źródło
Myślę, że najciekawsza część polega na różnicy między tymi dwoma liniami:
W pierwszym przypadku,
T
to wyraźnieNumber
, więc4L
to równieżNumber
nie ma problemu. W drugim przypadku4L
jest toLong
, a więcT
jestLong
, więc twoja funkcja nie jest kompatybilna, a Java nie może wiedzieć, czy masz na myśliNumber
czyLong
.źródło
Z następującym podpisem:
wszystkie twoje przykłady kompilują się, z wyjątkiem trzeciego, który wyraźnie wymaga, aby metoda miała dwie zmienne typu.
Przyczyną tego, że Twoja wersja nie działa, jest to, że odwołania do metod Java nie mają jednego określonego typu. Zamiast tego mają typ wymagany w danym kontekście. W twoim przypadku
R
zakłada się , że dzieje sięLong
tak z powodu4L
, ale moduł pobierający nie może mieć typu,Function<MyInterface,Long>
ponieważ w Javie typy ogólne są niezmienne w swoich argumentach.źródło
with( getNumber,"NO NUMBER")
co nie jest pożądane. Nie jest też prawdą, że generyczne są zawsze niezmienne (patrz stackoverflow.com/a/58378661/9549750, aby udowodnić, że generyczne setery zachowują się inaczej niż teThing<Cat>
doThing<? extends Animal>
zmiennej, ale na prawdziwe kowariancji Spodziewam się, żeThing<Cat>
może być przypisany doThing<Animal>
. Inne języki, takie jak Kotlin, pozwalają definiować zmienne typu ko- i kontrowariantnego.