2013-01-31 11 views
8

Warum verwenden die Factory-Factory-Methoden häufig einen spezifischen generischen Parameter (wie T) anstelle eines beschränkten Wildcard-Parameters (wie ? super T)?In Guava, warum wird nur "T" verwendet, wo "Super T" möglich wäre?

Zum Beispiel die Unterschrift von Functions#forPredicate ist:

public static <T> Function<T, Boolean> forPredicate(Predicate<T> predicate) 

Warum nicht:

public static <T> Function<T, Boolean> forPredicate(Predicate<? super T> predicate) 

Welche wäre so etwas wie die folgenden möglich machen?

Predicate<Number> isPositivePredicate = ... 
Function<Integer, Boolean> isPositiveInteger = Functions.forPredicate(isPositivePredicate); 
// above line is compiler error: 
// Type mismatch: cannot convert from Function<Number,Boolean> to Function<Integer,Boolean> 

Ist es, weil Verbraucher von Function und Predicate zu erwarten sind, die notwendigen beschränkten Wildcard Parameter haben, um diese überflüssig zu machen? Zum Beispiel würden die allgemeinen Grenzen für Iterables#find erlauben eine Predicate<Number> zu auf einem Iterable<Integer> verwendet werden:

public static <T> T find(Iterable<T> iterable, 
         Predicate<? super T> predicate) 

Gibt es andere Gründe?

Antwort

6

Ja, es ist absolut richtig, dass wir die Verbraucher erwarten, dass das Recht begrenzt Wildcard-Parameter haben, aber ein paar weitere Gründe in den Sinn:

  • Im Allgemeinen haben wir nicht die Typen von generischen Methoden erweitern bis wir einen bestimmten Grund haben. Diese Politik hat sich mehrfach ausgezahlt.
  • Java-Typ-Inferenz ist nicht immer in der Lage, erweiterte Generika automatisch herauszufinden, so dass die schmaleren Generika zu halten reduziert die Anzahl der Benutzer, die explizit angeben müssen T.
+0

re. Der erste Punkt drängt den Verbraucher nicht einfach dazu, zu werfen, was das Schlimmste von zwei Übeln ist? Auch neugierig, wenn du "wir" sagst, meinst du, du hast dich auf diese oder nur die Software-Community im Allgemeinen entwickelt. – djechlin

+0

Genauer gesagt - wie hat sich diese Politik in der Praxis bezahlt gemacht? Derzeit die einzigen Situationen, in denen ich es mir vorstellen kann, würde der Verbraucher nur mit Casting zu abgeleiteten Typ umgehen. – djechlin

+0

Wenn ich "wir" sage, meine ich, dass ich im Google Java Core Libraries Team bin, das Guava entwickelt. Aber der Konsument sollte nie zu casten haben: Ein 'Prädikat ' kann immer direkt verwendet werden, wo immer Sie ein 'Prädikat ' verwenden würden. Es gibt keine Notwendigkeit zu casten; Sie können auf genau die gleichen Werte angewendet werden. –

2

Im find() Beispiel kann immer eindeutig auf T gefolgert werden.

In dem Beispiel forPredicate[1]() kann T auch eindeutig abgeleitet werden. Im forPredicate[2]() Beispiel gibt es Unsicherheit, was T sein sollte. Wenn das Ergebnis der Methode einem Zieltyp zugeordnet ist, kann aus dem Zieltyp T ermittelt werden. Sonst ist es ein wenig Kopf kratzen:

forPredicate(isPositive).apply(42); // T is a subtype of Number, but which one? 

In Java5/6 sollte es nicht kompilieren. (Gut Getestet habe ich es auf Java 6 und es läßt sich, aber es ist wahrscheinlich ein Fehler, da auch Java 6 kompiliert forPredicate(p).apply("str"))

Java 7 ein wenig verbessert, und die neue Regel vor, dass T=Number zu diktieren. Es funktioniert, aber es fühlt sich eher wie ein Schiedsgericht an.


Idealerweise sollten wir uns keine Gedanken über Platzhalter machen. Wenn ich ein Prädikat für meine Ganzzahlen benötige, sollte ich einen Predicate<Integer> Parameter deklarieren. Die Tatsache, dass ein Predicate<Number> Argument auch akzeptabel wäre, ist eine andere Geschichte. Es sollte die Aufgabe des Compilers sein, Predicate<Number> in Predicate<Integer> umzuwandeln - wir können es tun, ohne existierende Java-Generika zu überholen, eine neue Konvertierungsregel ist alles, was benötigt wird.Man kann auch eine Umwandlung Bibliothek

Predicate<Number> pn = ...; 
Predicate<Integer> pi = convert(pn); 

Iterable<Integer> iter1 = ...; 
Iterable<Number> iter2 = convert(iter1); 

Alle convert() Methoden mechanisch erzeugt werden kann.


Java 8 machen die Dinge ein wenig einfacher. Wir können immer noch nicht tun

Predicate<Number> pn = n -> n.intValue()>0; 
Predicate<Integer> pi = pn; // error! 

aber wir können

Predicate<Integer> pi = pn::test; // ok 
// it means  pi = (Integer i)->pn.test(i) 

auch

Function<Integer, Boolean> f = pn::test; // ok 

tun, was f = forPredicate[2](pn) entspricht. In Java 8 brauchen wir selten forPredicate() usw., um zwischen Funktionstypen zu konvertieren.

+0

Es gibt kein Problem mit 'forPredicate (isPositive) .apply (42)' , obwohl — in diesem Fall 'T' ist der spezifischste Typ kann es sein,' Number'. 'Integer' erbt von' Number', der 'apply'-Aufruf ist also in Ordnung. Wenn es kompiliert wird, wenn Sie eine Zeichenfolge übergeben, klingt es irgendwie so, als wäre es mit Rawtypes oder etwas kompiliert worden. – matts

+0

Mein Gedanke ist: Lassen Sie mich sagen, ich habe eine private 'Prädikat ' in meiner Klasse, und ich möchte es von einer Methode als eine Funktion ', ich sollte nicht auf Aufrufer ausgesetzt sein, dass es ist wirklich ein 'Prädikat '. Und da es alle 'Numbers' akzeptiert, akzeptiert es auch alle' Integer's. Um dies jetzt zu tun, müsste ich es mit einer Wrapper-Klasse oder einer expliziten Klasse konvertieren. – matts

+0

Die politisch korrekte Antwort wäre, dass der Methodenrückgabetyp 'Funktion 'stattdessen. Das ist einfach zu hässlich. – irreputable