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 nouveau Builder<MyInterface>. La déclaration de la classe implique donc T = MyInterface. Selon votre déclaration de with, Fdoit être un Function<T, R>, ce qui est un Function<MyInterface, R>dans cette situation. Par conséquent, le paramètre getterdoit prendre un MyInterfaceparamètre as (satisfait par les références de méthode MyInterface::getNumberet MyInterface::getLong) et retourner R, qui doit être du même type que le deuxième paramètre de la fonction with. Voyons maintenant si cela vaut pour tous vos cas:
// 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);
Vous pouvez "résoudre" ce problème avec les options suivantes:
// 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);
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 :
La conversion de boxe traite les expressions d'un type primitif comme des expressions d'un type de référence correspondant. Plus précisément, les neuf conversions suivantes sont appelées les conversions de boxe :
- Du type booléen au type booléen
- Du type octet au type octet
- Du type court au type court
- Du type char au type Character
- Du type int au type Integer
- Du type long au type Long
- Du type float au type Float
- Du type double au type Double
- Du type nul au type nul
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 Fc'est un Function<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:
public <R> Builder<T> with(Function<T, ? super R> getter, R returnValue) {
return null;//TODO
}
Après cette modification, les événements suivants se produisent:
// 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);
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:
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");
}
}
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 que Builder<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.
MyInterface?