JavaRush /وبلاگ جاوا /Random-FA /عبارات لامبدا با مثال

عبارات لامبدا با مثال

در گروه منتشر شد
جاوا در ابتدا یک زبان کاملاً شی گرا است. به استثنای انواع اولیه، همه چیز در جاوا یک شی است. حتی آرایه ها نیز اشیا هستند. نمونه های هر کلاس اشیا هستند. هیچ امکان واحدی برای تعریف یک تابع به طور جداگانه وجود ندارد (خارج از یک کلاس - تقریباً ترجمه. ). و هیچ راهی برای ارسال یک متد به عنوان آرگومان یا برگرداندن بدنه متد به عنوان نتیجه متد دیگر وجود ندارد. مثل اونه. اما این قبل از جاوا 8 بود. عبارات لامبدا با مثال - 1از زمان Swing خوب، نوشتن کلاس های ناشناس در زمانی که لازم بود برخی از عملکردها به روشی منتقل شود، ضروری بود. به عنوان مثال، این چیزی است که اضافه کردن یک رویداد کنترل کننده به نظر می رسد:
someObject.addMouseListener(new MouseAdapter() {
            public void mouseClicked(MouseEvent e) {

                //Event listener implementation goes here...

            }
        });
در اینجا می خواهیم کدی را به شنونده رویداد ماوس اضافه کنیم. ما یک کلاس ناشناس تعریف کردیم MouseAdapterو بلافاصله یک شی از آن ایجاد کردیم. به این ترتیب، ما قابلیت های اضافی را به addMouseListener. به طور خلاصه، عبور یک روش ساده (عملکردی) در جاوا از طریق آرگومان ها آسان نیست. این محدودیت، توسعه دهندگان جاوا 8 را مجبور کرد تا قابلیتی مانند عبارت Lambda را به مشخصات زبان اضافه کنند.

چرا جاوا به عبارات لامبدا نیاز دارد؟

از همان ابتدا، زبان جاوا پیشرفت چندانی نکرده است، به جز مواردی مانند Annotations، Generics و غیره. اول از همه، جاوا همیشه شی گرا باقی مانده است. پس از کار با زبان های کاربردی مانند جاوا اسکریپت، می توان متوجه شد که چگونه جاوا به شدت شی گرا و به شدت تایپ می شود. توابع در جاوا مورد نیاز نیست. به خودی خود، آنها را نمی توان در دنیای جاوا یافت. در زبان های برنامه نویسی تابعی، توابع مطرح می شوند. آنها به تنهایی وجود دارند. می توانید آنها را به متغیرها اختصاص دهید و آنها را از طریق آرگومان ها به توابع دیگر منتقل کنید. جاوا اسکریپت یکی از بهترین نمونه های زبان های برنامه نویسی تابعی است. شما می توانید مقالات خوبی را در اینترنت بیابید که مزایای جاوا اسکریپت را به عنوان یک زبان کاربردی به تفصیل شرح می دهد. زبان‌های کاربردی دارای ابزارهای قدرتمندی مانند Closure هستند که مزایای زیادی نسبت به روش‌های سنتی نوشتن برنامه‌ها ارائه می‌کنند. بسته شدن تابعی است که یک محیط به آن متصل است - جدولی که ارجاعات به همه متغیرهای غیر محلی تابع را ذخیره می کند. در جاوا، بسته شدن ها را می توان از طریق عبارات لامبدا شبیه سازی کرد. البته بین بسته ها و عبارات لامبدا تفاوت هایی وجود دارد و نه کوچک، اما عبارت های لامبدا جایگزین مناسبی برای بسته شدن هستند. استیو یگ در وبلاگ طعنه آمیز و خنده دار خود توضیح می دهد که چگونه دنیای جاوا به شدت با اسم ها (موجودات، اشیاء - تقریباً ترجمه. ) گره خورده است. اگر وبلاگ او را نخوانده اید، توصیه می کنم. او دلیل دقیق اضافه شدن عبارات لامبدا به جاوا را به شیوه ای خنده دار و جالب توضیح می دهد. عبارات لامبدا قابلیت هایی را به جاوا می آورند که مدت هاست گم شده است. عبارات لامبدا عملکردی را مانند اشیا به زبان می آورند. در حالی که این 100٪ درست نیست، می توانید ببینید که عبارات لامبدا، در حالی که بسته نیستند، قابلیت های مشابهی را ارائه می دهند. در زبان تابعی، عبارات لامبدا توابعی هستند. اما در جاوا، عبارات لامبدا با اشیاء نشان داده می شوند و باید با یک نوع شی خاص به نام رابط تابعی مرتبط شوند. در ادامه به بررسی آن خواهیم پرداخت. مقاله ماریو فوسکو "چرا به بیان لامبدا در جاوا نیاز داریم" به طور مفصل توضیح می دهد که چرا همه زبان های مدرن به قابلیت های بسته شدن نیاز دارند.

مقدمه ای بر عبارات لامبدا

عبارات لامبدا توابع ناشناس هستند (ممکن است 100٪ تعریف درستی برای جاوا نباشد، اما تا حدی وضوح را به ارمغان می آورد). به عبارت ساده، این یک روش بدون اعلان است، یعنی. بدون تغییر دهنده دسترسی، مقدار و نام را برمی گرداند. به طور خلاصه، آنها به شما اجازه می دهند که یک روش بنویسید و بلافاصله از آن استفاده کنید. به ویژه در مورد فراخوانی روش یکبار مصرف مفید است، زیرا زمان اعلان و نوشتن متد را بدون نیاز به ایجاد کلاس کاهش می دهد. عبارات لامبدا در جاوا معمولاً دارای نحو زیر هستند (аргументы) -> (тело). مثلا:
(арг1, арг2...) -> { тело }

(тип1 арг1, тип2 арг2...) -> { тело }
در زیر چند نمونه از عبارات واقعی لامبدا آورده شده است:
(int a, int b) -> {  return a + b; }

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

(String s) -> { System.out.println(s); }

() -> 42

() -> { return 3.1415 };

ساختار عبارات لامبدا

بیایید ساختار عبارات لامبدا را مطالعه کنیم:
  • عبارات لامبدا می توانند 0 یا بیشتر پارامتر ورودی داشته باشند.
  • نوع پارامترها را می توان به صراحت مشخص کرد یا می توان از متن به دست آورد. برای مثال ( int a) را می توان اینگونه نوشت ( a)
  • پارامترها در داخل پرانتز قرار می گیرند و با کاما از هم جدا می شوند. برای مثال ( a, b) یا ( int a, int b) یا ( String a, int b, float c)
  • اگر هیچ پارامتری وجود ندارد، باید از پرانتز خالی استفاده کنید. مثلا() -> 42
  • هنگامی که تنها یک پارامتر وجود دارد، اگر نوع به صراحت مشخص نشده باشد، پرانتزها را می توان حذف کرد. مثال:a -> return a*a
  • بدنه یک عبارت لامبدا می تواند شامل 0 یا بیشتر باشد.
  • اگر بدنه از یک عبارت واحد تشکیل شده باشد، ممکن است در پرانتزهای فرفری محصور نشود و مقدار بازگشتی ممکن است بدون کلمه کلیدی مشخص شود return.
  • در غیر این صورت، پرانتزهای فرفری مورد نیاز هستند (بلوک کد) و مقدار بازگشتی باید در انتها با استفاده از کلمه کلیدی مشخص شود return(در غیر این صورت نوع برگشتی خواهد بود void).

یک رابط کاربردی چیست

در جاوا، اینترفیس‌های نشانگر، اینترفیس‌هایی هستند که متدها یا فیلدها را اعلام نمی‌کنند. به عبارت دیگر، رابط های نشانه، رابط های خالی هستند. به طور مشابه، واسط های تابعی، رابط هایی هستند که تنها یک روش انتزاعی بر روی آن اعلام شده است. java.lang.Runnableنمونه ای از یک رابط کاربردی است. فقط یک روش را اعلام می کند void run(). همچنین یک رابط وجود دارد ActionListener- همچنین کاربردی. قبلاً مجبور بودیم از کلاس های ناشناس برای ایجاد اشیایی که یک رابط عملکردی را پیاده سازی می کنند استفاده کنیم. با عبارات لامبدا، همه چیز ساده تر شده است. هر عبارت لامبدا می تواند به طور ضمنی به برخی از رابط های کاربردی متصل شود. به عنوان مثال، می توانید یک مرجع به Runnableیک رابط ایجاد کنید، همانطور که در مثال زیر نشان داده شده است:
Runnable r = () -> System.out.println("hello world");
این نوع تبدیل همیشه به طور ضمنی انجام می شود، زمانی که ما یک رابط کاربردی را مشخص نمی کنیم:
new Thread(
    () -> System.out.println("hello world")
).start();
در مثال بالا، کامپایلر به طور خودکار یک عبارت لامبدا را به عنوان پیاده سازی Runnableرابط از سازنده کلاس Thread: ایجاد می کند. public Thread(Runnable r) { }در اینجا چند نمونه از عبارات لامبدا و رابط های عملکردی مربوطه آورده شده است:
Consumer<Integer> c = (int x) -> { System.out.println(x) };

