JavaRush /Java blogi /Random-UZ /Java'da lambda ifodalari haqida mashhur. Misollar va tops...

Java'da lambda ifodalari haqida mashhur. Misollar va topshiriqlar bilan. 1-qism

Guruhda nashr etilgan
Bu maqola kim uchun?
  • Java Core-ni allaqachon yaxshi bilaman deb o'ylaydiganlar uchun, lekin Java-da lambda ifodalari haqida hech qanday tasavvurga ega emaslar. Yoki, ehtimol, siz allaqachon lambdalar haqida biror narsa eshitgansiz, ammo tafsilotlarsiz.
  • lambda iboralari haqida bir oz tushunchaga ega bo'lganlar uchun, lekin ularni ishlatishdan qo'rqadigan va g'ayrioddiy.
Agar siz ushbu toifalardan biriga kirmasangiz, siz ushbu maqolani zerikarli, noto'g'ri va umuman "salqin emas" deb topishingiz mumkin. Bunday holda, bemalol o'tib keting yoki agar siz mavzuni yaxshi bilsangiz, sharhlarda maqolani qanday yaxshilash yoki to'ldirishni taklif qiling. Material hech qanday ilmiy ahamiyatga ega emas, yangilik kamroq. Aksincha, aksincha: unda men murakkab (ba'zilar uchun) narsalarni iloji boricha sodda tasvirlashga harakat qilaman. Men oqim api ni tushuntirish so'rovidan ilhomlantirdim. Men bu haqda o'yladim va lambda iboralarini tushunmasdan, "oqimlar" haqidagi ba'zi misollarim tushunarsiz bo'lishiga qaror qildim. Shunday qilib, keling, lambdalardan boshlaylik. Java'da lambda ifodalari haqida mashhur.  Misollar va topshiriqlar bilan.  1-1-qismUshbu maqolani tushunish uchun qanday bilim kerak:
  1. Ob'ektga yo'naltirilgan dasturlashni tushunish (keyingi o'rinlarda OOP deb yuritiladi), xususan:
    • sinflar va ob'ektlar nima ekanligini bilish, ular o'rtasidagi farq nima;
    • interfeyslar nima ekanligini, ular sinflardan qanday farq qilishini, ular o'rtasidagi bog'liqlikni bilish (interfeyslar va sinflar);
    • usul nima ekanligini, uni qanday chaqirish kerakligini, mavhum usul (yoki amalga oshirilmagan usul) nima ekanligini, usulning parametrlari/argumentlari nima ekanligini, ularni u erga qanday o'tkazishni bilish;
    • kirish modifikatorlari, statik usullar/o'zgaruvchilar, yakuniy usullar/o'zgaruvchilar;
    • meros (sinflar, interfeyslar, interfeyslarning ko'p merosi).
  2. Java Core haqida ma'lumot: generiklar, to'plamlar (ro'yxatlar), mavzular.
Xo'sh, keling, boshlaylik.

Bir oz tarix

Lambda ifodalari Java-ga funktsional dasturlashdan, u erda esa matematikadan kelgan. 20-asrning o'rtalarida Amerikada Prinston universitetida matematikani va barcha turdagi abstraktsiyalarni juda yaxshi ko'radigan Alonzo cherkovi ishlagan. Aynan Alonzo cherkovi lambda hisobini o'ylab topdi, bu dastlab ba'zi mavhum g'oyalar to'plami bo'lib, dasturlash bilan hech qanday aloqasi yo'q edi. Shu bilan birga, Alan Turing va Jon fon Neyman kabi matematiklar o'sha Prinston universitetida ishlagan. Hamma narsa birlashdi: Cherch lambda hisoblash tizimini yaratdi, Tyuring o'zining mavhum hisoblash mashinasini yaratdi, hozirda "Tyuring mashinasi" deb nomlanadi. Xo'sh, fon Neumann zamonaviy kompyuterlarning asosini tashkil etuvchi kompyuterlar arxitekturasining diagrammasini taklif qildi (u hozir "von Neumann arxitekturasi" deb ataladi). O'sha paytda Alonzo Cherkovning g'oyalari uning hamkasblari ("sof" matematika sohasi bundan mustasno) ishi kabi shuhrat qozonmadi. Biroq, bir oz vaqt o'tgach, Jon Makkarti (shuningdek, Prinston universiteti bitiruvchisi, voqea sodir bo'lgan paytda - Massachusets texnologiya instituti xodimi) cherkov g'oyalari bilan qiziqib qoldi. Ularga asoslanib, 1958 yilda u Lisp nomli birinchi funksional dasturlash tilini yaratdi. Oradan 58 yil o'tib esa funksional dasturlash g'oyalari Java-ga 8-raqam sifatida kirib keldi. Oradan 70 yil ham o'tmadi... Aslida, bu matematik g'oyani amaliyotda qo'llash uchun eng uzoq vaqt emas.

mohiyati

Lambda ifodasi shunday funktsiyadir. Buni Java'da odatiy usul deb o'ylashingiz mumkin, yagona farq shundaki, uni boshqa usullarga argument sifatida o'tkazish mumkin. Ha, usullarga nafaqat raqamlar, satrlar va mushuklarni, balki boshqa usullarni ham o'tkazish mumkin bo'ldi! Bu bizga qachon kerak bo'lishi mumkin? Misol uchun, agar biz qayta qo'ng'iroq qilishni xohlasak. Biz unga o'tadigan boshqa usulni chaqirishimiz uchun biz chaqiradigan usulga muhtojmiz. Ya'ni, biz ba'zi hollarda bitta qayta qo'ng'iroqni, boshqalarida esa boshqasini uzatish imkoniyatiga ega bo'lishimiz uchun. Va bizning qayta qo'ng'iroqlarimizni qabul qiladigan usulimiz ularni chaqiradi. Oddiy misol - tartiblash. Aytaylik, biz shunga o'xshash qandaydir murakkab tartiblash yozamiz:
public void mySuperSort() {
    // ... do something here
    if(compare(obj1, obj2) > 0)
    // ... and here we do something
}
Qaerda ifusulni chaqiramiz compare(), u yerga solishtiradigan ikkita ob'ektni o'tkazamiz va biz ushbu ob'ektlarning qaysi biri "kattaroq" ekanligini aniqlamoqchimiz. Biz "ko'proq" bo'lganini "kichikroq" dan oldin qo'yamiz. Men qo'shtirnoq ichida "ko'proq" deb yozdim, chunki biz nafaqat o'sish, balki kamayish tartibida ham tartiblash imkoniyatiga ega bo'lgan universal usulni yozmoqdamiz (bu holda "ko'proq" ob'ekt aslida kichikroq bo'ladi va aksincha). . Aynan qanday saralashni istayotganimiz qoidasini o'rnatish uchun uni qandaydir tarzda o'zimizga o'tkazishimiz kerak mySuperSort(). Bunday holda, biz o'z usulimizni chaqirish paytida qandaydir tarzda "nazorat qila olamiz". Albatta, o'sish va kamayish tartibida tartiblashning mySuperSortAsc()ikkita alohida usulini yozishingiz mumkin. mySuperSortDesc()Yoki usul ichida ba'zi parametrlarni o'tkazing (masalan, booleanagar true, o'sish tartibida va agar falsekamayish tartibida tartiblang). Agar biz oddiy tuzilmani emas, balki, masalan, qatorlar qatorlari ro'yxatini saralashni istasak nima bo'ladi? Bizning usulimiz mySuperSort()ushbu satr massivlarini qanday saralashni biladi? O'lchamga? So'zlarning umumiy uzunligi bo'yicha? Ehtimol, massivdagi birinchi qatorga qarab, alifbo tartibida? Ammo ba'zi hollarda massivlar ro'yxatini massivning o'lchamiga ko'ra, boshqa holatda esa massivdagi so'zlarning umumiy uzunligi bo'yicha tartiblashimiz kerak bo'lsa-chi? O'ylaymanki, siz taqqoslashlar haqida allaqachon eshitgansiz va bunday hollarda biz shunchaki solishtiruvchi ob'ektni saralash usulimizga o'tkazamiz, unda biz saralashni xohlagan qoidalarni tasvirlaymiz. Standart usul sort()xuddi shu printsip bo'yicha amalga oshirilganligi sababli, mySuperSort()misollarda men standartdan foydalanaman 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);
Natija:
  1. onam ramkani yuvdi
  2. tinchlik Mehnat may
  3. Men java-ni juda yaxshi ko'raman
Bu erda massivlar har bir massivdagi so'zlar soni bo'yicha tartiblanadi. Kamroq so'zli massiv "kichikroq" hisoblanadi. Shuning uchun u boshida keladi. Ko'proq so'z bo'lgan "ko'proq" deb hisoblanadi va oxirida tugaydi. Agar metodga sort()boshqa taqqoslagichni o'tkazsak (sortByWordsLength), natija boshqacha bo'ladi:
  1. tinchlik Mehnat may
  2. onam ramkani yuvdi
  3. Men java-ni juda yaxshi ko'raman
Endi massivlar shunday massiv so'zlaridagi harflarning umumiy soni bo'yicha tartiblanadi. Birinchi holda 10 ta, ikkinchisida 12 ta, uchinchisida 15 ta harf mavjud. Agar biz faqat bitta taqqoslash vositasidan foydalansak, u uchun alohida o'zgaruvchi yarata olmaymiz, shunchaki anonim sinf ob'ektini yaratamiz. usulni chaqirish vaqti sort(). Shunga o'xshash:
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;
    }
});
Natija birinchi holatda bo'lgani kabi bo'ladi. 1-topshiriq . Ushbu misolni shunday yozingki, u massivlarni massivdagi so'zlar sonining o'sish tartibida emas, balki kamayish tartibida tartiblaydi. Bularning barchasini biz allaqachon bilamiz. Biz ob'ektlarni usullarga o'tkazishni bilamiz, biz u yoki bu ob'ektni hozirgi vaqtda kerakli narsaga qarab usulga o'tkazishimiz mumkin va biz bunday ob'ektni o'tkazadigan usul ichida biz amalga oshirishni yozgan usul deyiladi. . Savol tug'iladi: lambda iboralarining bunga qanday aloqasi bor? Lambda aynan bitta usulni o'z ichiga olgan ob'ekt ekanligini hisobga olsak. Bu usul ob'ektiga o'xshaydi. Ob'ektga o'ralgan usul. Ular biroz noodatiy sintaksisga ega (lekin bu haqda keyinroq). Keling, ushbu yozuvni yana bir bor ko'rib chiqaylik
arrays.sort(new Comparator<String[]>() {
    @Override
    public int compare(String[] o1, String[] o2) {
        return o1.length - o2.length;
    }
});
Bu erda biz ro'yxatimizni olamiz arraysva uning usulini chaqiramiz sort(), bu erda biz taqqoslash ob'ektini bitta usul bilan o'tkazamiz compare()(u nima deb nomlangani biz uchun muhim emas, chunki u ushbu ob'ektda yagona, biz uni o'tkazib yubormaymiz). Ushbu usul ikkita parametrni oladi, biz ular bilan keyingi ishlaymiz. Agar siz IntelliJ IDEA da ishlasangiz , ehtimol u sizga ushbu kodni sezilarli darajada qisqartirish uchun qanday taklif qilayotganini ko'rgansiz:
arrays.sort((o1, o2) -> o1.length - o2.length);
Shunday qilib, olti satr bitta qisqa chiziqqa aylandi. 6 satr bitta qisqa chiziqqa qayta yozildi. Biror narsa g'oyib bo'ldi, lekin men hech qanday muhim narsa yo'qolmaganiga kafolat beraman va bu kod anonim sinf bilan bir xil ishlaydi. Vazifa 2 . Lambdalar yordamida 1-muammoning yechimini qanday qayta yozishni aniqlang (oxirgi chora sifatida IntelliJ IDEA- dan anonim sinfingizni lambdaga aylantirishni so'rang).

Keling, interfeyslar haqida gapiraylik

Asosan, interfeys faqat mavhum usullar ro'yxatidir. Biz sinf yaratganimizda va u qandaydir interfeysni amalga oshiradi desak, biz o'z sinfimizga interfeysda sanab o'tilgan usullarni amalga oshirishni yozishimiz kerak (yoki oxirgi chora sifatida uni yozmasdan, balki sinfni abstrakt qilishimiz kerak. ). Turli xil usullarga ega interfeyslar mavjud (masalan, List), faqat bitta usulga ega interfeyslar mavjud (masalan, bir xil Comparator yoki Runnable). Bitta usulsiz interfeyslar mavjud (marker interfeyslari deb ataladi, masalan, Serializable). Faqat bitta usulga ega bo'lgan interfeyslar funktsional interfeyslar deb ham ataladi . Java 8 da ular hatto maxsus @FunctionalInterface izohi bilan belgilangan . Bu lambda ifodalari bilan foydalanish uchun mos bo'lgan yagona usul bilan interfeyslar. Yuqorida aytganimdek, lambda ifodasi ob'ektga o'ralgan usuldir. Va bunday ob'ektni biron bir joyga o'tkazganimizda, biz, aslida, bitta usuldan o'tamiz. Ma'lum bo'lishicha, biz uchun bu usul qanday nomlanishi muhim emas. Biz uchun muhim bo'lgan narsa bu usul qabul qiladigan parametrlar va aslida usul kodining o'zi. Lambda ifodasi, aslida. funktsional interfeysni amalga oshirish. Bir usul bilan interfeysni ko'rsak, bu biz lambda yordamida bunday anonim sinfni qayta yozishimiz mumkinligini anglatadi. Agar interfeys bir nechta/kamroq usulga ega bo'lsa, u holda lambda ifodasi bizga mos kelmaydi va biz anonim sinfdan yoki hatto oddiy sinfdan foydalanamiz. Lambdalarni qazish vaqti keldi. :)

