استفاده از متغیر final در عبارت‌های لامبدا جاوا 8

عبارت‌های لامبدا (Lambda Expression) یکی از نکات قوت جاوا 8 محسوب می‌شوند و بوسیله‌ی آنها می‌توان تعداد خط کد (Line of Code) را کاهش داد، همچنین کدهای نوشته شده با عبارت‌های لامبدا دارای خوانایی بهتری هستند (به زبان انسان نزدیکترند). با استفاده از عبارت لامبدا دیگر نیاز نیست برای کارهای ساده (مثل حلقه‌های تکرار و فیلتر کردن مقادیر) کد بیشتر بنویسیم و درنهایت قابلیت استفاده‌ی مجدد از مزیت‌های عبارت‌های لامبدا محسوب می‌شود. من قبلا در این وبلاگ در دو پست پیاده‌سازی حلقه‌های تودرتو با استفاده از لامبدا در جاوا ۸ و معادل متد contains در جاوا ۸ به بیان چند مثال ساده از نحوه‌ی استفاده از عبارت‌های لامبدا پرداخته‌ام؛ در این نوشته نیز می‌خواهم نحوه‌ی استفاده از یک متغیر محلی (Local Variable) را تحت عنوان یک متغیر نهایی (final Variable) در عبارت‌های لامبدا را آموزش دهم.

فرض کنید که یک کلاس به صورت زیر داریم:

public class Item{
    private String name;
    //Constructor
    //getter & setter
}

و می‌خواهیم با استفاده از Lambda Expression کاری روی آن انجام دهیم:

List<Item> items = new ArrayList<>();
items.add(new Item("a b c"));

String result = " d";

items.forEach(item -> result = item.getName().concat(result));
System.out.println(result);

خطای اولیه‌ای که با آن مواجه می‌شویم این است

Variables used in lambda should be final or effectively final

برای رفع این خطا، String result باید final تعریف کنیم (یعنی مقدار آن پس از initialize نمی‌تواند تغییر کند). پس از finalکردن متغیر به خطای دیگری برمی‌خوریم

Can not assign a value to final Variable 'result'

می‌توان این خطا را با Tips زیر که توسط IntelliJ IDEA پیشنهاد می‌شود حل کرد:

Transform result variable into final one element array

با این تغییر کد ما به صرت زیر در می‌آید:

List<Item> items = new ArrayList<>();
items.add(new Item("a b c"));

final String[] result = {" d"};

items.forEach(item -> result[0] = item.getName().concat(result[0]));
System.out.println(result[0]); //result: a b c d

با این تغییر مشکل کد حل می‌شود؛ اما چرا استفاده از آرایه تک عنصری به جای یک متغیر مشکل را رفع کرد؟

در پاسخ این سوال می‌توان گفت که متغیر محلی که در عبارت لامبدا استفاده می‌شود باید final یا effectively final باشد؛ به عبارت دیگر، ما مجاز به بازنویسی مقدار آن متغیر محلی در هر بار اجرای حلقه foreach لامبدا نیستیم. اما وقتی که یک آرایه (یا یک wrapper object) استفاده می‌کنیم، دیگر یک مقدار جدید را به آن متغییر تخصیص نمی‌دهیم بلکه فقط برخی aspectهای آن را تغییر می‌دهیم.بنابراین آن می‌تواند final باشد.

نکته‌ی مهم: باید توجه داشته باشید که چون عبارت لامبدا به صورت anonymous هستند، ما مجاز نیستیم متغیرهای محلی را در آن‌ها استفاده کنیم و اگر با استفاده از trick مطرح شده در این پست این کار را انجام دهیم، در حقیقت اصول و قواعد استفاده از عبارت لامبدا را زیر پا گذاشته‌ام، چیزی که در این پست مطرح شده است صرفا یک Quick Fix است و در اولین Refactoring باید تغییر کند، به عنوان مثال در موردی که من نیاز داشتم که یک متغیر محلی در داخل یک عبارات لامبدا استفاده کنم، در نهایت این متغیر محلی را با یک private class جایگزین کردم.


برچسب‌ها: ، ، ، ، ، ، ،
مرتضی اسدی
مرتضی اسدی

سلام! من مرتضی اسدی هستم، یک توسعه‌دهنده‌ی نرم‌افزار و در این وبلاگ دست‌نوشته‌هایم را می‌نویسم.