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


240

Function.identity()Yöntemin kullanımı ile ilgili bir sorum var .

Aşağıdaki kodu düşünün:

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.

Function.identity()Bunun yerine kullanmanız için herhangi bir neden var mı str->str(ya da tam tersi). İkinci seçeneğin daha okunabilir olduğunu düşünüyorum (elbette bir tat meselesi). Ancak, birinin tercih edilmesi için herhangi bir "gerçek" sebep var mı?


6
Sonuçta, hayır, bu bir fark yaratmaz.
fge

50
Her ikisi de iyi. Daha okunabilir olduğunu düşündüğünüzle birlikte gidin. (Endişelenme, mutlu ol.)
Brian Goetz

3
Sadece t -> tdaha özlü olduğu için tercih ederim .
David Conrad

3
Biraz ilgisiz bir soru, ancak herkes dil tasarımcılarının kimlik () neden T türünde bir parametreye sahip olmak ve yöntemi döndürmek yerine bir İşlev örneği döndürdüğünü biliyor mu?
Kirill Rakhman

İşlevsel programlamanın diğer alanlarında önemli bir anlamı olduğu için "kimlik" kelimesi ile konuşmanın bir yararı olduğunu iddia ediyorum.
orbfish

Yanıtlar:


312

Mevcut JRE uygulamasından itibaren, Function.identity()her seferinde aynı örneği döndürürken, her oluşumu identifier -> identifieryalnızca kendi örneğini oluşturmakla kalmayacak , aynı zamanda ayrı bir uygulama sınıfına sahip olacaktır. Daha fazla ayrıntı için buraya bakın .

Bunun nedeni, derleyicinin bu lambda ifadesinin önemsiz gövdesini tutan sentetik bir yöntem üretmesi (durumunda x->x, eşdeğerde return identifier;) ve çalışma zamanına, bu yöntemi çağıran işlevsel arabirimin bir uygulamasını oluşturmasını söylemesidir. Bu nedenle çalışma zamanı yalnızca farklı hedef yöntemler görür ve geçerli uygulama, belirli yöntemlerin eşdeğer olup olmadığını bulmak için yöntemleri analiz etmez.

Bu yüzden Function.identity()yerine kullanmak x -> xbiraz bellek tasarrufu sağlayabilir, ancak gerçekten x -> xdaha okunabilir olduğunu düşünüyorsanız, kararınızı yönlendirmemelidir Function.identity().

Hata ayıklama bilgileri etkinken derlenirken, sentetik yöntemin lambda ifadesini tutan kaynak kod satırlarına işaret eden bir satır hata ayıklama özelliğine sahip olacağını, bu nedenle Functionhata ayıklama sırasında belirli bir örneğin kaynağını bulma şansınız olduğunu düşünebilirsiniz. . Buna karşılık, Function.identity()bir işlemde hata ayıklama sırasında döndürülen örnekle karşılaştığınızda, bu yöntemi kimin çağırdığını ve örneği işleme geçirdiğini bilemezsiniz.


5
Güzel cevap. Hata ayıklama konusunda bazı şüphelerim var. Nasıl faydalı olabilir? x -> xÇerçeve içeren özel durum yığını izini almak pek olası değildir . Bu lambda için kesme noktasını ayarlamanızı önerir misiniz? Genellikle kesme noktasını tek ifadeli
lambda'ya

14
@Tagir Valeev: rasgele bir işlev alan kodda hata ayıklayabilir ve bu işlevin uygulama yöntemine geçebilirsiniz. Sonra lambda ifadesinin kaynak koduna gidebilirsiniz. Açık bir lambda ifadesi durumunda, işlevin nereden geldiğini bilirsiniz ve bir kimlik işlevi yapılmasına rağmen kararın hangi yerden geçeceğine karar verme şansınız olur. Kullanırken Function.identity()bu bilgiler kaybolur. Daha sonra, çağrı zinciri basit durumlarda yardımcı olabilir ancak düşünün, örneğin orijinal başlatıcıyı yığın izinde olmayan çok iş parçacıklı değerlendirme ...
Holger


13
@Wim Deblauwe: İlginç, ama ben her zaman başka bir şekilde görürdüm: bir fabrika yöntemi belgelerinde her çağrımda yeni bir örnek döndüreceğini açıkça belirtmezse, bunu kabul edemezsiniz. Bu yüzden eğer değilse şaşırtıcı olmamalı. Sonuçta, bunun yerine fabrika yöntemlerini kullanmak için büyük bir neden new. new Foo(…)kesin bir türün yeni bir örneğini oluşturma garantileri Foo, oysa Foo.getInstance(…)mevcut bir örneğini (bir alt türü) döndürebilir Foo
Holger

