Java'da lambda ifadelerini kullanmaya başlayın

Java SE 8'den önce, işlevselliği bir yönteme geçirmek için genellikle anonim sınıflar kullanılırdı. Bu uygulama, kaynak kodunu karmaşık hale getirerek anlaşılmasını zorlaştırdı. Java 8, lambdaları tanıtarak bu sorunu ortadan kaldırdı. Bu eğitici ilk olarak lambda dil özelliğini tanıtır, ardından hedef türlerle birlikte lambda ifadeleriyle işlevsel programlamaya daha ayrıntılı bir giriş sağlar. Ayrıca, lambdaların kapsamlar, yerel değişkenler, thisve superanahtar kelimeler ve Java istisnaları ile nasıl etkileşime girdiğini de öğreneceksiniz . 

Bu eğitimdeki kod örneklerinin JDK 12 ile uyumlu olduğunu unutmayın.

Kendiniz için türleri keşfetmek

Bu eğitimde daha önce öğrenmediğiniz herhangi bir lambda olmayan dil özelliğini tanıtmayacağım, ancak bu seride daha önce tartışmadığım türler aracılığıyla lambdaları göstereceğim. Bir örnek java.lang.Mathsınıftır. Bu türleri gelecekteki Java 101 eğitimlerinde tanıtacağım. Şimdilik, onlar hakkında daha fazla bilgi edinmek için JDK 12 API belgelerini okumanızı öneriyorum.

indir Kodu alın Bu eğitimdeki örnek uygulamalar için kaynak kodunu indirin. JavaWorld için Jeff Friesen tarafından düzenlendi.

Lambdas: Bir astar

Bir lambda ifadesi (lambda) , müteakip yürütme için yapıcılara veya yöntemlere aktarılabilen bir kod bloğunu (anonim bir işlev) tanımlar. Yapıcı veya yöntem lambda'yı bağımsız değişken olarak alır. Aşağıdaki örneği düşünün:

() -> System.out.println("Hello")

Bu örnek, standart çıktı akışına bir mesaj çıktılamak için bir lambda tanımlar. Soldan sağa, ()lambda'nın biçimsel parametre listesini tanımlar (örnekte hiçbir parametre yoktur), ->ifadenin bir lambda System.out.println("Hello")olduğunu ve çalıştırılacak kod olduğunu belirtir .

Lambdas , her biri tam olarak bir soyut yöntem bildiren açıklamalı arabirimler olan işlevsel arabirimlerin kullanımını basitleştirir (ancak varsayılan, statik ve özel yöntemlerin herhangi bir kombinasyonunu da bildirebilirler). Örneğin, standart sınıf kitaplığı, java.lang.Runnabletek bir soyut void run()yöntemle bir arabirim sağlar . Bu işlevsel arayüzün beyanı aşağıda görülmektedir:

@FunctionalInterface public interface Runnable { public abstract void run(); }

Sınıf kütüphanesi annotates Runnableile @FunctionalInterfacebir örneği olan java.lang.FunctionalInterfaceaçıklama türü. FunctionalInterfacelambda bağlamlarında kullanılacak arayüzlere açıklama eklemek için kullanılır.

Bir lambda'nın açık bir arabirim türü yoktur. Bunun yerine, derleyici, bir lambda belirtildiğinde hangi işlevsel arabirimin başlatılacağını anlamak için çevreleyen bağlamı kullanır - lambda bu arabirime bağlıdır . Örneğin, önceki lambda'yı java.lang.Threadsınıfın Thread(Runnable target)yapıcısına bir argüman olarak ileten aşağıdaki kod parçasını belirttiğimi varsayalım :

new Thread(() -> System.out.println("Hello"));

Derleyici lambda'nın geçildiğini belirler Thread(Runnable r)çünkü lambda'yı karşılayan tek kurucu budur: Runnableişlevsel bir arabirimdir, lambda'nın boş biçimsel parametre listesi boş parametre listesiyle ()eşleşir run()ve dönüş türleri ( void) de aynı fikirdedir. Lambda bağlanır Runnable.

Liste 1, kaynak kodunu bu örnekle oynamanıza izin veren küçük bir uygulamaya sunar.

Liste 1. LambdaDemo.java (sürüm 1)

public class LambdaDemo { public static void main(String[] args) { new Thread(() -> System.out.println("Hello")).start(); } }

Liste 1'i ( javac LambdaDemo.java) derleyin ve uygulamayı ( java LambdaDemo) çalıştırın . Aşağıdaki çıktıyı gözlemlemelisiniz:

Hello

Lambdas, yazmanız gereken kaynak kodu miktarını büyük ölçüde basitleştirebilir ve ayrıca kaynak kodunun anlaşılmasını çok daha kolay hale getirebilir. Örneğin, lambdalar olmadan, büyük olasılıkla Liste 2'nin uygulayan anonim bir sınıfın bir örneğini temel alan daha ayrıntılı kodunu belirtirsiniz Runnable.

Liste 2. LambdaDemo.java (sürüm 2)

public class LambdaDemo { public static void main(String[] args) { Runnable r = new Runnable() { @Override public void run() { System.out.println("Hello"); } }; new Thread(r).start(); } }

Bu kaynak kodunu derledikten sonra uygulamayı çalıştırın. Daha önce gösterilenle aynı çıktıyı keşfedeceksiniz.

Lambdas ve Akışlar API'si

Lambdalar, kaynak kodunu basitleştirmenin yanı sıra Java'nın işlevsel odaklı Akış API'sinde önemli bir rol oynar. Çeşitli API yöntemlerine aktarılan işlevsellik birimlerini açıklarlar.

Derinlemesine Java lambdas

Lambdaları etkili bir şekilde kullanmak için, hedef türü kavramıyla birlikte lambda ifadelerinin sözdizimini anlamanız gerekir. Ayrıca, lambdaların kapsamlar, yerel değişkenler, thisve superanahtar kelimeler ve istisnalarla nasıl etkileşime girdiğini anlamanız gerekir . Sonraki bölümlerde tüm bu konuları ele alacağım.

Lambdalar nasıl uygulanır

Lambdalar, Java sanal makinesinin invokedynamictalimatı ve java.lang.invokeAPI açısından uygulanır. Lambda mimarisi hakkında bilgi edinmek için Lambda: Başlığın Altına Göz Atma videosunu izleyin.

Lambda sözdizimi

Her lambda aşağıdaki sözdizimine uyar:

( formal-parameter-list ) -> { expression-or-statements }

Çalışma formal-parameter-listzamanında işlevsel arabirimin tek soyut yönteminin parametreleriyle eşleşmesi gereken, virgülle ayrılmış biçimsel parametreler listesidir. Türlerini atlarsanız, derleyici bu türlere lambda'nın kullanıldığı bağlamdan çıkar. Aşağıdaki örnekleri düşünün:

(double a, double b) // types explicitly specified (a, b) // types inferred by compiler

Lambdas ve var

Java SE 11'den başlayarak, bir tür adını ile değiştirebilirsiniz var. Örneğin, belirtebilirsiniz (var a, var b).

Çoklu veya hiçbir biçimsel parametre için parantez belirlemelisiniz. Ancak, tek bir biçimsel parametre belirlerken parantezleri (gerekmese de) çıkarabilirsiniz. (Bu yalnızca parametre adı için geçerlidir - tür de belirtildiğinde parantez gereklidir.) Aşağıdaki ek örnekleri göz önünde bulundurun:

x // parentheses omitted due to single formal parameter (double x) // parentheses required because type is also present () // parentheses required when no formal parameters (x, y) // parentheses required because of multiple formal parameters

Ardından formal-parameter-listbir ->belirteç gelir ve bunu bir expression-or-statementsifade veya bir ifade bloğu (lambda gövdesi olarak bilinir) izler . İfade tabanlı gövdelerden farklı olarak, ifade tabanlı gövdeler open ( {) ve close ( }) küme ayracı karakterleri arasına yerleştirilmelidir :

(double radius) -> Math.PI * radius * radius radius -> { return Math.PI * radius * radius; } radius -> { System.out.println(radius); return Math.PI * radius * radius; }

İlk örneğin ifade tabanlı lambda gövdesinin kaşlı ayraçlar arasına yerleştirilmesi gerekmez. İkinci örnek, ifade tabanlı gövdeyi, returnifadenin değerini döndürmek için belirtilmesi gereken ifade tabanlı bir gövdeye dönüştürür . Son örnek, çoklu ifadeleri gösterir ve kaşlı ayraçlar olmadan ifade edilemez.

Lambda gövdeleri ve noktalı virgül

;Önceki örneklerde noktalı virgüllerin ( ) yokluğuna veya varlığına dikkat edin. Her durumda, lambda gövdesi noktalı virgülle sonlandırılmaz çünkü lambda bir ifade değildir. Bununla birlikte, ifadeye dayalı bir lambda gövdesi içinde, her ifade noktalı virgülle sonlandırılmalıdır.