Sintaksis

Umumiy sintaksis quyidagicha:
(параметры) -> {тело метода}
Ya'ni, qavslar, ularning ichida usul parametrlari, "o'q" (bular ketma-ket ikkita belgi: minus va kattaroq), shundan so'ng usulning tanasi har doimgidek jingalak qavslarda joylashgan. Parametrlar usulni tavsiflashda interfeysda ko'rsatilganlarga mos keladi. Agar o'zgaruvchilar turini kompilyator aniq belgilashi mumkin bo'lsa (bizning holatda, biz satrlar massivlari bilan ishlayotganimiz aniq ma'lum, chunki u Listsatrlar massivlari bilan aniq terilgan), u holda o'zgaruvchilar turi String[]kerak emas . yozilsin.
Agar ishonchingiz komil bo'lmasa, turini belgilang, agar kerak bo'lmasa, IDEA uni kulrang rangda ajratib ko'rsatadi.
Siz , masalan, Oracle qo'llanmasida ko'proq o'qishingiz mumkin . Bu "maqsadli yozish" deb ataladi . O'zgaruvchilarga har qanday nom berishingiz mumkin, interfeysda ko'rsatilganlar shart emas. Parametrlar bo'lmasa, faqat qavslar. Agar faqat bitta parametr bo'lsa, faqat qavssiz o'zgaruvchining nomi. Biz parametrlarni saralab oldik, endi lambda ifodasining o'zi haqida. Jingalak qavslar ichida oddiy usuldagi kabi kodni yozing. Agar butun kodingiz faqat bitta satrdan iborat bo'lsa, siz umuman jingalak qavslar yozishingiz shart emas (ifs va looplar kabi). Agar lambda biror narsani qaytarsa, lekin uning tanasi bitta chiziqdan iborat bo'lsa, uni returnyozish kerak emas. Ammo agar sizda jingalak qavslar bo'lsa, unda odatdagi usulda bo'lgani kabi, siz aniq yozishingiz kerak return.