BiConsumer<Integer, String> b = (Integer x, String y) -> System.out.println(x + " : " + y);

Predicate<String> p = (String s) -> { s == null };
حاشیه نویسی @FunctionalInterfaceاضافه شده در جاوا 8 مطابق با مشخصات زبان جاوا بررسی می کند که آیا رابط اعلام شده کاربردی است یا خیر. علاوه بر این، جاوا 8 شامل تعدادی رابط کاربردی آماده برای استفاده با عبارات لامبدا است. @FunctionalInterfaceاگر رابط اعلام شده کاربردی نباشد، خطای کامپایل ایجاد می کند. در زیر نمونه ای از تعریف یک رابط کاربردی است:
@FunctionalInterface
public interface WorkerInterface {

    public void doSomeWork();

}
همانطور که تعریف نشان می دهد، یک رابط کاربردی می تواند تنها یک روش انتزاعی داشته باشد. اگر سعی کنید روش انتزاعی دیگری اضافه کنید، با خطای کامپایل مواجه خواهید شد. مثال:
@FunctionalInterface
public interface WorkerInterface {

    public void doSomeWork();

    public void doSomeMoreWork();

}
خطا
Unexpected @FunctionalInterface annotation
    @FunctionalInterface ^ WorkerInterface is not a functional interface multiple
    non-overriding abstract methods found in interface WorkerInterface 1 error
После определения функционального интерфейса, мы можем его использовать и получать все преимущества Lambda-выражений. Пример:// defining a functional interface
@FunctionalInterface
public interface WorkerInterface {

    public void doSomeWork();

}
public class WorkerInterfaceTest {

    public static void execute(WorkerInterface worker) {
        worker.doSomeWork();
    }

    public static void main(String [] args) {

      // calling the doSomeWork method via an anonymous class
      // (classic)
      execute(new WorkerInterface() {
            @Override
            public void doSomeWork() {
               System.out.println("Worker called via an anonymous class");
            }
        });

      // calling the doSomeWork method via Lambda expressions
      // (Java 8 new)
      execute( () -> System.out.println("Worker called via Lambda") );
    }

}
نتیجه:
Worker вызван через анонимный класс
Worker вызван через Lambda
در اینجا ما رابط کاربردی خود را تعریف کرده ایم و از عبارت لامبدا استفاده کرده ایم. این روش execute()قادر به پذیرش عبارات لامبدا به عنوان یک آرگومان است.

نمونه هایی از عبارات لامبدا

بهترین راه برای درک عبارات Lambda این است که به چند مثال نگاه کنید: یک جریان را Threadمی توان به دو روش مقداردهی اولیه کرد:
// Old way:
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello from thread");
    }
}).start();
// New way:
new Thread(
    () -> System.out.println("Hello from thread")
).start();
مدیریت رویداد در جاوا 8 نیز از طریق عبارت Lambda قابل انجام است. دو روش زیر برای افزودن یک رویداد کنترل کننده ActionListenerبه یک مؤلفه رابط کاربری وجود دارد:
// Old way:
button.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        System.out.println("Button pressed. Old way!");
    }
});
// New way:
button.addActionListener( (e) -> {
        System.out.println("Button pressed. Lambda!");
});
یک مثال ساده از نمایش تمام عناصر یک آرایه داده شده. توجه داشته باشید که بیش از یک راه برای استفاده از عبارت لامبدا وجود دارد. در زیر یک عبارت لامبدا را به روش معمول با استفاده از نحو فلش ایجاد می کنیم و همچنین از عملگر دو نقطه ای استفاده می کنیم (::)که در جاوا 8 یک متد معمولی را به یک عبارت لامبدا تبدیل می کند:
// Old way:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
for(Integer n: list) {
    System.out.println(n);
}
// New way:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
list.forEach(n -> System.out.println(n));
// New way using double colon operator ::
list.forEach(System.out::println);
در مثال زیر، ما از یک رابط کاربردی Predicateبرای ایجاد یک تست و چاپ مواردی که آن تست را قبول می‌کنند، استفاده می‌کنیم. به این ترتیب می توانید منطق را در عبارات لامبدا قرار دهید و کارها را بر اساس آن انجام دهید.
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;

