JavaRush /وبلاگ جاوا /Random-FA /محبوب در مورد عبارات لامبدا در جاوا. همراه با مثال و کار....
Стас Пасинков
مرحله
Киев

محبوب در مورد عبارات لامبدا در جاوا. همراه با مثال و کار. قسمت 1

در گروه منتشر شد
این مقاله برای چه کسانی است؟
  • برای کسانی که فکر می کنند قبلاً Java Core را به خوبی می شناسند، اما هیچ ایده ای در مورد عبارات لامبدا در جاوا ندارند. یا شاید قبلاً چیزی در مورد لامبدا شنیده باشید، اما بدون جزئیات.
  • برای کسانی که درک درستی از عبارات لامبدا دارند، اما هنوز از استفاده از آنها می ترسند و غیرعادی هستند.
اگر در یکی از این دسته بندی ها قرار نمی گیرید، ممکن است این مقاله را خسته کننده، نادرست و به طور کلی «خوب» بدانید. در این مورد، یا با خیال راحت از کنار آن عبور کنید، یا، اگر به این موضوع مسلط هستید، در نظرات پیشنهاد دهید که چگونه می توانم مقاله را بهبود یا تکمیل کنم. این مطالب هیچ ارزش آکادمیکی ندارد، چه جدیدتر. بلکه برعکس: در آن سعی خواهم کرد چیزهای پیچیده (برای برخی) را به ساده ترین شکل ممکن توصیف کنم. من از درخواستی برای توضیح استریم api الهام گرفتم. من در مورد آن فکر کردم و تصمیم گرفتم که بدون درک عبارات لامبدا، برخی از مثال های من در مورد "جریان ها" نامفهوم خواهد بود. پس بیایید با لامبدا شروع کنیم. محبوب در مورد عبارات لامبدا در جاوا.  همراه با مثال و کار.  قسمت 1 - 1برای درک این مقاله چه دانشی لازم است:
  1. درک برنامه نویسی شی گرا (از این پس OOP نامیده می شود)، یعنی:
    • آگاهی از چیستی کلاس ها و اشیاء، تفاوت بین آنها چیست.
    • دانستن اینکه رابط ها چیست، چگونه با کلاس ها تفاوت دارند، چه ارتباطی بین آنها وجود دارد (رابط ها و کلاس ها).
    • دانستن اینکه یک متد چیست، چگونه آن را فراخوانی کنیم، متد انتزاعی چیست (یا روشی بدون پیاده سازی)، پارامترها/آگومان های یک متد چیست، چگونه آنها را به آنجا منتقل کنیم.
    • اصلاح کننده های دسترسی، روش ها/متغیرهای استاتیک، روش ها/متغیرهای نهایی؛
    • وراثت (کلاس ها، رابط ها، وراثت چندگانه رابط ها).
  2. دانش Java Core: ژنریک ها، مجموعه ها (لیست ها)، موضوعات.
خوب، بیایید شروع کنیم.

کمی تاریخچه

عبارات لامبدا از برنامه نویسی تابعی به جاوا آمدند و در آنجا از ریاضیات. در اواسط قرن بیستم در آمریکا، یک کلیسای آلونزو در دانشگاه پرینستون کار می کرد که به ریاضیات و انواع انتزاعات علاقه زیادی داشت. این آلونزو چرچ بود که حساب لامبدا را ارائه کرد، که در ابتدا مجموعه ای از ایده های انتزاعی بود و ربطی به برنامه نویسی نداشت. در همان زمان، ریاضیدانانی مانند آلن تورینگ و جان فون نویمان در همان دانشگاه پرینستون کار می کردند. همه چیز جمع شد: چرچ با سیستم حساب دیفرانسیل لامبدا آمد، تورینگ ماشین محاسباتی انتزاعی خود را توسعه داد که اکنون به عنوان «ماشین تورینگ» شناخته می شود. خوب، فون نویمان نموداری از معماری کامپیوترها را پیشنهاد کرد که اساس کامپیوترهای مدرن را تشکیل داد (و اکنون "معماری فون نویمان" نامیده می شود). در آن زمان، ایده های آلونزو چرچ به اندازه کارهای همکارانش شهرت پیدا نکرد (به استثنای رشته ریاضیات "خالص"). با این حال، کمی بعد، جان مک کارتی خاص (همچنین فارغ التحصیل دانشگاه پرینستون، در زمان داستان - کارمند موسسه فناوری ماساچوست) به ایده های چرچ علاقه مند شد. بر اساس آنها، او در سال 1958 اولین زبان برنامه نویسی کاربردی، Lisp را ایجاد کرد. و 58 سال بعد، ایده های برنامه نویسی تابعی به عنوان شماره 8 به جاوا لو رفت. حتی 70 سال هم نگذشته است... در واقع، این طولانی ترین زمان برای به کارگیری یک ایده ریاضی در عمل نیست.

