2015-03-28 6 views
22

Ich bekomme einen Fehler auf den folgenden Code, die ich glaube, sollte nicht da sein ... Mit JDK 8u40, um diesen Code zu kompilieren.Verweis auf Methode ist mehrdeutig bei der Verwendung von Lambdas und Generika

public class Ambiguous { 
    public static void main(String[] args) { 
     consumerIntFunctionTest(data -> { 
      Arrays.sort(data); 
     }, int[]::new); 

     consumerIntFunctionTest(Arrays::sort, int[]::new); 
    } 

    private static <T> void consumerIntFunctionTest(final Consumer<T> consumer, final IntFunction<T> generator) { 

    } 

    private static <T> void consumerIntFunctionTest(final Function<T, ?> consumer, final IntFunction<T> generator) { 

    } 
} 

Der Fehler ist der folgende:

Error:(17, 9) java: reference to consumerIntFunctionTest is ambiguous both method consumerIntFunctionTest(java.util.function.Consumer,java.util.function.IntFunction) in net.tuis.ubench.Ambiguous and method consumerIntFunctionTest(java.util.function.Function,java.util.function.IntFunction) in net.tuis.ubench.Ambiguous match

Der Fehler in der folgenden Zeile auftritt:

consumerIntFunctionTest(Arrays::sort, int[]::new); 

Ich glaube, es sollte kein Fehler sein, da alle Arrays::sort Referenzen vom Typ sind void, und keiner von ihnen gibt einen Wert zurück. Wie Sie sehen können, funktioniert es funktioniert, wenn ich explizit das Consumer<T> Lambda erweitern.

Ist das wirklich ein Fehler in javac oder gibt der JLS an, dass das Lambda in diesem Fall nicht automatisch erweitert werden kann? Wenn es das letztere ist, würde ich immer noch denken, dass es seltsam ist, da consumerIntFunctionTest mit als erstes Argument Function<T, ?> nicht übereinstimmen sollte.

+2

Der Ort in der JLS, wo dies definiert werden sollte, ist [15.27.3] (http://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.27. 3). (Habe es nicht im Detail angeschaut). – Jesper

+3

Warum denken Sie, dass 'Funktion ' nicht übereinstimmt? '?' könnte 'Void' auch sein, also stimmt es überein. – tomse

+0

@tomse 'Void' als Rückgabetyp hat nichts mit' void' als Methodentyp zu tun, das 'Void' ist nur ein Objekt. – skiwi

Antwort

7

In Ihrem ersten Beispiel

consumerIntFunctionTest(data -> { 
     Arrays.sort(data); 
    }, int[]::new); 

der Lambda-Ausdruck einen void -kompatible Block hat, die ohne die Notwendigkeit, durch die Struktur des Ausdrucks identifiziert werden kann, die tatsächlichen Arten zu lösen.

Im Gegensatz dazu ist in dem Beispiel

consumerIntFunctionTest(Arrays::sort, int[]::new); 

Verfahren Referenz hat aufgelöst werden, um herauszufinden, ob es entweder übereinstimmt, eine void Funktion (Consumer) oder einen Wert zurückgegeben Funktion (Function). Das gleiche gilt für den vereinfachte Lambda-Ausdruck

consumerIntFunctionTest(data -> Arrays.sort(data), int[]::new); 

der beide sein könnte, void - kompatibel oder wert- kompatibel, auf der aufgelösten Zielmethode abhängig.

Das Problem besteht darin, dass das Auflösen der Methode Kenntnisse über die erforderliche Signatur erfordert, die über die Zieltypisierung bestimmt werden sollte, aber der Zieltyp ist erst bekannt, wenn die Typparameter der generischen Methode bekannt sind. Während in der Theorie beide gleichzeitig bestimmt werden konnten, wurde der (immer noch sehr unangenehme) Prozess in der Spezifikation dahingehend vereinfacht, dass die Überladungsauflösung zuerst durchgeführt wird und die Typinferenz zuletzt angewendet wird (siehe JLS §15.12.2). Daher können die Informationen, die die Typrückschlüsse liefern könnten, nicht zum Lösen der Überladungsauflösung verwendet werden.

Aber beachten Sie, dass der erste in 15.12.2.1. Identify Potentially Applicable Methods beschriebenen Schritt enthält:

An expression is potentially compatible with a target type according to the following rules:

  • A lambda expression (§15.27) is potentially compatible with a functional interface type (§9.8) if all of the following are true:

    • The arity of the target type's function type is the same as the arity of the lambda expression.

    • If the target type's function type has a void return, then the lambda body is either a statement expression (§14.8) or a void-compatible block (§15.27.2).

    • If the target type's function type has a (non-void) return type, then the lambda body is either an expression or a value-compatible block (§15.27.2).

  • A method reference expression (§15.13) is potentially compatible with a functional interface type if, where the type's function type arity is n, there exists at least one potentially applicable method for the method reference expression with arity n (§15.13.1), and one of the following is true:

    • The method reference expression has the form ReferenceType :: [TypeArguments] Identifier and at least one potentially applicable method is i) static and supports arity n, or ii) not static and supports arity n-1.

    • The method reference expression has some other form and at least one potentially applicable method is not static.

The definition of potential applicability goes beyond a basic arity check to also take into account the presence and "shape" of functional interface target types. In some cases involving type argument inference, a lambda expression appearing as a method invocation argument cannot be properly typed until after overload resolution.

So Ihr in erstem Beispiel eines des Verfahrens wird durch das Lambdas der Form, während im Fall eines Verfahrens Referenz oder einen Lambda-Ausdrucks aussortiert aus eines einzigen Aufrufausdrucks erleiden beide potentiell anwendbaren Methoden diesen ersten Auswahlprozess und ergeben einen "mehrdeutigen" Fehler, bevor die Typinferenz eintreten kann, um das Finden einer Zielmethode zu unterstützen, um festzustellen, ob es sich um eine void oder Rückgabewertmethode handelt.

Beachten Sie, dass wie x->{ foo(); } mit einem Lambda-Ausdruck explizit void -kompatible zu machen, Sie x->(foo()) verwenden können, einen Lambda-Ausdruck explizit Wert-kompatibel zu machen.


Sie verrückt weiter this answer erklärt lesen, dass diese Beschränkung des kombinierten Typinferenz und Auflösung überladene Methode eine bewusste (aber nicht leicht) Entscheidung war.

+0

Kann dies auch erklären, warum ich den gleichen Fehler bekomme, wenn ich das '' -Typargument entferne und es durch 'int []' in beiden Parametern ersetze? Das scheint das Generikaproblem zu lösen, gibt aber immer noch diesen Fehler. – skiwi

+1

Wie in der verknüpften Antwort (siehe das Beispiel "comparing") ausgeführt, wird der Rückgabetyp der Lambda-Ausdrucks-/Methodenreferenz bei der Überladungsauflösung nicht berücksichtigt.Beachten Sie, dass neuere Compiler Sie über die mögliche Mehrdeutigkeit der überladenen Methoden nur an der Deklarationsstelle warnen, ohne dass Sie über tatsächlich mehrdeutige Aufrufe herausfinden müssen. Sie wissen "der Zieltyp ist nicht bekannt" gilt für den Compiler (Einhaltung des formalen Prozesses) nicht für uns menschliche Leser und erfordert keine Generics, sondern nur eine strikte Reihenfolge der auflösenden Schritte. – Holger

Verwandte Themen