Misollar

1-misol.
() -> {}
Eng oddiy variant. Va eng ma'nosiz :) Chunki u hech narsa qilmaydi. 2-misol.
() -> ""
Bundan tashqari, qiziqarli variant. U hech narsani qabul qilmaydi va bo'sh qatorni qaytaradi ( returnkeraksiz deb o'tkazib yuborilgan). Xuddi shunday, lekin bilan return:
() -> {
    return "";
}
Misol 3. Salom dunyo lambdalar yordamida
() -> System.out.println("Hello world!")
Hech narsa qabul qilmaydi, hech narsa qaytarmaydi (biz returnqo'ng'iroqdan oldin qo'yib bo'lmaydi System.out.println(), chunki usulda qaytish turi println() — void)ekranda oddiygina yozuvni ko'rsatadi. Interfeysni amalga oshirish uchun ideal Runnable. Xuddi shu misol to'liqroq:
public class Main {
    public static void main(String[] args) {
        new Thread(() -> System.out.println("Hello world!")).start();
    }
}
Yoki shunday:
public class Main {
    public static void main(String[] args) {
        Thread t = new Thread(() -> System.out.println("Hello world!"));
        t.start();
    }
}
Yoki biz lambda ifodasini ob'ekt turi sifatida saqlashimiz Runnableva keyin uni konstruktorga o'tkazishimiz mumkin thread’а:
public class Main {
    public static void main(String[] args) {
        Runnable runnable = () -> System.out.println("Hello world!");
        Thread t = new Thread(runnable);
        t.start();
    }
}
Keling, lambda ifodasini o'zgaruvchiga saqlash momentini batafsil ko'rib chiqaylik. Interfeys Runnablebizga uning ob'ektlari usuli bo'lishi kerakligini aytadi public void run(). Interfeysga ko'ra, ishga tushirish usuli hech narsani parametr sifatida qabul qilmaydi. Va hech narsa qaytarmaydi (void). Shuning uchun, shu tarzda yozilayotganda, hech narsani qabul qilmaydigan yoki qaytarmaydigan ba'zi bir usul bilan ob'ekt yaratiladi. run()Bu interfeysdagi usulga juda mos keladi Runnable. Shuning uchun biz ushbu lambda ifodasini kabi o'zgaruvchiga qo'yishga muvaffaq bo'ldik Runnable. 4-misol
() -> 42
Shunga qaramay, u hech narsani qabul qilmaydi, lekin 42 raqamini qaytaradi. Ushbu lambda ifodasi turidagi o'zgaruvchiga joylashtirilishi mumkin Callable, chunki bu interfeys faqat bitta usulni belgilaydi, u quyidagicha ko'rinadi:
V call(),
VQaytish qiymatining turi qayerda (bizning holatlarimizda int). Shunga ko'ra, biz lambda ifodasini quyidagicha saqlashimiz mumkin:
Callable<Integer> c = () -> 42;
Misol 5. Lambda bir necha qatorda
() -> {
    String[] helloWorld = {"Hello", "world!"};
    System.out.println(helloWorld[0]);
    System.out.println(helloWorld[1]);
}
Shunga qaramay, bu parametrlarsiz va uning qaytish turisiz lambda ifodasidir void(chunki mavjud emas return). 6-misol
x -> x
Bu erda biz biror narsani o'zgaruvchiga olamiz хva uni qaytaramiz. E'tibor bering, agar faqat bitta parametr qabul qilingan bo'lsa, uning atrofidagi qavslar yozilishi shart emas. Xuddi shunday, lekin qavslar bilan:
(x) -> x
Va bu erda aniq variant mavjud return:
x -> {
    return x;
}
Yoki bu kabi, qavslar bilan va return:
(x) -> {
    return x;
}
Yoki turini aniq ko'rsatgan holda (va shunga mos ravishda qavslar bilan):
(int x) -> x
7-misol
x -> ++x
Biz buni qabul qilamiz хva qaytarib beramiz, lekin 1ko'proq. Siz uni quyidagicha qayta yozishingiz mumkin:
x -> x + 1
Ikkala holatda ham parametr, usul tanasi va so'z atrofida qavs ko'rsatmaymiz return, chunki bu shart emas. Qavslar va qaytarishli variantlar 6-misolda tasvirlangan. 8-misol
(x, y) -> x % y
Biz bir qismini qabul qilamiz хva уbo'linishning qolgan qismini xbilan qaytaramiz y. Parametrlar atrofidagi qavslar bu erda allaqachon talab qilinadi. Ular faqat bitta parametr mavjud bo'lganda ixtiyoriydir. Turlarning aniq ko'rsatilishi bilan shunday:
(double x, int y) -> x % y
9-misol
(Cat cat, String name, int age) -> {
    cat.setName(name);
    cat.setAge(age);
}
Biz Cat ob'ektini, nomi va butun soniga ega qatorni qabul qilamiz. Usulning o'zida biz o'tkazilgan ism va yoshni Mushukga o'rnatdik. catBizning o'zgaruvchimiz mos yozuvlar turi bo'lganligi sababli, lambda ifodasi tashqarisidagi Cat ob'ekti o'zgaradi (u ichida kiritilgan ism va yoshni oladi). Shunga o'xshash lambda ishlatadigan biroz murakkabroq versiya:
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 +
                '}';
    }
}
Natija: Cat{name='null', age=0} Cat{name='Murzik', age=3} Ko'rib turganingizdek, dastlab Cat obyekti bitta holatga ega edi, lekin lambda ifodasidan foydalangandan so'ng holat o'zgardi . Lambda ifodalari generiklar bilan yaxshi ishlaydi. Va agar biz sinfni yaratishimiz kerak bo'lsa Dog, masalan, u ham amalga oshiradi WithNameAndAge, u holda usulda main()biz lambda ifodasini o'zgartirmasdan, xuddi shu amallarni Dog bilan bajarishimiz mumkin. 3-topshiriq . Raqamni oladigan va mantiqiy qiymatni qaytaradigan usul bilan funktsional interfeysni yozing. trueO'tkazilgan son 13 ga qoldiqsiz bo'linadigan bo'lsa, qaytaradigan lambda ifodasi ko'rinishidagi bunday interfeysning amalga oshirilishini yozing.4 -topshiriq . Ikki qatorni oladigan va bir xil satrni qaytaradigan usul bilan funktsional interfeysni yozing. Eng uzun satrni qaytaradigan lambda ko'rinishida bunday interfeysning amalga oshirilishini yozing. 5-topshiriq . Funktsional interfeysni uchta kasr sonni qabul qiladigan usul bilan yozing: a, b, cva bir xil kasr sonini qaytaradi. Diskriminantni qaytaruvchi lambda ifodasi ko'rinishida bunday interfeysning amalga oshirilishini yozing. Kim unutdi, D = b^2 - 4ac . 6-topshiriq . 5-topshiriqdagi funktsional interfeysdan foydalanib, operatsiya natijasini qaytaruvchi lambda ifodasini yozing a * b^c. Java'da lambda ifodalari haqida mashhur. Misollar va topshiriqlar bilan. 2-qism.
Izohlar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION