Java 8 lambdas, Function.identity () o t-> t


240

Ho una domanda sull'uso del Function.identity()metodo.

Immagina il seguente codice:

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.

C'è qualche motivo per cui dovresti usare Function.identity()invece di str->str(o viceversa). Penso che la seconda opzione sia più leggibile (una questione di gusti ovviamente). Ma c'è qualche motivo "reale" per cui uno dovrebbe essere preferito?


6
Alla fine, no, questo non farà differenza.
fge

50
O va bene. Vai con quello che pensi sia più leggibile. (Non preoccuparti, sii felice.)
Brian Goetz,

3
Preferirei t -> tsemplicemente perché è più conciso.
David Conrad,

3
Domanda leggermente non correlata, ma qualcuno sa perché i progettisti del linguaggio fanno in modo che identity () restituisca un'istanza di Function invece di avere un parametro di tipo T e restituirlo in modo che il metodo possa essere utilizzato con i riferimenti al metodo?
Kirill Rakhman,

Direi che è utile essere abili con la parola "identità", poiché ha un significato importante in altre aree della programmazione funzionale.
orbfish

Risposte:


312

A partire dall'attuale implementazione JRE, Function.identity()restituirà sempre la stessa istanza mentre ogni occorrenza di identifier -> identifiernon solo creerà la propria istanza ma avrà anche una classe di implementazione distinta. Per maggiori dettagli, vedi qui .

Il motivo è che il compilatore genera un metodo sintetico che contiene il corpo banale di quell'espressione lambda (nel caso di x->x, equivalente a return identifier;) e dice al runtime di creare un'implementazione dell'interfaccia funzionale che chiama questo metodo. Quindi il runtime vede solo diversi metodi target e l'implementazione corrente non analizza i metodi per scoprire se determinati metodi sono equivalenti.

Quindi usare al Function.identity()posto di x -> xpotrebbe risparmiare un po 'di memoria, ma ciò non dovrebbe guidare la tua decisione se pensi davvero che x -> xsia più leggibile di Function.identity().

Puoi anche considerare che durante la compilazione con le informazioni di debug abilitate, il metodo sintetico avrà un attributo di debug di linea che punta alle righe del codice sorgente che contengono l'espressione lambda, quindi hai la possibilità di trovare l'origine di una particolare Functionistanza durante il debug . Al contrario, quando si incontra l'istanza restituita Function.identity()durante il debug di un'operazione, non si saprà chi ha chiamato quel metodo e ha passato l'istanza all'operazione.


5
Bella risposta. Ho dei dubbi sul debug. Come può essere utile? È molto improbabile ottenere la traccia dello stack delle eccezioni che coinvolge x -> xframe. Suggerisci di impostare il breakpoint su questo lambda? Di solito non è così facile inserire il breakpoint nella lambda a espressione singola (almeno in Eclipse) ...
Tagir Valeev

14
@Tagir Valeev: è possibile eseguire il debug del codice che riceve una funzione arbitraria e passare al metodo apply di quella funzione. Quindi potresti finire con il codice sorgente di un'espressione lambda. Nel caso di un'espressione lambda esplicita saprai da dove proviene la funzione e avrai la possibilità di riconoscere in quale luogo è stata presa la decisione di passare attraverso una funzione di identità. Quando si utilizzano Function.identity()tali informazioni vengono perse. Quindi, la catena di chiamate può aiutare in casi semplici, ma pensa ad esempio a una valutazione multi-thread in cui l'iniziatore originale non si trova nella traccia dello stack ...
Holger


13
@Wim Deblauwe: Interessante, ma lo vedrei sempre viceversa: se un metodo factory non afferma esplicitamente nella sua documentazione che restituirà una nuova istanza ad ogni invocazione, non puoi supporre che lo farà. Quindi non dovrebbe essere sorprendente se non lo è. Dopotutto, questo è un grande motivo per usare metodi di fabbrica invece di new. new Foo(…)garantisce di creare una nuova istanza del tipo esatto Foo, mentre Foo.getInstance(…)potrebbe restituire un'istanza esistente di (un sottotipo di) Foo...
Holger,

