2015-01-28 9 views
9

Der folgende TestZwei genaue Methode Referenzen sind nicht gleich

@Test 
public void test() { 
    Function<String, Integer> foo = Integer::parseInt; 
    Function<String, Integer> bar = Integer::parseInt; 
    assertThat(foo, equalTo(bar)); 
} 

ist es eine Möglichkeit, es passieren zu machen scheitert?

bearbeiten: Ich werde versuchen, es klarer zu machen, was ich versuche zu tun.

Lets sagen, ich habe diese Klassen:

class A { 
    public int foo(Function<String, Integer> foo) {...} 
} 

class B { 
    private final A a; // c'tor injected 
    public int bar() { 
    return a.foo(Integer::parseInt); 
    } 
} 

kann jetzt sagen, dass ich Unit-Test für B schreiben will:

@Test 
public void test() { 
    A a = mock(A.class); 
    B b = new B(a); 
    b.bar(); 
    verify(a).foo(Integer::parseInt); 
} 

das Problem ist, dass der Test nicht bestanden, weil die Methode Referenzen nicht gleich.

+1

"das hindert mich daran, einen Einheitentest für eine Methode zu schreiben, die eine Funktion als Argument nimmt" <- erster Tipp: Der Test testet nicht, was er sollte. Was, wenn Sie etwas mehr über den fraglichen Test erzählten? – fge

+0

Ich würde annehmen, dass der Weg, um zu testen, dass zwei Funktionen äquivalent sind, darin besteht, Zeug in sie einzuziehen und zu überprüfen, dass das gleiche Ding auf der anderen Seite herauskommt. – biziclop

+1

@biziclop aber Sie müssten die gesamte Domäne der Eingabewerte testen, und manchmal können Sie es einfach nicht tun: p – fge

Antwort

11

Lambdas werden nicht zwischengespeichert und dies scheint absichtlich zu sein. Es gibt keine Möglichkeit, zwei Lambdas zu vergleichen, um zu sehen, ob sie das Gleiche tun würden.

Sie müssen etwas tun, wie

static final Function<String, Integer> parseInt = Integer::parseInt; 

@Test 
public void test() { 
    Function<String, Integer> foo = parseInt; 
    Function<String, Integer> bar = parseInt; 
    assertThat(foo, equalTo(bar)); 
} 

Antwort von Brian Goetz; Is there a way to compare lambdas?

4

Ich habe nicht die API zur Hand, aber Funktion ist eine Schnittstelle. Integer :: parseInt scheint nicht zu cachen, daher gibt es zwei verschiedene Instanzen zurück, die durch Referenz => false verglichen werden.

Sie können es passieren, indem Sie einen Komparator schreiben, der das tut, was Sie wollen.

+0

Und wenn Sie darüber nachdenken, wie sonst könnten sie verglichen werden? – biziclop

+5

Ich bezweifle, dass es irgendeine Komparatorimplementierung gibt, die zumindest auf portable Weise tun würde, was OP will. –

+0

@MarkoTopolnik Ja, der Hauptgrund dafür, dass "MethodHandle's nicht einfach einfache Methodenzeiger sind, können Sie zusammengesetzte oder vollständig künstliche Methodenhandles erstellen, die nicht auf eine tatsächliche Methode einer realen Klasse verweisen. Und wie sagt man einem Handle einer Methode, die nichts anderes tut, als eine 'UnsupportedOperationException' zu werfen, eine Methode, die man mit' MethodHandles.throwException() '' erstellt? – biziclop

0

Es ist OK, dass der Test nicht besteht. Lambdas sind keine Objekte, sie unterliegen keinen Eigenschaften wie der Objektidentität. Stattdessen handelt es sich um adhoc-Implementierungen von funktionalen Schnittstellen.

Ich glaube, Sie sollten nicht erwarten, dass Ihr Code sich auf das von Ihnen beschriebene Verhalten stützt.

1

Haben Blick auf der Java Language Specification:

15.27.4. Run-time Evaluation of Lambda Expressions

Zur Laufzeit Auswertung eines Lambda-Ausdrucks ist ähnlich Auswertung einer Schöpfung Ausdruck Klasseninstanz, sofern normale Beendigung eine Referenz erzeugt zu einem Objekt. Die Auswertung eines Lambda-Ausdrucks unterscheidet sich von der Ausführung des Lambda-Körpers.

Entweder wird eine neue Instanz einer Klasse mit den folgenden Eigenschaften zugewiesen und initialisiert, oder eine vorhandene Instanz einer Klasse mit den folgenden Eigenschaften wird referenziert.

  • bei jeder Auswertung zugewiesen Ein neues Objekt muss nicht sein:

    ...

    Diese Regeln Flexibilität zu Implementierungen der Java-Programmiersprache, dass, bieten soll.

  • Objekte, die durch verschiedene Lambda-Ausdrücke erzeugt werden, müssen nicht zu verschiedenen Klassen gehören (wenn die Körper zum Beispiel identisch sind).

  • Jedes von der Evaluierung erstellte Objekt muss nicht derselben Klasse angehören (erfasste lokale Variablen können z. B. inline sein).

  • Wenn eine "existierende Instanz" verfügbar ist, muss sie nicht bei einer vorherigen Lambda-Evaluierung erstellt worden sein (sie wurde beispielsweise während der Initialisierung der umschließenden Klasse zugewiesen).

Im Prinzip bedeutet dies, dass auch nur ein einziges Auftreten von Integer::parseInt im Quellcode zu verschiedenen Objektinstanzen (auch unterschiedlicher Klassen) führen kann, wenn sie mehrmals ausgewertet werden, nicht von mehreren Vorkommen zu sprechen davon. Die genaue Entscheidung bleibt der eigentlichen JRE-Implementierung überlassen. Siehe this answer, in dem das aktuelle Verhalten der Implementierung von Oracle erläutert wird.

Verwandte Themen