93

Örneğinizde, dahili olarak basit olduğu için str -> strve arasında büyük bir fark yoktur .Function.identity()t->t

Ama bazen kullanamayız Function.identityçünkü a kullanamayız Function. Buraya bir göz atın:

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

bu iyi derlenecek

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

ama derlemeye çalışırsanız

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

ile ilişkili olmayan mapToIntbeklenti beri derleme hatası alırsınız . Ayrıca yoktur yöntemi.ToIntFunctionFunctionToIntFunctionidentity()


3
Bkz stackoverflow.com/q/38034982/14731 değiştirilmesi başka örnek i -> iile Function.identity()bir derleyici hata ile sonuçlanır.
Gili

19
Ben tercih ederim mapToInt(Integer::intValue).
shmosel

4
@shmosel Tamam ama hem çözümler beri aynı işlevi görecektir o bahsetmemiz olduğunu mapToInt(i -> i)basitleştirilmesi mapToInt( (Integer i) -> i.intValue()). Daha net olduğunu düşündüğünüz sürümü kullanın, benim için mapToInt(i -> i)bu kodun niyetlerini daha iyi gösterir.
Pshemo

1
Yöntem referanslarını kullanmanın performans avantajları olabileceğini düşünüyorum, ancak çoğunlukla sadece kişisel bir tercih. Daha açıklayıcı buluyorum, çünkü i -> ibu durumda olmayan bir kimlik fonksiyonuna benziyor.
shmosel

@shmosel Performans farkı hakkında fazla bir şey söyleyemem bu yüzden haklı olabilirsiniz. Ancak performans bir sorun değilse, i -> iamacım Integer'i mapToIntaçıkça intValue()yöntem çağırmamak için int ile (ki bu oldukça güzel bir şekilde önerir) eşlemektir . Bu haritalamanın nasıl sağlanacağı gerçekten önemli değil. Bu yüzden hemfikir olmayı kabul edelim, ancak olası performans farkına işaret ettiğiniz için teşekkürler, bir gün buna daha yakından bakmam gerekecek.
Pshemo

44

Gönderen JDK kaynağı :

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

Yani, sözdizimsel olarak doğru olduğu sürece hayır.


8
Bunun bir nesne yaratan bir lambda ile ilgili yukarıdaki cevabı geçersiz kılıp geçersiz kılmadığını veya bunun belirli bir uygulama olup olmadığını merak ediyorum.
15'te orbfish

28
@orbfish: bu mükemmel bir çizgide. Her olay t->tkaynak kodunda bir nesne ve uygulanmasını yaratabilir Function.identity()olan bir olay. Böylece, çağrı yapan tüm çağrı siteleri identity()bu nesneyi paylaşırken, lambda ifadesini açıkça kullanan tüm siteler t->tkendi nesnelerini oluşturur. Yöntem Function.identity()herhangi bir şekilde özel değildir, yaygın olarak kullanılan bir lambda ifadesini kapsayan bir fabrika yöntemi oluşturduğunuzda ve lambda ifadesini tekrarlamak yerine bu yöntemi çağırdığınızda , geçerli uygulama göz önüne alındığında biraz bellek tasarrufu yapabilirsiniz .
Holger

Ben derleyici t->työntem çağrıldığında her zaman yeni bir nesne oluşturma optimize ve yöntem çağrıldığında aynı bir geri dönüştürür çünkü tahmin ediyorum ?
Daniel Gray

1
@DanielGray karar zamanında yapılır. Derleyici invokedynamic, lambda ifadeleri durumunda bulunan bir bootstrap yöntemi uygulayarak ilk yürütme ile bağlantılı olan bir talimat ekler LambdaMetafactory. Bu uygulama, her zaman aynı nesneyi döndüren bir kurucuya, fabrika yöntemine veya koda bir tanıtıcı döndürmeye karar verir. Ayrıca, mevcut bir tanıtıcıya (şu anda gerçekleşmeyen) bir bağlantı döndürmeye de karar verebilir.
Holger

@Holger Bu kimlik çağrısının satır içine alınmayacağından ve sonra potansiyel olarak monomorfize edileceğinden (ve yine satır içinde) emin misiniz?
JasonN
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.