Nous savons tous que Long s'étend Number
. Alors pourquoi cela ne compile-t-il pas?
Et comment définir la méthode with
pour que le programme compile sans aucune conversion manuelle?
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;
}
}
- Impossible de déduire les arguments de type pour
<F, R> with(F, R)
- Le type de getNumber () du type Builder.MyInterface est Number, cela est incompatible avec le type de retour du descripteur: Long
Pour un cas d'utilisation, voir: Pourquoi le type de retour lambda n'est-il pas vérifié au moment de la compilation
java
type-inference
jukzi
la source
la source
MyInterface
?<F extends Function<T, R>, R, S extends R> Builder<T> with(F getter, S returnValue)
mais j'ai obtenujava.lang.Number cannot be converted to java.lang.Long)
, ce qui est surprenant car je ne vois pas où le compilateur a l'idée que la valeur de retourgetter
devra être convertiereturnValue
.Number getNumber()
pour<A extends Number> A getNumber()
faire fonctionner les choses. Je ne sais pas si c'est ce que tu voulais. Comme d'autres l'ont dit, le problème est que celaMyInterface::getNumber
pourrait être une fonction qui revientDouble
par exemple et nonLong
. Votre déclaration ne permet pas au compilateur d'affiner le type de retour en fonction d'autres informations présentes. En utilisant un type de retour générique, vous autorisez le compilateur à le faire, donc cela fonctionne.Réponses:
Cette expression:
peut être réécrit comme:
Prise en compte de la signature de la méthode:
R
sera déduit deLong
F
seraFunction<MyInterface, Long>
et vous passez une référence de méthode qui sera déduite comme
Function<MyInterface, Number>
ceci est la clé - comment le compilateur devrait-il prédire que vous voulez réellement revenirLong
d'une fonction avec une telle signature? Il ne fera pas le downcasting pour vous.Étant donné que
Number
c'est une superclasse deLong
etNumber
n'est pas nécessairement unLong
(c'est pourquoi il ne se compile pas) - vous devrez effectuer un cast explicite par vous-même:faire
F
pour êtreFunction<MyIinterface, Long>
ou passer des arguments génériques explicitement pendant l'appel de méthode comme vous l'avez fait:et know
R
sera vu commeNumber
et le code sera compilé.la source
with
texte est écrit. Vous avez donc leMJyInterface::getNumber
typeFunction<MyInterface, Number>
ainsiR=Number
et vous avez égalementR=Long
de l'autre argument (rappelez-vous que les littéraux Java ne sont pas polymorphes!). À ce stade, le compilateur s'arrête car il n'est pas toujours possible de convertir aNumber
en aLong
. La seule façon de résoudre ce problème est de changerMyInterface
pour utiliser<A extends Number> Number
comme type de retour, ce qui fait que le compilateur aR=A
et puisR=Long
et puisqu'ilA extends Number
peut se substituerA=Long
La clé de votre erreur est dans la déclaration générique du type de
F
:F extends Function<T, R>
. La déclaration qui ne fonctionne pas est: d'new Builder<MyInterface>().with(MyInterface::getNumber, 4L);
abord, vous avez un nouveauBuilder<MyInterface>
. La déclaration de la classe implique doncT = MyInterface
. Selon votre déclaration dewith
,F
doit être unFunction<T, R>
, ce qui est unFunction<MyInterface, R>
dans cette situation. Par conséquent, le paramètregetter
doit prendre unMyInterface
paramètre as (satisfait par les références de méthodeMyInterface::getNumber
etMyInterface::getLong
) et retournerR
, qui doit être du même type que le deuxième paramètre de la fonctionwith
. Voyons maintenant si cela vaut pour tous vos cas:Vous pouvez "résoudre" ce problème avec les options suivantes:
Au-delà de ce point, c'est principalement une décision de conception pour laquelle l'option réduit la complexité du code pour votre application particulière, alors choisissez celle qui vous convient le mieux.
La raison pour laquelle vous ne pouvez pas le faire sans transtyper réside dans ce qui suit, à partir de la spécification du langage Java :
Comme vous pouvez le voir clairement, il n'y a pas de conversion de boxe implicite de long en Number, et la conversion d'élargissement de Long en Number ne peut se produire que lorsque le compilateur est sûr qu'il nécessite un nombre et non un long. Comme il existe un conflit entre la référence de méthode qui nécessite un nombre et le 4L qui fournit un long, le compilateur (pour une raison quelconque ???) ne peut pas faire le saut logique que long est un nombre et déduire que
F
c'est unFunction<MyInterface, Number>
.Au lieu de cela, j'ai réussi à résoudre le problème en modifiant légèrement la signature de la fonction:
Après cette modification, les événements suivants se produisent:
Edit:
Après y avoir passé plus de temps, il est énormément difficile d'appliquer la sécurité de type getter. Voici un exemple de travail qui utilise des méthodes de définition pour appliquer la sécurité de type d'un générateur:
Pourvu de la capacité de type sûr de construire un objet, nous espérons qu'à un moment donné dans le futur, nous serons en mesure de renvoyer un objet de données immuable à partir du générateur (peut-être en ajoutant une
toRecord()
méthode à l'interface et en spécifiant le générateur en tant queBuilder<IntermediaryInterfaceType, RecordType>
), vous n'avez donc même pas à vous soucier de la modification de l'objet résultant. Honnêtement, c'est une honte absolue qu'il faut tant d'efforts pour obtenir un constructeur flexible sur le terrain, mais c'est probablement impossible sans de nouvelles fonctionnalités, la génération de code ou une quantité ennuyeuse de réflexion.la source
Il semble que le compilateur ait utilisé la valeur 4L pour décider que R est long, et getNumber () renvoie un nombre, qui n'est pas nécessairement un long.
Mais je ne sais pas pourquoi la valeur prime sur la méthode ...
la source
Le compilateur Java n'est généralement pas bon pour déduire des types génériques multiples ou imbriqués ou des caractères génériques. Souvent, je ne peux pas obtenir quelque chose à compiler sans utiliser une fonction d'assistance pour capturer ou déduire certains types.
Mais, avez-vous vraiment besoin de capturer le type exact de
Function
asF
? Sinon, peut-être que les œuvres suivantes, et comme vous pouvez le voir, semblent également fonctionner avec des sous-types deFunction
.la source
La partie la plus intéressante réside dans la différence entre ces 2 lignes, je pense:
Dans le premier cas, l'
T
est est explicitementNumber
, tout comme l'4L
est aussiNumber
, pas de problème. Dans le second cas,4L
est unLong
, toutT
comme unLong
, donc votre fonction n'est pas compatible, et Java ne peut pas savoir si vous vouliez direNumber
ouLong
.la source
Avec la signature suivante:
tous vos exemples sont compilés, sauf le troisième, qui requiert explicitement que la méthode ait deux variables de type.
La raison pour laquelle votre version ne fonctionne pas est que les références de méthode Java n'ont pas de type spécifique. Au lieu de cela, ils ont le type requis dans le contexte donné. Dans votre cas,
R
est supposé êtreLong
dû à la4L
, mais le getter ne peut pas avoir le typeFunction<MyInterface,Long>
car en Java, les types génériques sont invariants dans leurs arguments.la source
with( getNumber,"NO NUMBER")
ce qui n'est pas souhaité. De plus, il n'est pas vrai que les génériques sont toujours invariants (voir stackoverflow.com/a/58378661/9549750 pour prouver que les génériques des setters se comportent autrement que ceux des getters)Thing<Cat>
à uneThing<? extends Animal>
variable, mais pour une covariance réelle, je m'attends à ce que aThing<Cat>
puisse être affecté à aThing<Animal>
. D'autres langages, comme Kotlin, permettent de définir des variables de type co et contravariantes.