اصل

یک عبارت لامبدا چنین تابعی است. شما می توانید این را به عنوان یک متد معمولی در جاوا در نظر بگیرید، تنها تفاوت این است که می توان آن را به عنوان یک آرگومان به متدهای دیگر منتقل کرد. بله، انتقال نه تنها اعداد، رشته ها و گربه ها به روش ها، بلکه روش های دیگر نیز ممکن شده است! چه زمانی ممکن است به این نیاز داشته باشیم؟ به عنوان مثال، اگر بخواهیم مقداری callback ارسال کنیم. ما به متدی که فراخوانی می کنیم نیاز داریم تا بتوانیم متد دیگری را که به آن پاس می دهیم فراخوانی کنیم. یعنی در برخی موارد فرصتی برای انتقال یک تماس و در موارد دیگر وجود دارد. و روش ما، که تماس های ما را می پذیرد، آنها را فراخوانی می کند. یک مثال ساده مرتب سازی است. بیایید بگوییم که ما نوعی مرتب‌سازی پیچیده می‌نویسیم که چیزی شبیه به این است:
public void mySuperSort() {
    // ... do something here
    if(compare(obj1, obj2) > 0)
    // ... and here we do something
}
در جایی که ifمتد را صدا می زنیم compare()، دو شی را که با هم مقایسه می کنیم به آنجا منتقل می کنیم و می خواهیم بفهمیم کدام یک از این اشیاء "بزرگتر" است. ما آن را که "بیشتر" است را قبل از "کوچکتر" قرار می دهیم. من "بیشتر" را در نقل قول نوشتم زیرا ما در حال نوشتن یک روش جهانی هستیم که می تواند نه تنها به ترتیب صعودی بلکه به ترتیب نزولی مرتب شود (در این مورد "بیشتر" جسمی است که اساساً کوچکتر است و بالعکس) . برای تنظیم قانون دقیقاً چگونه می‌خواهیم مرتب کنیم، باید به نحوی آن را به ما منتقل کنیم mySuperSort(). در این صورت، ما قادر خواهیم بود تا زمانی که متد خود را فراخوانی می‌کند، به نوعی «کنترل» کنیم. البته می توانید دو روش جداگانه mySuperSortAsc()برای mySuperSortDesc()مرتب سازی به ترتیب صعودی و نزولی بنویسید. یا مقداری پارامتر را در داخل متد ارسال کنید (مثلا booleanif true, مرتب سازی به ترتیب صعودی و اگر falseبه ترتیب نزولی). اما اگر بخواهیم نه برخی از ساختارهای ساده، بلکه برای مثال، فهرستی از آرایه های رشته ای را مرتب کنیم، چه؟ روش ما چگونه می mySuperSort()داند که چگونه این آرایه های رشته ای را مرتب کند؟ به اندازه؟ بر اساس طول کل کلمات؟ شاید بر اساس حروف الفبا، بسته به ردیف اول آرایه؟ اما اگر در برخی موارد، لازم باشد فهرستی از آرایه‌ها را بر اساس اندازه آرایه و در مورد دیگر، بر اساس طول کل کلمات آرایه مرتب کنیم، چه؟ فکر می کنم قبلاً در مورد مقایسه کننده ها شنیده اید و اینکه در چنین مواردی ما به سادگی یک شی مقایسه کننده را به روش مرتب سازی خود منتقل می کنیم، که در آن قوانینی را که می خواهیم بر اساس آنها مرتب سازی کنیم را شرح می دهیم. از آنجایی که روش استاندارد sort()بر اساس همان اصل اجرا می شود، mySuperSort()در مثال ها از روش استاندارد استفاده خواهم کرد sort().
String[] array1 = {"Mother", "soap", "frame"};
String[] array2 = {"I", "Very", "I love", "java"};
String[] array3 = {"world", "work", "May"};

