2017-01-12 4 views
0

Supposons que je souhaite écrire une méthode buildBiFxnWithSameTypeArgs, qui produise un argument à deux arguments BiFunction dont les arguments d'entrée sont du même type. Il n'est pas connu à l'avance (c'est-à-dire quand buildBiFxnWithSameTypeArgs est invoqué) spécifiquement quel type est - je veux juste que le compilateur applique que lorsque la fonction renvoyée par buildBiFxnWithSameTypeArgs est invoquée, les types d'arguments qui lui sont fournis doivent correspondre. L'effet final devrait être, essentiellement, l'équivalent lambda de l'utilisation du même paramètre de type générique pour le type de deux arguments dans une définition de méthode.Retour d'une fonction lambda avec des types de paramètres contraints par des paramètres de type générique

Ma première tentative se présente comme suit:

public interface ConstrainedBiFunction<I, O> extends BiFunction<I, I, O> {} 

public static ConstrainedBiFunction<?, String> buildBiFxnWithSameTypeArgs() { 
    return (inputOne, inputTwo) -> String.valueOf(inputOne) + String.valueOf(inputTwo); 
} 

public void test() { 
    buildBiFxnWithSameTypeArgs().apply(Integer.valueOf(1), Integer.valueOf(2)); 
} 

Il semble, cependant, que le paramètre de type générique ? ne peut pas être utilisé de cette façon; compilation échoue sur l'étape apply avec l'erreur suivante:

apply (capture<?>, capture<?> in BiFunction cannot be applied to (java.lang.Integer, java.lang.Integer) 

Est-il possible de retourner une fonction lambda dont les types de paramètres sont inférées, puis contraint par rapport à l'autre de cette façon?

EDIT: Excuses pour la question pas très bonne; J'avais un problème plus complexe dont j'essayais de capturer l'essence pour poser la question la plus simple possible, mais il semble que j'ai trop simplifié le problème. Je suis conscient, bien sûr, que toutes les classes héritent de Object, donc dans cette interprétation trop simplifiée du problème, les solutions proposées par @shmosel et @JB Nizet fonctionnent. Je posterai une nouvelle question une fois que j'ai fait un meilleur travail en distillant le problème original.

+0

Comment pensez-vous que '' ConstrainedBiFunction concerne '.Appliquer (Integer.valueOf (1), Integer.valueOf (2));'? – Savior

+2

Toutes les classes finissent par hériter de Object, donc même si vous passez, par exemple, un Banana et un Truck, le compilateur l'interprétera comme un objet ConstrainedBuFunction , sauf si vous spécifiez explicitement le type. –

Répondre

1

Le caractère générique signifie "d'un type spécifique, mais inconnu". Vous ne pouvez pas passer un argument qui satisfera un paramètre de type ?, car il peut être du mauvais type. Modifier le type de retour à ConstrainedBiFunction<Object, String> et il sera en mesure d'accepter tout type d'entrée, puisque chaque classe étend implicitement Object:

public static ConstrainedBiFunction<Object, String> buildBiFxnWithSameTypeArgs() { 
    return (inputOne, inputTwo) -> String.valueOf(inputOne) + String.valueOf(inputTwo); 
} 

Notez que cela peut encore être utilisé pour les méthodes avec des restrictions sur le type d'entrée, en utilisant la PECS principe. Par exemple:

// input parameter must be of type Integer or any supertype, 
// so that we can safely pass in an Integer 
String combine(ConstrainedBiFunction<? super Integer, String> function) { 
    return function.apply(1, 2); 
} 

void test() { 
    ConstrainedBiFunction<Object, String> concat = buildBiFxnWithSameTypeArgs(); 
    ConstrainedBiFunction<Integer, String> sum = (a, b) -> String.valueOf(a + b); 
    System.out.println(combine(concat)); // "12" 
    System.out.println(combine(sum)); // "3" 
} 
0

Alors que shmosel’s answer souligne à juste titre que l'utilisation Object comme type d'entrée doit être suffisante pour toutes les API qui suivent la règle de PECS, il est possible de corriger votre tentative originale.

Au lieu d'un caractère générique, vous devez ajouter un paramètre de type qui permet à l'appelant de choisir un type:

public interface ConstrainedBiFunction<I, O> extends BiFunction<I, I, O> {} 

public static <T> ConstrainedBiFunction<T, String> buildBiFxnWithSameTypeArgs() { 
    return (inputOne, inputTwo) -> String.valueOf(inputOne) + String.valueOf(inputTwo); 
} 

public void test() { 
    ConstrainedBiFunction<Integer, String> f1 = buildBiFxnWithSameTypeArgs(); 
    String s1 = f1.apply(1, 1); 
    ConstrainedBiFunction<String, String> f2 = buildBiFxnWithSameTypeArgs(); 
    String s2 = f2.apply("hello ", "world"); 
} 

Notez que lorsque vous l'utilisez comme buildBiFxnWithSameTypeArgs().apply(1, 1);, le compilateur déduire Object pour T de toute façon. Ceci permet de passer des arguments Integer, mais, bien sûr, aussi tout autre chose, ce qui rend l'idée d'avoir le même type pour les deux arguments, c'est-à-dire buildBiFxnWithSameTypeArgs().apply(1.5, "foo"); fonctionne aussi, car les deux arguments sont assignables au même type Object.


Il existe des exemples concrets pour de telles constructions traitant de problèmes non résolubles via PECS.

E.g. il y a Function.identity() renvoyant une fonction typée arbitraire ayant le même type d'entrée et de sortie. Lorsque vous passez cette fonction au toMap collector, vous pouvez obtenir une carte ayant la même clé ou le même type de valeur que les éléments de flux. Un exemple plus complexe est le suivant: . Ici, le paramètre type a une limite qui garantit que les éléments à comparer sont en effet Comparable. Puisque les types n'implémentent pas Comparable<?>, mais plutôt un type se référant à lui-même, le naturalOrder() ne fonctionnerait pas sans le paramètre type. Vous ne pouvez pas faire Comparator.naturalOrder().compare(42, "foo"). Mais vous pouvez faire

Comparator<Integer> c1 = Comparator.naturalOrder(); 
c1.compare(10, 42); 
Comparator<String> c2 = Comparator.naturalOrder(); 
c2.compare("foo", "bar");