Lambda ifadelerinin kod satırlarını kaydetmekten başka bir kullanımı var mı?


120

Lambda ifadelerinin kod satırlarını kaydetmekten başka bir kullanımı var mı?

Lambdas tarafından sağlanan, çözülmesi kolay olmayan sorunları çözen özel özellikler var mı? Gördüğüm tipik kullanım, bunu yazmak yerine şudur:

Comparator<Developer> byName = new Comparator<Developer>() {
  @Override
  public int compare(Developer o1, Developer o2) {
    return o1.getName().compareTo(o2.getName());
  }
};

Kodu kısaltmak için bir lambda ifadesi kullanabiliriz:

Comparator<Developer> byName =
(Developer o1, Developer o2) -> o1.getName().compareTo(o2.getName());

27
Daha da kısa olabileceğini unutmayın: (o1, o2) -> o1.getName().compareTo(o2.getName())
Thorbjørn Ravn Andersen

88
veya daha kısa:Comparator.comparing(Developer::getName)
Thomas Kläger

31
Bu faydayı küçümsemem. İşler bu kadar kolay olduğunda, işleri "doğru" şekilde yapma olasılığınız artar. Uzun vadede daha sürdürülebilir olan ve kısa vadede not almak daha kolay olan kod yazmak arasında genellikle bir miktar gerilim vardır. Lambdalar bu gerilimi azaltır ve böylece daha temiz kod yazmanıza yardımcı olur.
yshavit

5
Lambdalar, yer tasarrufunu daha da artıran kapanışlardır, çünkü artık değiştirme sınıfınıza parametreleştirilmiş bir kurucu, alanlar, atama kodu vb. Eklemeniz gerekmez.
CodesInChaos

Yanıtlar:


116

Lambda ifadeleri genel olarak Java ile çözebileceğiniz problemler kümesini değiştirmez, ancak belirli problemleri çözmeyi kesinlikle kolaylaştırır, sadece aynı nedenle artık assembly dilinde programlama yapmıyoruz. Gereksiz görevleri programcının çalışmasından kaldırmak hayatı kolaylaştırır ve başka türlü dokunmayacağınız şeyleri, yalnızca (manuel olarak) üretmeniz gereken kod miktarı için yapmanızı sağlar.

Ancak lambda ifadeleri yalnızca kod satırlarını kaydetmez. Lambda ifadeleri , daha önce geçici çözüm olarak anonim iç sınıfları kullanabileceğiniz işlevleri tanımlamanıza izin verir , bu nedenle bu durumlarda anonim iç sınıfları değiştirebilirsiniz, ancak genel olarak değil.

En önemlisi, lambda ifadeleri, dönüştürülecekleri işlevsel arabirimden bağımsız olarak tanımlanır, bu nedenle erişebilecekleri miras alınmış üyeler yoktur, ayrıca işlevsel arabirimi uygulayan türün örneğine erişemezler. Bir lambda ifadesi içinde thisve superçevresindeki bağlamında aynı anlama sahip, ayrıca bkz bu cevabı . Ayrıca, çevreleyen bağlamın yerel değişkenlerini gölgeleyen yeni yerel değişkenler oluşturamazsınız. Amaçlanan bir işlev tanımlama görevi için bu, birçok hata kaynağını ortadan kaldırır, ancak aynı zamanda diğer kullanım durumları için, işlevsel bir arabirim uygulansa bile bir lambda ifadesine dönüştürülemeyen anonim iç sınıflar olabileceği anlamına gelir.

Dahası, yapı new Type() { … }yeni bir farklı örnek oluşturmayı garanti eder ( newher zaman olduğu gibi). Anonim iç sınıf örnekleri, staticbağlam dışı olarak oluşturulmuşsa, her zaman dış örneklerine bir başvuru saklar . Buna karşılık, lambda ifadeleri, yalnızca bir başvuru yakalamak thiseriştikleri takdirde, ie gerektiğinde thisveya olmayan staticüyesi. Ayrıca, uygulamanın mevcut örnekleri yeniden kullanıp kullanmayacağına çalışma zamanında karar vermesine olanak tanıyan kasıtlı olarak belirtilmemiş bir kimliğin örneklerini üretirler (ayrıca bkz. " Bir lambda ifadesi, her yürütüldüğünde yığın üzerinde bir nesne oluşturur mu? ").

Bu farklılıklar örneğiniz için geçerlidir. Anonim iç sınıf yapınız her zaman yeni bir örnek üretir, ayrıca dış örneğe bir başvuru yakalayabilir, oysa sizin (Developer o1, Developer o2) -> o1.getName().compareTo(o2.getName())tipik uygulamalarda bir tek ton olarak değerlendirilecek yakalamayan bir lambda ifadesidir. Ayrıca, .classsabit sürücünüzde bir dosya oluşturmaz .