List<String[]> arrays = new ArrayList<>();
arrays.add(array1);
arrays.add(array2);
arrays.add(array3);

Comparator<String[]> sortByLength = new Comparator<String[]>() {
    @Override
    public int compare(String[] o1, String[] o2) {
        return o1.length - o2.length;
    }
};

Comparator<String[]> sortByWordsLength = new Comparator<String[]>() {
    @Override
    public int compare(String[] o1, String[] o2) {
        int length1 = 0;
        int length2 = 0;
        for (String s : o1) {
            length1 += s.length();
        }
        for (String s : o2) {
            length2 += s.length();
        }
        return length1 - length2;
    }
};

arrays.sort(sortByLength);
نتیجه:
  1. مامان قاب را شست
  2. صلح کارگر ممکن است
  3. من واقعاً جاوا را دوست دارم
در اینجا آرایه ها بر اساس تعداد کلمات هر آرایه مرتب می شوند. آرایه ای با کلمات کمتر "کوچکتر" در نظر گرفته می شود. به همین دلیل در ابتدا می آید. جایی که کلمات بیشتری وجود دارد "بیشتر" در نظر گرفته می شود و در پایان به پایان می رسد. اگر sort()مقایسه کننده دیگری را به متد ارسال کنیم (sortByWordsLength)، نتیجه متفاوت خواهد بود:
  1. صلح کارگر ممکن است
  2. مامان قاب را شست
  3. من واقعاً جاوا را دوست دارم
اکنون آرایه ها بر اساس تعداد کل حروف در کلمات چنین آرایه ای مرتب می شوند. در مورد اول 10 حرف، در دومی 12 و در سومی 15 حرف وجود دارد. اگر فقط از یک مقایسه کننده استفاده کنیم، نمی توانیم یک متغیر جداگانه برای آن ایجاد کنیم، بلکه به سادگی یک شی از یک کلاس ناشناس درست در قسمت زمان فراخوانی روش sort(). مانند آن:
String[] array1 = {"Mother", "soap", "frame"};
String[] array2 = {"I", "Very", "I love", "java"};
String[] array3 = {"world", "work", "May"};

List<String[]> arrays = new ArrayList<>();
arrays.add(array1);
arrays.add(array2);
arrays.add(array3);

arrays.sort(new Comparator<String[]>() {
    @Override
    public int compare(String[] o1, String[] o2) {
        return o1.length - o2.length;
    }
});
نتیجه مانند حالت اول خواهد بود. وظیفه 1 . این مثال را دوباره بنویسید تا آرایه ها را نه به ترتیب افزایشی تعداد کلمات آرایه، بلکه به ترتیب نزولی مرتب کند. ما از قبل همه اینها را می دانیم. ما می دانیم که چگونه اشیاء را به متدها منتقل کنیم، بسته به آنچه در لحظه نیاز داریم می توانیم این یا آن شی را به یک متد ارسال کنیم و در داخل متدی که چنین شیئی را ارسال می کنیم، متدی که پیاده سازی را برای آن نوشته ایم نامیده می شود. . این سوال مطرح می شود: عبارات لامبدا چه ربطی به آن دارند؟ با توجه به اینکه لامبدا شی ای است که دقیقاً یک روش را شامل می شود. مانند یک شیء متد است. روشی که در یک شی پیچیده شده است. آنها فقط یک نحو کمی غیر معمول دارند (اما بعداً در مورد آن توضیح خواهیم داد). بیایید نگاهی دیگر به این مدخل بیندازیم
arrays.sort(new Comparator<String[]>() {
    @Override
    public int compare(String[] o1, String[] o2) {
        return o1.length - o2.length;
    }
});
در اینجا لیست خود را می گیریم arraysو متد آن را فرا می خوانیم sort()، جایی که یک شی مقایسه کننده را با یک متد واحد ارسال می کنیم compare()(برای ما مهم نیست نام آن چیست، زیرا تنها شیء موجود در این شی است، ما آن را از دست نمی دهیم). این روش دو پارامتر دارد که در ادامه با آنها کار می کنیم. اگر در IntelliJ IDEA کار می کنید ، احتمالاً دیده اید که چگونه این کد را به شما ارائه می دهد تا به میزان قابل توجهی کوتاه شود:
arrays.sort((o1, o2) -> o1.length - o2.length);
به این ترتیب شش خط به یک خط کوتاه تبدیل شد. 6 خط در یک خط کوتاه بازنویسی شد. چیزی ناپدید شده است، اما من تضمین می کنم که هیچ چیز مهمی ناپدید نشده است، و این کد دقیقاً مانند یک کلاس ناشناس کار می کند. وظیفه 2 . نحوه بازنویسی راه حل مسئله 1 با استفاده از لامبدا را بیابید (به عنوان آخرین راه، از IntelliJ IDEA بخواهید کلاس ناشناس شما را به لامبدا تبدیل کند).

بیایید در مورد رابط ها صحبت کنیم

اساساً یک رابط فقط فهرستی از روش های انتزاعی است. وقتی یک کلاس ایجاد می کنیم و می گوییم که نوعی رابط را پیاده سازی می کند، باید در کلاس خود پیاده سازی متدهایی را که در اینترفیس لیست شده است بنویسیم (یا به عنوان آخرین چاره آن را ننویسیم، بلکه کلاس را انتزاعی کنیم. ). اینترفیس هایی با روش های مختلف وجود دارد (به عنوان مثال List)، اینترفیس هایی با تنها یک متد (مثلاً همان مقایسه کننده یا Runnable) وجود دارد. اینترفیس هایی بدون یک روش وجود دارند (به اصطلاح رابط های نشانگر، برای مثال Serializable). به آن دسته از اینترفیس هایی که تنها یک روش دارند، اینترفیس های تابعی نیز گفته می شود . در جاوا 8 حتی با یک حاشیه نویسی @FunctionalInterface مشخص شده اند . این رابط با یک روش واحد است که برای استفاده توسط عبارات لامبدا مناسب است. همانطور که در بالا گفتم، عبارت لامبدا روشی است که در یک شی پیچیده شده است. و وقتی چنین شیئی را در جایی رد می کنیم، در واقع این روش واحد را پاس می کنیم. معلوم می شود که برای ما مهم نیست که این روش چه نامی دارد. تنها چیزی که برای ما مهم است پارامترهایی است که این روش می گیرد و در واقع خود کد متد است. یک عبارت لامبدا، اساساً است. پیاده سازی یک رابط کاربردی جایی که ما یک رابط را با یک متد می بینیم، به این معنی است که می توانیم چنین کلاس ناشناس را با استفاده از یک لامبدا بازنویسی کنیم. اگر اینترفیس بیش از یک متد داشته باشد، عبارت lambda مناسب ما نخواهد بود و از یک کلاس ناشناس یا حتی یک کلاس معمولی استفاده خواهیم کرد. وقت آن است که به لامبداها بپردازیم. :)

نحو

نحو کلی چیزی شبیه به این است:
(параметры) -> {тело метода}
یعنی پرانتزها، داخل آنها پارامترهای روش است، یک "فلش" (اینها دو کاراکتر پشت سر هم هستند: منهای و بزرگتر)، پس از آن بدنه روش مانند همیشه در پرانتزهای فرفری قرار دارد. پارامترها با پارامترهای مشخص شده در رابط هنگام توصیف روش مطابقت دارند. اگر نوع متغیرها را بتوان به وضوح توسط کامپایلر تعریف کرد (در مورد ما، مطمئناً مشخص است که ما با آرایه‌های رشته‌ها کار می‌کنیم، زیرا Listدقیقاً توسط آرایه‌های رشته‌ها تایپ می‌شود)، نوع متغیرها String[]لازم نیست. نوشته شود.
اگر مطمئن نیستید، نوع آن را مشخص کنید و IDEA در صورت عدم نیاز، آن را به رنگ خاکستری برجسته می کند.
برای مثال می‌توانید در آموزش Oracle بیشتر بخوانید . به این "تایپ هدف" می گویند . شما می توانید هر نامی را به متغیرها بدهید، نه لزوماً نام هایی که در رابط مشخص شده اند. اگر هیچ پارامتری وجود ندارد، فقط پرانتز کنید. اگر فقط یک پارامتر وجود دارد، فقط نام متغیر بدون پرانتز است. اکنون در مورد بدنه خود عبارت لامبدا، پارامترها را مرتب کردیم. در داخل بریس های فرفری، کد را مانند یک روش معمولی بنویسید. اگر کل کد شما فقط از یک خط تشکیل شده است، اصلاً مجبور نیستید پرانتزهای فرفری بنویسید (مثل if ها و حلقه ها). اگر لامبدا شما چیزی را برمی گرداند، اما بدنه آن از یک خط تشکیل شده است، returnنوشتن آن اصلا ضروری نیست. اما اگر بریس های فرفری دارید، مانند روش معمول، باید به صراحت بنویسید return.