93

Nel tuo esempio non c'è grande differenza tra str -> stre Function.identity()poiché internamente lo è semplicemente t->t.

Ma a volte non possiamo usare Function.identityperché non possiamo usare a Function. Dai un'occhiata qui:

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

questo compilerà bene

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

ma se provi a compilare

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

si otterrà errore di compilazione in quanto mapToIntsi aspetta ToIntFunction, che non è legato al Function. Inoltre ToIntFunctionnon ha identity()metodo.


3
Vedere stackoverflow.com/q/38034982/14731 per un altro esempio in cui la sostituzione i -> icon Function.identity()comporterà un errore del compilatore.
Gili,

19
Io preferisco mapToInt(Integer::intValue).
shmosel,

4
@shmosel va bene, ma vale la pena ricordare che entrambe le soluzioni funzioneranno in modo simile poiché la mapToInt(i -> i)semplificazione di mapToInt( (Integer i) -> i.intValue()). Usa la versione che ritieni più chiara, per me mapToInt(i -> i)mostra meglio le intenzioni di questo codice.
Pshemo,

1
Penso che possano esserci vantaggi in termini di prestazioni nell'uso dei riferimenti ai metodi, ma è principalmente solo una preferenza personale. Lo trovo più descrittivo, perché i -> isembra una funzione di identità, che non è in questo caso.
shmosel,

@shmosel Non posso dire molto sulla differenza di prestazioni, quindi potresti avere ragione. Ma se le prestazioni non sono un problema, rimarrò i -> idal momento che il mio obiettivo è mappare Integer a int (che mapToIntsuggerisce abbastanza bene) per non chiamare esplicitamente il intValue()metodo. Come sarà realizzato questo mapping non è poi così importante. Quindi accettiamo solo di non essere d'accordo, ma grazie per aver sottolineato la possibile differenza di prestazioni, dovrò dare un'occhiata più da vicino a quel giorno.
Pshemo,

44

Dalla fonte JDK :

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

Quindi no, purché sia ​​sintatticamente corretto.


8
Mi chiedo se questo invalida la risposta sopra relativa a un lambda che crea un oggetto - o se questa è un'implementazione particolare.
orbfish

28
@orbfish: è perfettamente in linea. Ogni occorrenza di t->tnel codice sorgente può creare un oggetto e l'implementazione di Function.identity()è una ricorrenza. Quindi tutti i siti di chiamata che invocano identity()condivideranno quell'oggetto mentre tutti i siti che usano esplicitamente l'espressione lambda t->tcreeranno il proprio oggetto. Il metodo Function.identity()non è speciale in alcun modo, ogni volta che si crea un metodo factory che incapsula un'espressione lambda comunemente usata e si chiama quel metodo invece di ripetere l'espressione lambda, è possibile risparmiare un po 'di memoria, data l'implementazione corrente .
Holger,

Immagino che ciò sia dovuto al fatto che il compilatore ottimizza la creazione di un nuovo t->toggetto ogni volta che viene chiamato il metodo e lo ricicla ogni volta che viene chiamato il metodo?
Daniel Gray,

1
@DanielGray la decisione viene presa in fase di esecuzione. Il compilatore inserisce invokedynamicun'istruzione che viene collegata alla sua prima esecuzione eseguendo un cosiddetto metodo bootstrap, che nel caso delle espressioni lambda si trova nel file LambdaMetafactory. Questa implementazione decide di restituire un handle a un costruttore, un metodo factory o codice che restituisce sempre lo stesso oggetto. Potrebbe anche decidere di restituire un collegamento a un handle già esistente (che attualmente non accade).
Holger,

@Holger Sei sicuro che questa chiamata all'identità non sia incorporata, quindi potenzialmente monomorfizzata (e nuovamente incorporata)?
JasonN,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.