Liste 3, lambda sözdizimini gösteren basit bir uygulama sunar; bu listenin önceki iki kod örneğini temel aldığını unutmayın.

Liste 3. LambdaDemo.java (sürüm 3)

@FunctionalInterface interface BinaryCalculator { double calculate(double value1, double value2); } @FunctionalInterface interface UnaryCalculator { double calculate(double value); } public class LambdaDemo { public static void main(String[] args) { System.out.printf("18 + 36.5 = %f%n", calculate((double v1, double v2) -> v1 + v2, 18, 36.5)); System.out.printf("89 / 2.9 = %f%n", calculate((v1, v2) -> v1 / v2, 89, 2.9)); System.out.printf("-89 = %f%n", calculate(v -> -v, 89)); System.out.printf("18 * 18 = %f%n", calculate((double v) -> v * v, 18)); } static double calculate(BinaryCalculator calc, double v1, double v2) { return calc.calculate(v1, v2); } static double calculate(UnaryCalculator calc, double v) { return calc.calculate(v); } }

3 birinci tanıtır Listeleme BinaryCalculatorve UnaryCalculatorfonksiyonel arayüzler calculate()yöntemleri, sırasıyla iki giriş değişkenleri hakkında ya da tek parametreli ilgili hesaplamalar. Bu liste ayrıca LambdaDemo, main()yöntemi bu işlevsel arayüzleri gösteren bir sınıfı da tanıtır .

İşlevsel arayüzler static double calculate(BinaryCalculator calc, double v1, double v2)ve static double calculate(UnaryCalculator calc, double v)yöntemlerinde gösterilmektedir. Lambdalar, BinaryCalculatorveya UnaryCalculatorörnekler olarak alınan bu yöntemlere veri olarak kod geçirir .

Liste 3'ü derleyin ve uygulamayı çalıştırın. Aşağıdaki çıktıyı gözlemlemelisiniz:

18 + 36.5 = 54.500000 89 / 2.9 = 30.689655 -89 = -89.000000 18 * 18 = 324.000000

Hedef türleri

Bir lambda, lambda'nın bağlı olduğu nesnenin türünü tanımlayan örtük bir hedef türüyle ilişkilendirilir . Hedef türü, bağlamdan çıkarılan ve lambdaların aşağıdaki bağlamlarda görünmesini sınırlayan işlevsel bir arabirim olmalıdır:

  • Değişken beyanı
  • Görev
  • Dönüş ifadesi
  • Dizi başlatıcı
  • Yöntem veya yapıcı bağımsız değişkenleri
  • Lambda gövdesi
  • Üçlü koşullu ifade
  • Cast ifadesi

Liste 4, bu hedef tipi bağlamlarını gösteren bir uygulama sunar.

Liste 4. LambdaDemo.java (sürüm 4)

import java.io.File; import java.io.FileFilter; import java.nio.file.Files; import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.FileVisitor; import java.nio.file.FileVisitResult; import java.nio.file.Path; import java.nio.file.PathMatcher; import java.nio.file.Paths; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.concurrent.Callable; public class LambdaDemo { public static void main(String[] args) throws Exception { // Target type #1: variable declaration Runnable r = () -> { System.out.println("running"); }; r.run(); // Target type #2: assignment r = () -> System.out.println("running"); r.run(); // Target type #3: return statement (in getFilter()) File[] files = new File(".").listFiles(getFilter("txt")); for (int i = 0; i  path.toString().endsWith("txt"), (path) -> path.toString().endsWith("java") }; FileVisitor visitor; visitor = new SimpleFileVisitor() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attribs) { Path name = file.getFileName(); for (int i = 0; i  System.out.println("running")).start(); // Target type #6: lambda body (a nested lambda) Callable callable = () -> () -> System.out.println("called"); callable.call().run(); // Target type #7: ternary conditional expression boolean ascendingSort = false; Comparator cmp; cmp = (ascendingSort) ? (s1, s2) -> s1.compareTo(s2) : (s1, s2) -> s2.compareTo(s1); List cities = Arrays.asList("Washington", "London", "Rome", "Berlin", "Jerusalem", "Ottawa", "Sydney", "Moscow"); Collections.sort(cities, cmp); for (int i = 0; i < cities.size(); i++) System.out.println(cities.get(i)); // Target type #8: cast expression String user = AccessController.doPrivileged((PrivilegedAction) () -> System.getProperty("user.name")); System.out.println(user); } static FileFilter getFilter(String ext) { return (pathname) -> pathname.toString().endsWith(ext); } }