مثال ها

مثال 1.
() -> {}
ساده ترین گزینه. و بی معنی ترینش :).چون هیچ کاری نمیکنه. مثال 2.
() -> ""
همچنین یک گزینه جالب. هیچ چیز را نمی پذیرد و یک رشته خالی را برمی گرداند ( returnبه عنوان غیر ضروری حذف شده است). همان، اما با return:
() -> {
    return "";
}
مثال 3. سلام جهان با استفاده از لامبدا
() -> System.out.println("Hello world!")
چیزی دریافت نمی‌کند، چیزی را برمی‌گرداند (نمی‌توانیم returnقبل از فراخوانی قرار دهیم System.out.println()، زیرا نوع برگشتی در متد println() — void)، به سادگی یک کتیبه را روی صفحه نمایش می‌دهد. ایده‌آل برای پیاده‌سازی یک رابط Runnable. همین مثال کامل‌تر است:
public class Main {
    public static void main(String[] args) {
        new Thread(() -> System.out.println("Hello world!")).start();
    }
}
یا مثل این:
public class Main {
    public static void main(String[] args) {
        Thread t = new Thread(() -> System.out.println("Hello world!"));
        t.start();
    }
}
یا حتی می توانیم عبارت lambda را به عنوان یک شی از نوع ذخیره کنیم Runnableو سپس آن را به سازنده ارسال کنیم thread’а:
public class Main {
    public static void main(String[] args) {
        Runnable runnable = () -> System.out.println("Hello world!");
        Thread t = new Thread(runnable);
        t.start();
    }
}
بیایید نگاهی دقیق تر به لحظه ذخیره یک عبارت لامبدا در یک متغیر بیندازیم. رابط Runnableبه ما می گوید که اشیاء آن باید یک متد داشته باشند public void run(). با توجه به رابط، متد run چیزی را به عنوان پارامتر نمی پذیرد. و چیزی را بر نمی گرداند (void). بنابراین، هنگام نوشتن به این صورت، یک شی با روشی ساخته می شود که چیزی را نمی پذیرد یا برمی گرداند. که کاملاً با روش موجود run()در رابط سازگار است Runnable. به همین دلیل است که ما توانستیم این عبارت لامبدا را در متغیری مانند Runnable. مثال 4
() -> 42
باز هم چیزی را قبول نمی کند، اما عدد 42 را برمی گرداند. این عبارت لامبدا را می توان در متغیری از نوع قرار داد Callable، زیرا این رابط فقط یک متد را تعریف می کند که چیزی شبیه به این است:
V call(),
Vنوع مقدار بازگشتی کجاست (در مورد ما ) int. بر این اساس، ما می توانیم چنین عبارت لامبدا را به صورت زیر ذخیره کنیم:
Callable<Integer> c = () -> 42;
مثال 5. لامبدا در چند خط
() -> {
    String[] helloWorld = {"Hello", "world!"};
    System.out.println(helloWorld[0]);
    System.out.println(helloWorld[1]);
}
باز هم، این یک عبارت لامبدا بدون پارامتر و نوع بازگشت آن است void(چون وجود ندارد return). مثال 6
x -> x
در اینجا ما چیزی را در یک متغیر می گیریم хو آن را برمی گردانیم. لطفاً توجه داشته باشید که اگر فقط یک پارامتر پذیرفته شود، پرانتزهای اطراف آن نیازی به نوشتن ندارند. همان، اما با پرانتز:
(x) -> x
و در اینجا گزینه ای با یک صریح وجود دارد return:
x -> {
    return x;
}
یا مانند این، با پرانتز و return:
(x) -> {
    return x;
}
یا با اشاره صریح از نوع (و بر این اساس، با پرانتز):
(int x) -> x
مثال 7
x -> ++x
ما آن را می پذیریم хو آن را پس می دهیم، اما برای 1بیشتر. شما همچنین می توانید آن را به این صورت بازنویسی کنید:
x -> x + 1
در هر دو مورد، ما پرانتز را در اطراف پارامتر، بدنه متد و کلمه نشان نمی‌دهیم return، زیرا این کار ضروری نیست. گزینه های دارای براکت و بازگشت در مثال 6 توضیح داده شده است. مثال 8
(x, y) -> x % y
مقداری را می پذیریم хو уباقیمانده تقسیم xرا برمی گردانیم y. پرانتز در اطراف پارامترها از قبل در اینجا لازم است. آنها فقط زمانی اختیاری هستند که فقط یک پارامتر وجود داشته باشد. مانند این با اشاره صریح انواع:
(double x, int y) -> x % y
مثال 9
(Cat cat, String name, int age) -> {
    cat.setName(name);
    cat.setAge(age);
}
ما یک شی Cat، یک رشته با نام و یک سن صحیح را می پذیریم. در خود روش، نام و سن پاس شده را روی Cat قرار می دهیم. از آنجایی که متغیر catما یک نوع مرجع است، شی Cat خارج از عبارت لامبدا تغییر خواهد کرد (نام و سن ارسال شده در داخل آن را دریافت خواهد کرد). یک نسخه کمی پیچیده تر که از لامبدا مشابه استفاده می کند:
public class Main {
    public static void main(String[] args) {
        // create a cat and print to the screen to make sure it's "blank"
        Cat myCat = new Cat();
        System.out.println(myCat);

        // create lambda
        Settable<Cat> s = (obj, name, age) -> {
            obj.setName(name);
            obj.setAge(age);
        };

        // call the method, to which we pass the cat and the lambda
        changeEntity(myCat, s);
        // display on the screen and see that the state of the cat has changed (has a name and age)
        System.out.println(myCat);
    }

    private static <T extends WithNameAndAge>  void changeEntity(T entity, Settable<T> s) {
        s.set(entity, "Murzik", 3);
    }
}

interface WithNameAndAge {
    void setName(String name);
    void setAge(int age);
}

interface Settable<C extends WithNameAndAge> {
    void set(C entity, String name, int age);
}

class Cat implements WithNameAndAge {
    private String name;
    private int age;

    @Override
    public void setName(String name) {
        this.name = name;
    }

    @Override
    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Cat{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
نتیجه: Cat{name='null', age=0} Cat{name='Murzik', age=3} همانطور که می بینید، در ابتدا شی Cat یک حالت داشت، اما پس از استفاده از عبارت لامبدا، حالت تغییر کرد. . عبارات لامبدا به خوبی با ژنریک کار می کنند. و اگر نیاز به ایجاد یک کلاس داشته باشیم Dog، به عنوان مثال، که آن را نیز پیاده سازی کند WithNameAndAge، در متد main()می توانیم همان عملیات را با Dog انجام دهیم، بدون اینکه اصلاً خود عبارت لامبدا را تغییر دهیم. وظیفه 3 . یک رابط کاربردی با متدی بنویسید که عددی را می گیرد و مقدار بولی را برمی گرداند. یک پیاده سازی از چنین رابطی را به شکل یک عبارت لامبدا بنویسید که trueاگر عدد ارسال شده بدون باقیمانده بر 13 بخش پذیر باشد ، برمی گردد . یک رابط کاربردی با متدی بنویسید که دو رشته را بگیرد و همان رشته را برگرداند. پیاده سازی چنین رابطی را به شکل لامبدا بنویسید که طولانی ترین رشته را برمی گرداند. وظیفه 5 . یک رابط کاربردی با روشی بنویسید که سه عدد کسری را بپذیرد: a, bو cهمان عدد کسری را برمی گرداند. پیاده سازی چنین رابطی را به شکل یک عبارت لامبدا بنویسید که یک تفکیک کننده را برمی گرداند. چه کسی فراموش کرد، D = b^2 - 4ac . وظیفه 6 . با استفاده از رابط عملکردی از کار 5، یک عبارت لامبدا بنویسید که نتیجه عملیات را برمی گرداند a * b^c. محبوب در مورد عبارات لامبدا در جاوا. همراه با مثال و کار. قسمت 2.
نظرات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION