در سال ۲۰۱۴، جاوا SE 8 مفهوم Lambda Expressionها را معرفی کرد. اگر روزهای قبل از انتشار جاوا SE 8 را به یاد دارید، احتمالاً مفهوم Anonymous Classها را به یاد دارید. و شاید شنیده باشید که Lambda Expressionها روش دیگر و سادهتری برای نوشتن نمونههای Anonymous Class هستند، در برخی موارد خاص.
اگر آن روزها را به یاد نمیآورید، ممکن است درباره Anonymous Classها شنیده یا خوانده باشید و احتمالاً از این سینتکس مبهم میترسید.
خبر خوب این است: لازم نیست از Anonymous Classها عبور کنید تا بفهمید چگونه Lambda Expression بنویسید. علاوه بر این، در بسیاری از موارد، به لطف اضافه شدن Lambda به زبان جاوا، دیگر به Anonymous Class نیاز ندارید.
نوشتن Lambda Expression به درک سه مرحله تقسیم میشود:
واقعاً همین است. بیایید این سه مرحله را با جزئیات ببینیم.
اگر مشتاق تمرین استفاده از Lambda با Collectionها هستید، میتوانید مستقیماً به انتهای این صفحه بروید: تمرین استفاده از Lambda در Collectionها
---هر چیزی در زبان جاوا نوعی دارد و این نوع در زمان کامپایل مشخص است. بنابراین همیشه میتوان نوع Lambda Expression را پیدا کرد. ممکن است نوع متغیر، فیلد، پارامتر متد یا نوع بازگشتی متد باشد.
محدودیتی روی نوع Lambda Expression وجود دارد: باید یک Functional Interface باشد. بنابراین Anonymous Classی که Functional Interface پیادهسازی نمیکند، نمیتواند به صورت Lambda Expression نوشته شود.
تعریف کامل Functional Interface کمی پیچیده است. آنچه در این مرحله باید بدانید این است که Functional Interface، Interfaceای است که فقط یک متد abstract دارد.
باید بدانید که از جاوا SE 8 به بعد، متدهای concrete در Interfaceها مجاز هستند. میتوانند متدهای instance باشند که در آن صورت Default Method نامیده میشوند، یا متدهای static باشند. این متدها شمرده نمیشوند چون abstract نیستند.
آیا باید Annotation
@FunctionalInterfaceرا به Interface اضافه کنم تا Functional شود؟خیر. این Annotation برای کمک به شماست تا مطمئن شوید Interface واقعاً Functional است. اگر این Annotation را روی نوعی قرار دهید که Functional Interface نیست، کامپایلر خطا ایجاد میکند.
بیایید چند مثال از API جاوا ببینیم. فقط کامنتها را از کد حذف کردهایم.
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
Interface Runnable واقعاً Functional است چون فقط یک متد abstract دارد. Annotation @FunctionalInterface به عنوان کمک اضافه شده اما ضروری نیست.
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
default Consumer<T> andThen(Consumer<? super T> after) {
// the body of this method has been removed
}
}
Interface Consumer<T> هم Functional است: یک متد abstract و یک متد default concrete دارد که شمرده نمیشود. باز هم، Annotation @FunctionalInterface ضروری نیست.
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
default Predicate<T> and(Predicate<? super T> other) {
// the body of this method has been removed
}
default Predicate<T> negate() {
// the body of this method has been removed
}
default Predicate<T> or(Predicate<? super T> other) {
// the body of this method has been removed
}
static <T> Predicate<T> isEqual(Object targetRef) {
// the body of this method has been removed
}
static <T> Predicate<T> not(Predicate<? super T> target) {
// the body of this method has been removed
}
}
Interface Predicate<T> کمی پیچیدهتر است اما همچنان Functional Interface است:
اکنون نوع Lambda Expressionای که باید بنویسید را شناسایی کردهاید و خبر خوب این است: سختترین بخش را انجام دادهاید: بقیه بسیار مکانیکی و آسانتر است.
Lambda Expression پیادهسازی تنها متد abstract در این Functional Interface است. بنابراین پیدا کردن متد صحیح فقط پیدا کردن همین متد است.
میتوانید یک دقیقه وقت بگذارید و در سه مثال پاراگراف قبل آن را پیدا کنید.
برای Interface Runnable:
public abstract void run();
برای Interface Predicate:
boolean test(T t);
و برای Interface Consumer<T>:
void accept(T t);
---
Predicate<String> را پیادهسازی میکنداکنون برای بخش آخر: نوشتن خود Lambda. آنچه باید بفهمید این است که Lambda Expressionای که مینویسید، پیادهسازی متد abstractای است که پیدا کردهاید. با استفاده از سینتکس Lambda Expression، میتوانید این پیادهسازی را بهصورت inline در کد قرار دهید.
این سینتکس از سه عنصر تشکیل شده:
->. توجه کنید جاوا از تیرهای باریک (->) و نه تیرهای پهن (=>) استفاده میکند؛بیایید مثالهایی ببینیم. فرض کنید به نمونهای از Predicate نیاز دارید که برای رشتههای با طول دقیقاً ۳ کاراکتر true برگرداند.
Predicate استboolean test(String s) استسپس بلوک پارامترها را مینویسید، که کپی/پیست سادهی signature متد است: (String s).
سپس تیر باریک اضافه کنید: ->.
و بدنهی متد. نتیجه باید این شکلی باشد:
Predicate<String> predicate =
(String s) -> {
return s.length() == 3;
};
این سینتکس سپس میتواند ساده شود، به لطف کامپایلر که میتواند بسیاری چیزها را حدس بزند تا مجبور نباشید آنها را بنویسید.
اول، کامپایلر میداند که متد abstract Interface Predicate را پیادهسازی میکنید و میداند این متد یک String به عنوان آرگومان میگیرد. پس (String s) میتواند به (s) ساده شود. در این حالت که فقط یک آرگومان وجود دارد، حتی میتوانید یک قدم جلوتر بروید و پرانتزها را حذف کنید. بلوک آرگومانها به s تبدیل میشود. اگر بیش از یک آرگومان یا هیچ آرگومانی ندارید، پرانتزها را نگه دارید.
دوم، فقط یک خط کد در بدنهی متد وجود دارد. در این حالت به Curly Braces و کلمهکلیدی return نیاز ندارید.
پس سینتکس نهایی در واقع به این صورت است:
Predicate<String> predicate = s -> s.length() == 3;
Consumer<String>در某个 момент، افراد ممکن است وسوسه شوند میانبر بزنند. خواهید شنید توسعهدهندگان میگویند «Consumer شیای میگیرد و چیزی برنمیگرداند». یا «Predicate وقتی true است که رشته دقیقاً سه کاراکتر دارد». بیشتر اوقات، بین Lambda Expression، متد abstractای که پیادهسازی میکند، و Functional Interfaceای که این متد را در خود دارد، ابهام وجود دارد.
اما چون Functional Interface، متد abstract آن و Lambda Expressionای که آن را پیادهسازی میکند بسیار به هم پیوند خوردهاند، این شیوهی صحبت کردن کاملاً منطقی است. پس تا زمانی که به ابهامی منجر نشود، مشکلی نیست.
بیایید Lambdaای بنویسیم که Stringای را مصرف و روی System.out چاپ کند. سینتکس میتواند این باشد:
Consumer<String> print = s -> IO.println(s);
اینجا مستقیماً نسخهی سادهشدهی Lambda Expression را نوشتیم.
پیادهسازی Runnable در واقع نوشتن پیادهسازی void run() است. بلوک آرگومانها خالی است، پس باید با پرانتز نوشته شود. به یاد داشته باشید: فقط وقتی میتوانید پرانتزها را حذف کنید که یک آرگومان داشته باشید، اینجا صفر داریم.
بیایید Runnableای بنویسیم که به ما بگوید در حال اجراست:
Runnable runnable = () -> IO.println("I am running");
---
بیایید به مثال قبلی Predicate برگردیم و فرض کنیم این Predicate در یک متد تعریف شده. چگونه میتوانید از آن برای آزمایش اینکه آیا رشتهی کاراکتر دادهشده دقیقاً طول ۳ دارد استفاده کنید؟
خوب، با وجود سینتکسی که برای نوشتن Lambda استفاده کردید، باید به یاد داشته باشید که این Lambda نمونهای از Interface Predicate است. این Interface متدی به نام test() تعریف میکند که String میگیرد و boolean برمیگرداند.
بیایید آن را در یک متد بنویسیم:
اجرای کد قبلی خروجی زیر را چاپ میکند.
Original list: [one, two, three, four]
Only elements of length 3: [one, two]
توجه کنید چگونه Predicate را تعریف کردید، دقیقاً مثل مثال قبلی. چون Interface Predicate این متد boolean test(String) را تعریف میکند، فراخوانی متدهای تعریفشده در Predicate از طریق متغیری از نوع Predicate کاملاً مجاز است. ممکن است در نگاه اول گیجکننده به نظر برسد.
صبر کنید، راههای بسیار بهتری برای نوشتن این کد وجود دارد که بعداً در این آموزش خواهید دید.
پس هر بار Lambda مینویسید، میتوانید هر متد تعریفشده در Interfaceای را که Lambda آن را پیادهسازی میکند فراخوانی کنید. فراخوانی متد abstract، کد Lambda را فراخوانی میکند. فراخوانی متد default، کد نوشتهشده در Interface را فراخوانی میکند. Lambda هیچوقت نمیتواند متد default را بازنویسی کند.
---وقتی به آنها عادت کنید، نوشتن Lambda بسیار طبیعی میشود. آنها بهخوبی در Collections Framework، Stream API و بسیاری جاهای دیگر در JDK ادغام شدهاند. از جاوا SE 8 به بعد، Lambda همه جا هست، به نفع همه.
محدودیتهایی در استفاده از Lambda وجود دارد و ممکن است با خطاهای زمان کامپایل مواجه شوید که باید آنها را درک کنید.
کد زیر را در نظر بگیرید:
int calculateTotalPrice(List<Product> products) {
int totalPrice = 0;
Consumer<Product> consumer =
product -> totalPrice += product.getPrice();
for (Product product: products) {
consumer.accept(product);
}
}
اگرچه این کد خوب به نظر میرسد، تلاش برای کامپایل آن خطای زیر را در استفاده از totalPrice در این پیادهسازی Consumer نشان میدهد:
Variable used in lambda expression should be final or effectively final
دلیل این است: Lambda نمیتواند متغیرهای تعریفشده خارج از بدنهی خود را تغییر دهد. میتوانند آنها را بخوانند، تا زمانی که final باشند، یعنی تغییرناپذیر. این فرآیند دسترسی به متغیر گرفتن (Capturing) نام دارد: Lambda نمیتواند متغیرها را بگیرد، فقط میتواند مقادیر را بگیرد. متغیر final در واقع یک مقدار است.
متوجه شدهاید که پیام خطا به ما میگوید متغیر میتواند final باشد، که مفهومی کلاسیک در زبان جاوا است. همچنین میگوید متغیر میتواند effectively final باشد. این مفهوم در جاوا SE 8 معرفی شد: حتی اگر متغیر را صریحاً final اعلان نکنید، کامپایلر ممکن است این کار را برای شما انجام دهد. اگر ببیند این متغیر از Lambda خوانده میشود و شما آن را تغییر نمیدهید، اعلان final را بهصورت خودکار اضافه میکند. این متغیرها final نامیده نمیشوند؛ effectively final نامیده میشوند. این قابلیت بسیار مفیدی است.
Lambda Expressionها طوری ساخته شدهاند که بتوان آنها را سریالسازی کرد.
چرا Lambda Expressionها را سریالسازی میکنید؟ خوب، Lambda Expression ممکن است در فیلدی ذخیره شود و این فیلد ممکن است از طریق Constructor یا متد Setter قابل دسترسی باشد. سپس ممکن است Lambda Expression در وضعیت شی شما در زمان اجرا باشد، بدون اینکه از آن آگاه باشید.
پس برای حفظ سازگاری معکوس با کلاسهای سریالپذیر موجود، سریالسازی Lambda Expression ممکن است.
---اجرای کد قبلی خروجی زیر را چاپ میکند.
Message: Duke loves Java
Message: Duke also loves coffee
اجرای کد قبلی خروجی زیر را چاپ میکند.
Did we already mention that Duke loves Java?
اجرای کد قبلی خروجی زیر را چاپ میکند.
'Duke' has 4 chars? true
'loves' has 4 chars: false
'Java' has 4 chars: true
اجرای کد قبلی خروجی زیر را چاپ میکند.
Length of 'Duke': 4
میتوانید عناصر یک Collection را یکییکی با استفاده از Consumer چاپ کنید.
اجرای کد قبلی خروجی زیر را چاپ میکند.
cat
dog
elephant
ant
bee
butterfly
میتوانید عناصر یک Collection را با Function تغییر دهید، تا زمانی که نوع عناصر را تغییر ندهید.
اجرای کد قبلی خروجی زیر را چاپ میکند.
[cat, dog, elephant, ant, bee, butterfly]
[CAT, DOG, ELEPHANT, ANT, BEE, BUTTERFLY]
میتوانید بهصورت انتخابی عناصری از لیست را با Predicate حذف کنید.
اجرای کد قبلی خروجی زیر را چاپ میکند.
[cat, dog, elephant, ant, bee, butterfly]
[cat, dog, elephant, ant]
این محتوا کاملا رایگان توسط تیم کدلپر ترجمه شده و در اختیار شما کاربران عزیز قرار گرفته است، هر گونه کپی برداری برای مقاصد غیر رایگان و بدون ذکر منبع، مورد پیگیری قانونی قرار میگیرد.
ترجمه شده از منبع: https://dev.java/learn/