Hem anlamsal hem de performansla ilgili farklılıklar göz önüne alındığında, lambda ifadeleri, elbette, yeni dil özelliklerini kullanan işlevsel programlama fikirlerini benimseyen yeni API'ler nedeniyle programcıların gelecekte belirli sorunları çözme şeklini değiştirebilir. Ayrıca bkz. Java 8 lambda ifadesi ve birinci sınıf değerler .


1
Esasen, lambda ifadeleri bir tür işlevsel programlamadır. Fonksiyonel programlama veya zorunlu programlama ile herhangi bir görevin yapılabileceği düşünüldüğünde , okunabilirlik ve sürdürülebilirlik dışında hiçbir avantajı yoktur , bu da diğer endişelerden daha ağır basabilir.
Draco18'ler artık

1
@Trilarion: Kitapların tamamını işlev tanımıyla doldurabilirsiniz . Ayrıca, hedefe mi yoksa Java'nın lambda ifadeleri tarafından desteklenen yönlere mi odaklanacağınıza karar vermeniz gerekir . Cevabımın son halkası ( bu ) Java bağlamında bu yönlerden bazılarını zaten tartışıyor. Ancak cevabımı anlamak için, fonksiyonların sınıflardan farklı bir kavram olduğunu anlamak zaten yeterli. Daha fazla ayrıntı için, zaten mevcut kaynaklar ve Sorular ve Cevaplar var…
Holger

1
@Trilarion: Kod boyutu / karmaşıklığı miktarını veya diğer çözümlerin ihtiyaç duyduğu geçici çözümleri kabul edilemez bulmadığınız sürece (ilk paragrafta söylendiği gibi) ve işlevleri tanımlamanın bir yolu olmadığı sürece, her iki işlev de daha fazla sorunu çözmeye izin vermez , söylendiği gibi, iç sınıflar, işlevleri tanımlamanın daha iyi bir yolunun yokluğu için geçici çözüm olarak kullanılabilir (ancak iç sınıflar, çok daha fazla amaca hizmet edebildikleri için işlev değildir). OOP ile aynıdır - montajda yapamadığınız hiçbir şeyi yapmanıza izin vermez, ancak yine de, derlemede yazılmış Eclipse gibi bir uygulamayı görüntüleyebilir misiniz?
Holger

3
@EricDuminil: Bana göre Turing Tamlığı çok teorik. Örneğin, Java Turing tamamlanmış olsun ya da olmasın, Java'da bir Windows aygıt sürücüsü yazamazsınız. (Veya aynı zamanda Turing tamamlanmış olan PostScript'te) Java'ya bunu yapmaya izin veren özellikler eklemek, onunla çözebileceğiniz problemler kümesini değiştirir, ancak bu olmaz. Bu yüzden Montaj noktasını tercih ediyorum; Yapabileceğiniz her şey çalıştırılabilir CPU komutlarında uygulandığından, her CPU talimatını destekleyen Assembly'de yapamayacağınız hiçbir şey yoktur (ayrıca Turing makinesi biçimciliğinden daha kolay anlaşılır).
Holger

2
@abelito, tüm üst düzey programlama yapılarında yaygındır , "gerçekte neler olup bittiğine dair daha az görünürlük" sağlamaktır, çünkü her şey bununla ilgilidir, daha az ayrıntı, gerçek programlama problemine daha fazla odaklanma. Kodu daha iyi veya daha kötü hale getirmek için kullanabilirsiniz; bu sadece bir araçtır. Oylama özelliği gibi; bir gönderinin gerçek kalitesi hakkında temel bir yargı sağlamak için amaçlanan şekilde kullanabileceğiniz bir araçtır. Ya da sırf lambdalardan nefret ettiğiniz için ayrıntılı, makul bir cevaba olumsuz oy vermek için kullanırsınız.
Holger

52

Programlama dilleri, makinelerin çalıştırması için değildir.

Programcıların düşünmesi içindir .

Diller, düşüncelerimizi bir makinenin yürütebileceği bir şeye dönüştürmek için bir derleyici ile yapılan görüşmedir. Diğer dillerden kendisine gelen (ya da insanlardan Java hakkında baş şikayetlerden biri terk onu için bu eskiden diğer diller) zorlar programcı belli bir zihinsel modeli (yani her şeyin bir sınıftır).