public class Main {

    public static void main(String [] a)  {

        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);

        System.out.print("Outputs all numbers: ");
        evaluate(list, (n)->true);

        System.out.print("Does not output any number: ");
        evaluate(list, (n)->false);

        System.out.print("Output even numbers: ");
        evaluate(list, (n)-> n%2 == 0 );

        System.out.print("Output odd numbers: ");
        evaluate(list, (n)-> n%2 == 1 );

        System.out.print("Output numbers greater than 5: ");
        evaluate(list, (n)-> n > 5 );

    }

    public static void evaluate(List<Integer> list, Predicate<Integer> predicate) {
        for(Integer n: list)  {
            if(predicate.test(n)) {
                System.out.print(n + " ");
            }
        }
        System.out.println();
    }

}
نتیجه:
Выводит все числа: 1 2 3 4 5 6 7
Не выводит ни одного числа:
Вывод четных чисел: 2 4 6
Вывод нечетных чисел: 1 3 5 7
Вывод чисел больше 5: 6 7
با سرهم کردن عبارات لامبدا، می توانید مربع هر عنصر از لیست را نمایش دهید. توجه داشته باشید که ما از روشی stream()برای تبدیل یک لیست معمولی به یک جریان استفاده می کنیم. جاوا 8 یک کلاس عالی Stream( java.util.stream.Stream) ارائه می دهد. این شامل هزاران روش مفید است که می توانید از عبارات لامبدا استفاده کنید. ما یک عبارت لامبدا را x -> x*xبه متد ارسال می کنیم map()که آن را برای همه عناصر موجود در جریان اعمال می کند. پس از آن ما forEachبرای چاپ تمام عناصر لیست استفاده می کنیم.
// Old way:
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
for(Integer n : list) {
    int x = n * n;
    System.out.println(x);
}
// New way:
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
list.stream().map((x) -> x*x).forEach(System.out::println);
با توجه به یک لیست، باید مجموع مربع های تمام عناصر لیست را چاپ کنید. عبارات لامبدا به شما این امکان را می دهد که فقط با نوشتن یک خط کد به این هدف برسید. این مثال از روش کانولوشن (کاهش) استفاده می کند reduce(). ما از یک روش map()برای مربع هر عنصر استفاده می کنیم و سپس از روشی reduce()برای جمع کردن همه عناصر به یک عدد استفاده می کنیم.
// Old way:
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
int sum = 0;
for(Integer n : list) {
    int x = n * n;
    sum = sum + x;
}
System.out.println(sum);
// New way:
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
int sum = list.stream().map(x -> x*x).reduce((x,y) -> x + y).get();
System.out.println(sum);

تفاوت بین عبارات لامبدا و کلاس های ناشناس

تفاوت اصلی در استفاده از کلمه کلیدی است this. برای کلاس های ناشناس، کلمه کلیدی " " thisیک شی از کلاس ناشناس را نشان می دهد، در حالی که در یک عبارت لامبدا، " this" نشان دهنده یک شی از کلاس است که در آن عبارت lambda استفاده می شود. تفاوت دیگر در نحوه کامپایل آنهاست. جاوا عبارات لامبدا را کامپایل می کند و آنها را به privateمتدهای کلاس تبدیل می کند. این از دستورالعمل invokedynamic استفاده می کند که در جاوا 7 برای اتصال متد پویا معرفی شده است. تال وایس در وبلاگ خود توضیح داد که چگونه جاوا عبارات لامبدا را در بایت کد کامپایل می کند

نتیجه

مارک رینهولد (معمار اصلی اوراکل) عبارات لامبدا را مهم‌ترین تغییر در مدل برنامه‌نویسی می‌داند که تا به حال رخ داده است - حتی مهم‌تر از ژنریک. باید حق با او باشد، زیرا ... آنها به برنامه نویسان جاوا قابلیت های زبان برنامه نویسی کاربردی را می دهند که همه منتظر آن بودند. در کنار نوآوری هایی مانند روش های توسعه مجازی، عبارت های Lambda به شما امکان می دهند کدهای بسیار باکیفیت بنویسید. امیدوارم این مقاله نگاهی به جاوا 8 داشته باشد. موفق باشید :)
نظرات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION