2015-02-13 6 views
18

Ich war auf der Suche durch Java-Quellcode für die Map Schnittstelle und lief in diesen kleinen Code-Schnipsel:Java Lambda-Ausdrücke, Gießen und Komparatoren

/** 
    * Returns a comparator that compares {@link Map.Entry} in natural order on value. 
    * 
    * <p>The returned comparator is serializable and throws {@link 
    * NullPointerException} when comparing an entry with null values. 
    * 
    * @param <K> the type of the map keys 
    * @param <V> the {@link Comparable} type of the map values 
    * @return a comparator that compares {@link Map.Entry} in natural order on value. 
    * @see Comparable 
    * @since 1.8 
    */ 
    public static <K, V extends Comparable<? super V>> Comparator<Map.Entry<K,V>> comparingByValue() { 
     return (Comparator<Map.Entry<K, V>> & Serializable) 
      (c1, c2) -> c1.getValue().compareTo(c2.getValue()); 
    } 

Von der Methode Erklärung erhalte ich, dass dies eine generische Methode ist das gibt einen Comparator eines Typs zurück, der entweder von den Map-Einträgen abgeleitet oder explizit in der Methode angegeben wurde.

Was mich wirklich abschreckt ist der Rückgabewert. Es scheint, dass der Lambda-Ausdruck

(c1, c2) -> c1.getValue().compareTo(c2.getValue()); 

explizit auf eine Comparator<Map.Entry<K, V>> gegossen wird. Ist das richtig?

Ich bemerkte auch, dass die scheinbare Besetzung enthält & Serializable. Ich habe noch nie eine Schnittstelle in Verbindung mit einer Klasse in einer Besetzung vor, aber es sieht aus wie die folgenden gültigen im Compiler gesehen:

((SubClass & AnInterface) anObject).interfaceMethod();

Obwohl die folgenden nicht funktioniert:

public class Foo { 
    public static void main(String[] args) { 
     Object o = new Foo() { 
      public void bar() { 
       System.out.println("nope"); 
      } 
     }; 
     ((Foo & Bar) o).bar(); 
    } 
} 

interface Bar { 
    public void bar(); 
} 
So

, zwei Fragen:

  1. Wie eine Schnittstelle zu einem Guss wirkt sich das Hinzufügen funktionieren soll? Erzwingt dies nur den Rückgabetyp der Methode einer Schnittstelle?

  2. Können Sie einen Lambda-Ausdruck in eine Comparator umwandeln? Was können sie sonst noch werfen? Oder ist ein Lambda-Ausdruck im Wesentlichen nur ein Comparator? Kann jemand das alles klären?

+2

offensichtlich nicht initialisieren. Ein Lambda kann zu irgendeiner Schnittstelle mit einer einzelnen abstrakten Methode umgewandelt werden, deren Unterschrift dem Körper des Lambda entspricht. –

+0

Aargh, 'Seralizable'! – fge

+2

http://www.java2s.com/Tutorials/Java/Java_Lambda/0080__Java_Intersection_Type.htm –

Antwort

13

Wie eine Schnittstelle zu einem Guss funktionieren sollte nicht hinzufügen?

Dies hat die Syntax einer Umwandlung, definiert jedoch tatsächlich den Typ des Lambda, den Sie über die Typschnittstelle erstellen. I.e. Sie erstellen keine Instanz eines Objekts, das dann in einen anderen Typ umgewandelt wird.

Erzwingt dies nur den Rückgabetyp der Methode einer Schnittstelle?

Dies definiert den Typ, zu dem das Lambda zur Laufzeit erstellt wird. Es gibt eine LambdaMetaFactory, die diesen Typ zur Laufzeit erhält und zusätzlichen Code generiert, wenn der Typ Serializable enthält.

Können Sie einen Lambda-Ausdruck in einen Komparator umwandeln?

Sie können nur einen Verweis auf einen Typ umwandeln, den das Objekt bereits enthält. In diesem Fall definieren Sie, dass das zu erstellende Lambda Comparator lauten muss. Sie können jeden Typ verwenden, der genau eine abstrakte Methode hat.

Oder ist ein Lambda-Ausdruck im Wesentlichen nur ein Komparator?

Derselbe Lambda-Code könnte in verschiedenen Kontexten und unterschiedlichen Schnittstellen ohne Änderung verwendet (kopiert + eingefügt) werden. Es muss kein Comparator sein, wie Sie in vielen anderen Beispielen im JDK sehen werden.

Eine interessant finde ich die count Methode auf einem Stream.

+2

@sotirios danke für die Korrekturen. –

+1

Geringfügige Terminologiekorrektur: Anstatt den Typ des Lambdas zu definieren, nennen wir dies einen _Vorschlag für das Lambda. –

3

Gemäß den Java Language Specification der Umwandlungsoperator (was auch immer in Klammern ist) kann ein Reference durch einen oder mehrere Begriffe AdditionalBound, das heißt, ein oder mehr Schnittstellentypen folgen. Darüber hinaus gibt die Spezifikation auch an, dass Es ist ein Kompilierzeitfehler, wenn der Kompilierzeittyp des Operanden nie in den vom Cast-Operator angegebenen Typ gemäß den Regeln der Castingkonvertierung umgewandelt werden kann.

In Ihrem Fall Foo nicht implementiert Bar, aber diese Tatsache möglicherweise nicht bei der Kompilierung offensichtlich sein, so dass Sie ein ClassCastException bekommen, denn obwohl das Verfahren in der anonymen Klasse definiert hat die gleiche Signatur wie die in Bar definierte ein, das Objekt implementiert nicht explizit Bar. Darüber hinaus werden in anonymen Klassen definierten Methoden versteckt, in der gleichen Anweisung aufgerufen, es sei denn, als wenn sie definiert sind, dh

new MyClass() { 
    void doSomethingAwesome() { /* ... */ } 
}.doSomethingAwesome(); 

funktioniert, aber das bedeutet nicht:

MyClass awesome = new MyClass() { 
    void doSomethingAwesome() { /* ... */ } 
}; 
// undefined method, does not compile! 
awesome.doSomethingAwesome(); 
+0

Es wirft 'ClassCastException', aber es kompiliert in Java 8. Denken Sie daran, Sie könnten eine' Klasse FooBar erweitert Foo implementiert Bar' haben. – Radiodef

+0

Guter Punkt. Dann gibt es keine Überraschung hier. –

13

Obwohl Peter eine gegeben hat, ausgezeichnete Antwort, lassen Sie mich mehr für mehr Klarheit hinzufügen.

Ein Lambda erhält seinen genauen Typ nur während der Initialisierung. Dies basiert auf dem Zieltyp. Zum Beispiel:

Comparator<Integer> comp = (Integer c1, Integer c2) -> c1.compareTo(c2); 
BiFunction<Integer, Integer, Integer> c = (Integer c1, Integer c2) -> c1.compareTo(c2); 
Comparator<Integer> compS = (Comparator<Integer> & Serializable) (Integer c1, Integer c2) -> c1.compareTo(c2); 

über sein gleiches Lambda in allen 3 Fällen, aber es wird seine Art auf dem Grundlage des Referenztyp Sie bereitgestellt haben. Daher können Sie für jeden Fall das gleiche Lambda auf 3 verschiedene Typen einstellen.

Aber Achtung, sobald der Typ eingestellt ist (während der Initialisierung), kann er nicht mehr geändert werden. Es wird auf Bytecode-Ebene aufgenommen. So offensichtlich können Sie c zu einer Methode übergeben, die Comparator erwartet, denn sobald sie dann initialisiert werden, sind sie wie normale Java-Objekte. (Sie können in diesem class schauen Sie sich um und erzeugen lambdas auf dem Sprung zu spielen)


So im Falle von:

(Comparator<Map.Entry<K, V>> & Serializable) 
      (c1, c2) -> c1.getValue().compareTo(c2.getValue()); 

Die Lambda ist mit seinem Zieltyp als Vergleicher initialisiert und Serializable. Beachten Sie, dass der Rückgabetyp der Methode nur Comparator ist, aber da Serializable während der Initialisierung ebenfalls angegeben wird, kann er immer serialisiert werden, obwohl diese Nachricht in der Methodensignatur verloren geht.

Jetzt beachten Sie, Gießen zu einem Lambda ist anders als ((Foo & Bar) o).bar();. Bei Lambda initialisieren Sie das Lambda mit seinem Typ als deklariertem Zieltyp. Aber mit ((Foo & Bar) o).bar(); geben Sie die Variable o als Foo und Bar ein. Im ersten Fall legen Sie den Typ fest. In letzterem Fall hat es bereits einen Typ und Sie versuchen Ihr Glück, es auf etwas anderes zu übertragen.Daher früher, wirft es Classcast weil es o zu Bar

Wie wirkt sich das Hinzufügen einer Schnittstelle zu einem Guss funktionieren soll konvertieren kann nicht?

Für Objekt, wie es normalerweise wäre. Für Lambda, oben erklärt.

Erzwingt dies nur den Rückgabetyp der Methode einer Schnittstelle?

Nein. Java hat Structural Types nicht. Also kein spezieller Typ basierend auf der Methode. Es wird versucht, einfach o sowohl SO1 und Bar zu werfen und es schlägt fehl, weil letztere

Können Sie einen Lambda-Ausdruck in einen Komparator werfen? Was können sie sonst noch werfen? Oder ist ein Lambda-Ausdruck im Wesentlichen nur ein Komparator? Kann jemand das alles klären?

Wie oben erläutert. Ein Lambda kann zu jedem FunctionalInterface initialisiert werden, basierend darauf, welche Schnittstellen für dieses Lambda in Frage kommen. In obigen Beispielen können Sie (c1, c2) -> c1.compareTo(c2) zu einem Predicate