2015-01-19 4 views
104

Ich habe eine Frage bezüglich der Verwendung der Function.identity() Methode.Java 8 lambdas, Funktion.identität() oder t-> t

Stellen Sie sich den folgenden Code ein:

Arrays.asList("a", "b", "c") 
      .stream() 
      .map(Function.identity()) // <- This, 
      .map(str -> str)   // <- is the same as this. 
      .collect(Collectors.toMap(
         Function.identity(), // <-- And this, 
         str -> str));  // <-- is the same as this. 

Gibt es einen Grund, warum Sie Function.identity() statt str->str (oder umgekehrt) verwendet werden soll. Ich denke, dass die zweite Option besser lesbar ist (natürlich eine Frage des Geschmacks). Aber gibt es einen "echten" Grund, warum man bevorzugt werden sollte?

+4

Letztendlich, nein, das wird keinen Unterschied machen. – fge

+21

Entweder ist in Ordnung. Gehen Sie mit dem, was Sie für besser lesbar halten. (Mach dir keine Sorgen, sei glücklich.) –

+2

Ich würde 't -> t 'bevorzugen, einfach weil es prägnanter ist. –

Antwort

48

In Ihrem Beispiel gibt es keinen großen Unterschied zwischen str -> str und Function.identity(), da intern einfach t->t ist.

Aber manchmal können wir Function.identity nicht verwenden. Werfen Sie einen Blick hier

List<Integer> list = new ArrayList<>(); 
list.add(1); 
list.add(2); 

dies kompilieren feine

int[] arrayOK = list.stream().mapToInt(i -> i).toArray(); 

aber wenn Sie versuchen, zu kompilieren

int[] arrayProblem = list.stream().mapToInt(Function.identity()).toArray(); 

Sie Kompilierungsfehler erhalten, da mapToIntToIntFunction erwartet, die nicht verwandt ist zu Function. Auch ToIntFunction hat keine identity() Methode.

+1

Siehe http://StackOverflow.com/q/38034982/14731 für ein anderes Beispiel, bei dem das Ersetzen von 'i -> i' durch' Function.identity() 'zu einem Compilerfehler führt. – Gili

+4

Ich bevorzuge 'mapToInt (Integer :: intValue)'. – shmosel

+1

@smossel ist in Ordnung, aber es ist erwähnenswert, dass beide Lösungen ähnlich funktionieren werden, da mapToInt (i -> i) 'eine Vereinfachung von mapToInt ((Integer i) -> i.intValue()) ist. Verwenden Sie die Version, die Sie für übersichtlicher halten, für mich zeigt 'mapToInt (i -> i)' besser die Absichten dieses Codes. – Pshemo

20

Vom JDK source:

static <T> Function<T, T> identity() { 
    return t -> t; 
} 

Also, nein, solange es syntaktisch korrekt ist.

+3

Ich frage mich, ob dies die obige Antwort in Bezug auf ein Lambda, das ein Objekt erstellt, ungültig macht - oder ob dies eine bestimmte Implementierung ist. – orbfish

+14

@Orfffish: das ist perfekt in der Linie. Jedes Auftreten von "t-> t" im Quellcode kann ein Objekt erzeugen und die Implementierung von 'Function.identity()' ist * ein * Vorkommen. Daher teilen alle Call-Sites, die 'identity()' aufrufen, dieses eine Objekt, während alle Sites, die explizit den Lambda-Ausdruck 't-> t' verwenden, ihr eigenes Objekt erstellen. Die Methode 'Function.identity()' ist in keiner Weise etwas Besonderes. Wenn Sie eine Factory-Methode erstellen, die einen häufig verwendeten Lambda-Ausdruck einkapselt und diese Methode aufruft, anstatt den Lambda-Ausdruck zu wiederholen, können Sie bei der aktuellen Implementierung etwas Speicher sparen *. – Holger

+0

Ich vermute, dass dies daran liegt, dass der Compiler jedes Mal, wenn die Methode aufgerufen wird, die Erzeugung eines neuen "t-> t" -Objekts wegoptimiert und dieselbe wiederverwendet, wenn die Methode aufgerufen wird? –

112

Ab der aktuellen JRE-Implementierung gibt Function.identity() immer dieselbe Instanz zurück, während jedes Vorkommen identifier -> identifier nicht nur eine eigene Instanz erstellt, sondern sogar eine eindeutige Implementierungsklasse hat. Weitere Informationen finden Sie unter here.

Der Grund hierfür ist, dass der Compiler ein Syntheseverfahren erzeugt den trivialen Körper des Lambda-Ausdruck (im Fall von x->x entspricht return identifier;) und geben die Laufzeit eine Implementierung der Funktionsschnittstelle zu schaffen, hält diese Methode aufrufen. Daher sieht die Laufzeitumgebung nur verschiedene Zielmethoden, und die aktuelle Implementierung analysiert die Methoden nicht, um herauszufinden, ob bestimmte Methoden äquivalent sind.

Also mit Function.identity() statt x -> x könnte etwas Speicher sparen, aber das sollte Ihre Entscheidung nicht fahren, wenn Sie wirklich, dass x -> x besser lesbar als Function.identity() sind.

Sie können auch in Betracht ziehen, dass die synthetische Methode beim Kompilieren mit aktivierten Debuginformationen über ein Zeilenfehlerattribut verfügt, das auf die Quelltextzeile (n) verweist, die den Lambda-Ausdruck enthalten, sodass Sie die Quelle von a finden können insbesondere Function Instanz während des Debuggens. Wenn Sie während des Debuggens einer Operation auf die von Function.identity() zurückgegebene Instanz stoßen, wissen Sie dagegen nicht, wer diese Methode aufgerufen und die Instanz an die Operation übergeben hat.

+1

Schöne Antwort. Ich habe einige Zweifel am Debuggen. Wie kann es nützlich sein? Es ist sehr unwahrscheinlich, dass die Exception-Stack-Trace mit 'x -> x' frame kommt. Schlägst du vor, den Breakpoint auf dieses Lambda zu setzen? Normalerweise ist es nicht so einfach, den Breakpoint in das Single-Expression-Lambda zu setzen (zumindest in Eclipse) ... –

+7

@Tagir Valeev: Sie können Code debuggen, der eine beliebige Funktion empfängt und in die Apply-Methode dieser Funktion wechselt. Dann können Sie am Quellcode eines Lambda-Ausdrucks enden. Im Falle eines expliziten Lambda-Ausdrucks wissen Sie, woher die Funktion kommt und haben eine Chance zu erkennen, an welcher Stelle die Entscheidung getroffen wurde, eine Identitätsfunktion zu übergeben. Bei Verwendung von 'Function.identity()' ist diese Information verloren. Dann kann die Anrufkette in einfachen Fällen helfen, aber denken Sie z.B. Multithread-Auswertung, bei der der ursprüngliche Initiator nicht im Stack-Trace ist ... – Holger

+0

Interessant in diesem Zusammenhang: http://blog.codefx.org/java/instances-non-capturing-lambdas/ –