Bunun iyi ya da kötü olup olmadığına ağırlık vermeyeceğim: her şey değiş tokuş. Ancak Java 8 lambdas, programcıların daha önce Java'da yapamadığınız bir şey olan işlevler açısından düşünmesine izin verir .

Bu , Java'ya geldiklerinde sınıflar açısından düşünmeyi öğrenen bir yordamsal programcı ile aynı şey : onların yüceltilmiş yapılar olan sınıflardan yavaş yavaş hareket ettiğini ve bir grup statik yöntemle 'yardımcı' sınıflara sahip olduğunu görürsünüz ve rasyonel bir OO tasarımına (mea culpa) daha yakından benzer.

Bunları anonim iç sınıfları ifade etmenin daha kısa bir yolu olarak düşünürseniz, muhtemelen yukarıdaki prosedür programcısının muhtemelen sınıfların büyük bir gelişme olduğunu düşünmediği gibi onları çok etkileyici bulmayacaksınız.


5
Yine de dikkatli olun, eskiden OOP'nin her şey olduğunu düşünürdüm ve sonra varlık-bileşen-sistem mimarileriyle korkunç bir şekilde kafam karıştı (ne demek oluyor ki her tür oyun nesnesi için bir sınıfınız yok mu?! Ve her oyun nesnesi bir OOP nesnesi değil mi ?!)
user253751

3
Bir başka deyişle, düşünme içinde yerine sadece düşünce yerine, OOP * O f * Başka araç olarak sınıflar, y kötü bir şekilde benim düşünme kısıtlı.
user253751

2
@immibis: "OOP'de düşünmenin" birçok farklı yolu vardır, bu nedenle "OOP'de düşünmeyi" "sınıflarda düşünme" ile karıştırmanın yaygın hatasının yanı sıra, karşı üretken bir şekilde düşünmenin birçok yolu vardır. Ne yazık ki, kitaplar ve öğreticiler bile aşağı olanları öğrettiği, örneklerde anti kalıplar gösterdiği ve bu düşünceyi yaydığı için, bu daha baştan çok sık oluyor. Her ne türden olursa olsun, "her tür için bir sınıfa sahip olma" ihtiyacı, soyutlama kavramıyla çelişen sadece bir örnektir, aynı şekilde, "OOP, mümkün olduğunca çok şablon yazmak anlamına gelir" miti vb.
Holger

@immibis, bu durumda size yardımcı olmasa da, bu anekdot aslında konuyu destekliyor: dil ve paradigma düşüncelerinizi yapılandırmak ve onları bir tasarıma dönüştürmek için bir çerçeve sağlar. Sizin durumunuzda, çerçevenin kenarlarına doğru koştunuz, ancak başka bir bağlamda bunu yapmak, büyük bir çamur topuna sapıp çirkin bir tasarım üretmenize yardımcı olabilirdi. Bu nedenle, "her şeyin değiş tokuş olduğunu" varsayıyorum. Bir dilin ne kadar "büyük" olacağına karar verirken, önceki sorundan kaçınırken ikinci faydayı korumak kilit bir sorudur.
Leushenko

@immibis bu yüzden bir grup paradigmayı iyi öğrenmek önemlidir : her biri size diğerlerinin yetersizlikleri hakkında ulaşır.
Jared Smith

36

Kod satırlarının kaydedilmesi, daha kısa ve daha net bir şekilde önemli miktarda mantık yazmanıza olanak sağlıyorsa, bu da başkalarının okuyup anlaması için daha az zaman alırsa, yeni bir özellik olarak görülebilir.

Lambda ifadeleri (ve / veya yöntem referansları) Streamolmasaydı ardışık düzenler çok daha az okunabilir olurdu.

Örneğin, Streamher lambda ifadesini anonim bir sınıf örneğiyle değiştirirseniz aşağıdaki ardışık düzeneğin nasıl görüneceğini düşünün .

List<String> names =
    people.stream()
          .filter(p -> p.getAge() > 21)
          .map(p -> p.getName())
          .sorted((n1,n2) -> n1.compareToIgnoreCase(n2))
          .collect(Collectors.toList());

Olurdu:

List<String> names =
    people.stream()
          .filter(new Predicate<Person>() {
              @Override
              public boolean test(Person p) {
                  return p.getAge() > 21;
              }
          })
          .map(new Function<Person,String>() {
              @Override
              public String apply(Person p) {
                  return p.getName();
              }
          })
          .sorted(new Comparator<String>() {
              @Override
              public int compare(String n1, String n2) {
                  return n1.compareToIgnoreCase(n2);
              }
          })
          .collect(Collectors.toList());

Bu, lambda ifadeleri içeren sürümden çok daha zordur ve hataya çok daha açıktır. Anlaması da daha zor.

Ve bu nispeten kısa bir boru hattı.

Bunu lambda ifadeleri ve yöntem referansları olmadan okunabilir hale getirmek için, burada kullanılan çeşitli işlevsel arabirim örneklerini tutan değişkenler tanımlamanız gerekirdi; bu, işlem hattının mantığını bölerek anlaşılmasını zorlaştırırdı.


17
Bunun önemli bir fikir olduğunu düşünüyorum. Sözdizimsel ve bilişsel ek yük yararlı olamayacak kadar büyükse, bir programlama dilinde bir şey yapmanın pratik olarak imkansız olduğu ve diğer yaklaşımların daha üstün olacağı iddia edilebilir. Bu nedenle, sözdizimsel ve bilişsel ek yükü azaltarak (lambdaların anonim sınıflara kıyasla yaptığı gibi), yukarıdakiler gibi ardışık düzenler mümkün hale gelir. Daha fazla yanak, eğer bir kod parçası yazmak işinizden kovulmanıza neden oluyorsa, bunun mümkün olmadığı söylenebilir.
Sebastian Redl

Nitpick: Zaten doğal düzende karşılaştırıldığı Comparatoriçin for'a ihtiyacınız yok sorted. Belki örneği daha da iyi hale getirmek için dizelerin uzunluğunu karşılaştırmaya veya benzerine değiştirebilir misiniz?
tobias_k

@tobias_k sorun değil. Bundan compareToIgnoreCasefarklı davranması için değiştirdim sorted().
Eran

1
compareToIgnoreCaseHala mevcut String.CASE_INSENSITIVE_ORDERkarşılaştırıcıyı kullanabileceğiniz için kötü bir örnek . @ tobias_k'ın uzunluğa göre karşılaştırma önerisi (veya yerleşik bir karşılaştırıcıya sahip olmayan başka bir özellik) daha iyi uyuyor. SO Soru-Cevap kodu örneklerine bakılırsa, lambdalar birçok gereksiz karşılaştırma uygulamasına neden oldu…
Holger

İkinci örneğin anlaşılmasının daha kolay olduğunu düşünen tek kişi ben miyim?
Clark Savaşı

8

Dahili yineleme

Java Koleksiyonlar yineleme olduğunda, çoğu geliştirici eğilimi olsun bir element ve sonra işlemek bunu. Bu, o öğeyi çıkarın ve sonra kullanın veya yeniden yerleştirin, vb. Java'nın 8'den önceki sürümleriyle, bir iç sınıfı uygulayabilir ve aşağıdaki gibi bir şey yapabilirsiniz:

numbers.forEach(new Consumer<Integer>() {
    public void accept(Integer value) {
        System.out.println(value);
    }
});

Şimdi Java 8 ile aşağıdakilerle daha iyi ve daha az ayrıntılı yapabilirsiniz:

numbers.forEach((Integer value) -> System.out.println(value));

ya da daha iyisi

numbers.forEach(System.out::println);

Argüman olarak davranışlar

Aşağıdaki durumu tahmin edin:

public int sumAllEven(List<Integer> numbers) {
    int total = 0;

    for (int number : numbers) {
        if (number % 2 == 0) {
            total += number;
        }
    } 
    return total;
}

Java 8 Predicate arayüzü ile şu şekilde daha iyisini yapabilirsiniz:

public int sumAll(List<Integer> numbers, Predicate<Integer> p) {
    int total = 0;

    for (int number : numbers) {
        if (p.test(number)) {
            total += number;
        }
    }
    return total;
}

Şöyle diyor:

sumAll(numbers, n -> n % 2 == 0);

Kaynak: DZone - Java'da Lambda İfadelerine Neden İhtiyacımız Var?


Muhtemelen ilk durum, bazı kod satırlarını kaydetmenin bir yoludur, ancak kodun okunmasını kolaylaştırır
alseether

4
Dahili yineleme nasıl bir lambda özelliği olur? Aslında basit olan şu ki, teknik olarak lambdalar java-8'den önce yapamadığınız hiçbir şeyi yapmanıza izin vermez. Bu, kodun etrafından dolaşmanın daha kısa bir yolu.
Ousmane D.

Aominè @ Bu durumda numbers.forEach((Integer value) -> System.out.println(value));lambda ifadesi iki bölümden üzerinde bir yapılır sol (->) onun listeleme ok sembolünün parametrelerini ve bir tane sağ onun içeren vücudu . Derleyici, lambda ifadesinin, Tüketici arabiriminin uygulanmamış tek yöntemiyle aynı imzaya sahip olduğunu otomatik olarak anlar.
alseether

5
Lambda ifadesinin ne olduğunu sormadım, sadece iç yinelemenin lambda ifadeleriyle ilgisi olmadığını söylüyorum.
Ousmane D.

1
@ Aominè Demek istediğini anlıyorum, kendi başına lambdalarla hiçbir şeye sahip değil , ama senin de işaret ettiğin gibi, daha temiz bir yazma şekli gibi. Verimlilik açısından, 8 öncesi sürümler kadar iyi olduğunu düşünüyorum
alseether

4

Aşağıdaki gibi iç sınıf yerine lambda kullanmanın birçok faydası vardır:

  • Daha fazla dil sözdizimi semantiği eklemeden kodu daha kompakt ve anlamlı hale getirin. zaten sorunuzda bir örnek verdiniz.

  • Lambdaları kullanarak, koleksiyonlardaki harita azaltma dönüşümleri gibi öğe akışları üzerinde işlevsel tarzda işlemlerle programlama yapmaktan mutlu olursunuz. bkz java.util.function & java.util.stream paketleri belgelerine.

  • Derleyici tarafından lambdalar için oluşturulmuş fiziksel sınıflar dosyası yoktur. Böylece teslim edilen uygulamalarınızı küçültür. Bellek lambda'ya nasıl atanır?

  • Lambda, kapsamı dışındaki değişkenlere erişmezse, bu, lambda örneğinin JVM tarafından yalnızca bir kez oluşturulduğu anlamına gelir. Daha fazla ayrıntı için @ Holger'ın soruya verdiği yanıtı görebilirsiniz. Yöntem referansını önbelleğe almak Java 8'de iyi bir fikir mi? .

  • Lambdas, işlevsel arabirimin yanı sıra çoklu işaretleyici arabirimleri de uygulayabilir, ancak anonim iç sınıflar daha fazla arabirim uygulayamaz, örneğin:

    //                 v--- create the lambda locally.
    Consumer<Integer> action = (Consumer<Integer> & Serializable) it -> {/*TODO*/};

2

Lambdalar, anonim sınıflar için sadece sözdizimsel şekerdir.

Lambdalardan önce, aynı şeyi elde etmek için anonim sınıflar kullanılabilir. Her lambda ifadesi anonim bir sınıfa dönüştürülebilir.

IntelliJ IDEA kullanıyorsanız, dönüşümü sizin için yapabilir:

  • İmleci lambda'ya getirin
  • Alt / seçenek + enter tuşlarına basın

görüntü açıklamasını buraya girin


3
... @StuartMarks'ın lambdaların sözdizimsel şekerini (sp? Gr?) Zaten açıkladığını düşündüm. ( stackoverflow.com/a/15241404/3475600 , softwareengineering.stackexchange.com/a/181743 )
srborlongan

2

Soruna cevaben, aslına bakarsan lambda'lar olduğu yok sen önce java-8'e yapamayacağını şey yapmalarına izin, o, kendisi için daha fazla yazmak sağlayan özlü kodu. Bunun faydası, kodunuzun daha net ve daha esnek olmasıdır.


Açıkçası @Holger, lambda verimliliği açısından herhangi bir şüpheyi ortadan kaldırdı. Bu yalnızca kodu daha temiz hale getirmenin bir yolu değil, aynı zamanda lambda herhangi bir değer yakalamazsa, bir Singleton sınıfı olacak (yeniden kullanılabilir)
alseether

Derinlere inerseniz, her dil özelliğinin bir farkı vardır. Elimizdeki soru üst düzeyde, bu nedenle cevabımı yüksek düzeyde yazdım.
Ousmane D.

2

Henüz bahsetmediğim bir şey, lambda'nın kullanıldığı yerde işlevselliği tanımlamanıza izin vermesidir .

Dolayısıyla, basit bir seçim işleviniz varsa, onu bir grup standart şablonla ayrı bir yere koymanıza gerek yoktur, sadece kısa ve yerel olarak alakalı bir lambda yazarsınız.


1

Evet birçok avantajı var.

  • Tüm sınıfı tanımlamaya gerek yok, işlevin uygulanmasını referans olarak kendimize iletebiliriz.
    • Sınıfın dahili olarak oluşturulması .class dosyası oluştururken, lambda kullanırsanız, sınıf oluşturma derleyici tarafından engellenir çünkü lambda'da sınıf yerine işlev uygulamasını iletiyorsunuz.
  • Kod yeniden kullanılabilirliği öncekinden daha yüksek
  • Ve dediğiniz gibi kod normal uygulamadan daha kısadır.
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.